You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不依赖系统信号实现Laravel Horizon的Supervisor暂停

Laravel Horizon自定义管控Supervisor暂停/恢复的问题

问题背景

在Laravel Horizon中部署了多个运行正常的Supervisor进程,需要通过自研Web界面对这些Supervisor做交互管控,核心操作包括暂停(pause)、恢复(continue,即取消暂停)。
需求约束是尽可能不通过系统调用实现功能:官方自带的artisan horizon:pause-supervisor命令底层通过posix_kill($supervisor->pid, 12)向进程发送信号实现暂停,需要规避这种实现方式。

尝试过的方案与异常现象

曾尝试通过直接实例化Supervisor对象的方式实现暂停功能,代码如下:

class HorizonManager
{
    private SupervisorRepository       $supervisors;
    private MasterSupervisorRepository $masters;
    private WorkloadRepository         $workload;
    private RedisJobRepository         $jobRepository;
    private QueueManager               $queueManager;

    public function __construct(MasterSupervisorRepository $masters, SupervisorRepository $supervisors, WorkloadRepository $workload, RedisJobRepository $jobRepository, QueueManager $manager)
    {
        $this->masters = $masters;
        $this->supervisors = $supervisors;
        $this->workload = $workload;
        $this->jobRepository = $jobRepository;
        $this->queueManager = $manager;

    }

    public function pauseSupervisor(string $supervisorName){
        $supervisor = $this->supervisors->find($supervisorName);
        $supervisorOpt = new SupervisorOptions(...$supervisor->options);
        $sup = new Supervisor($supervisorOpt);
        $sup->pause();
        $sup->persist();
        return $this->supervisors->find($supervisorName);
    }
}

测试时出现不符合预期的现象:

  • 方法返回结果显示Supervisor状态为已暂停
  • 实际对应进程并未真正暂停,即使对实例化的Supervisor对象执行了persist()持久化操作,Supervisor进程依旧保持运行状态,没有达到暂停效果。

问题根因

代码不生效的核心逻辑错误非常直白:

  • 你通过new Supervisor($supervisorOpt)创建的是一个全新的、存在于当前Web请求内存里的对象实例,和系统里正在运行的Supervisor常驻进程没有任何运行时绑定关系。
  • 调用pause()再persist(),本质只是把你新造的这个对象的"已暂停"状态写到了Redis里,但正在运行的Supervisor是个独立的常驻进程,它自己内存里的运行状态不会因为你在另一个请求里新建个对象改Redis就自动同步。
  • 官方用posix_kill发信号的本质是跨进程给正在运行的Supervisor传递通知,告诉它要切换到暂停状态。你跳过了通知运行中进程的步骤,只改了存在Redis里的状态标记,运行中的进程根本收不到要暂停的指令,自然会继续运行。

无系统调用的实现方法

如果要完全规避posix_kill类系统信号调用,可以直接用Horizon原生的Redis指令通道实现,不需要自己造对象改状态:
Horizon每个运行中的Supervisor进程,会持续监听Redis上对应自己名称的订阅频道,你只要往对应频道里写入暂停/恢复指令,Supervisor轮询到指令后会自动执行状态切换,全程不需要调用系统进程接口。

可直接复用的实现代码:

/**
 * 暂停指定名称的Supervisor
 */
public function pauseSupervisor(string $supervisorName): void
{
    $supervisor = $this->supervisors->find($supervisorName);
    if (!$supervisor) {
        throw new \InvalidArgumentException("指定Supervisor不存在: {$supervisorName}");
    }

    // 向Horizon专用Redis连接的对应Supervisor频道下发暂停指令
    app('redis')->connection('horizon')->publish(
        "supervisor:{$supervisorName}",
        json_encode(['command' => 'pause'])
    );
}

/**
 * 恢复指定名称的Supervisor
 */
public function continueSupervisor(string $supervisorName): void
{
    $supervisor = $this->supervisors->find($supervisorName);
    if (!$supervisor) {
        throw new \InvalidArgumentException("指定Supervisor不存在: {$supervisorName}");
    }

    app('redis')->connection('horizon')->publish(
        "supervisor:{$supervisorName}",
        json_encode(['command' => 'continue'])
    );
}

方案注意点

  • 该实现完全不依赖系统进程信号,所有通信走Horizon原生依赖的Redis发布订阅能力,和Horizon内部的进程交互逻辑完全兼容,不会有版本兼容问题
  • 不需要手动实例化Supervisor对象、也不需要手动调用persist()持久化状态:Supervisor收到指令执行完暂停/恢复操作后,会自动把最新的真实运行状态写入Redis,后续你从SupervisorRepository查询到的状态和实际运行状态完全一致
  • 如果需要批量操作所有Supervisor,直接遍历$this->supervisors->all()拿到所有Supervisor名称逐个下发指令即可;如果要做全局暂停/恢复,也可以直接往Master进程的监听频道发对应指令。

内容的提问来源于stack exchange,提问作者Brice Harismendy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 09:39:20