#6139·swoole-src

SIGTERM 平滑退出时过早移除 worker pipe 读事件,导致已提交 Task 的 onFinish 丢失

Author: caijweiCreated Aug 4, 2026Updated Aug 13, 2026

在 Swoole Server 平滑退出过程中,如果 Event Worker 中仍有请求正在等待 Task Worker 的执行结果,Task Worker 可以正常完成任务并发送 FINISH 消息,但 Event Worker 不会再处理该消息,因此 onFinish 或 Task 回调无法触发,存量请求最终只能等待超时。

该问题可以从 Swoole 源码中的退出流程直接看出。

源码调用链

Event Worker 收到 SIGTERM 后,worker_signal_handler() 会调用:

cpp
case SIGTERM:
    if (swoole_event_is_available()) {
        // Event Worker
        sw_server()->stop_async_worker(sw_worker());
    } else {
        // Task Worker
        sw_worker()->shutdown();
    }
    break;

参见:[Swoole 5.1.5 worker.cc](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/worker.cc#L45-L61)

Event Worker 启动时,会将 worker->pipe_worker 注册到 Reactor,并由 Worker_onPipeReceive 处理这条 pipe 上的消息:

cpp
reactor->add(worker->pipe_worker, SW_EVENT_READ);
reactor->set_handler(SW_FD_PIPE, Worker_onPipeReceive);

Worker_onPipeReceive() 读取消息后,会调用:

cpp
serv->worker_accept_event(&pipe_buffer->info);

参见:[Swoole 5.1.5 worker.cc](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/worker.cc#L493-L495)、[[Worker_onPipeReceive](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/worker.cc#L550-L562)](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/worker.cc#L550-L562)

Task Worker 完成任务时,reply_task_result() 会把消息类型设置为:

cpp
buf.info.type = SW_SERVER_EVENT_FINISH;

然后通过 worker pipe 把结果发回原 Event Worker:

cpp
ret = send_to_worker_from_worker(
    worker,
    &buf,
    sizeof(buf.info) + buf.info.len,
    SW_PIPE_MASTER
);

参见:[Swoole 5.1.5 task_worker.cc](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/task_worker.cc#L253-L310)

Event Worker 收到这条消息后,正常情况下会进入:

cpp
case SW_SERVER_EVENT_FINISH: {
    onFinish(this, (EventData *) message_bus.get_buffer());
    break;
}

参见:[Swoole 5.1.5 worker.cc](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/worker.cc#L190-L193)

但是,Event Worker 收到 SIGTERM 并进入 stop_async_worker() 后,会执行:

cpp
if (worker->pipe_worker && !worker->pipe_worker->removed) {
    reactor->remove_read_event(worker->pipe_worker);
}

参见:[Swoole 5.1.5 worker.cc](https://github.com/swoole/swoole-src/blob/v5.1.5/src/server/worker.cc#L334-L363)

也就是说,Event Worker 在等待 Reactor 中其他存量事件退出之前,就先移除了接收 worker pipe 消息的读事件。

因此,完整的问题链路是:

  1. Event Worker 提交一个 Task,并等待返回结果。
  2. Task Worker 已经开始执行该任务。
  3. Event Worker 收到 SIGTERM,进入 stop_async_worker()
  4. stop_async_worker() 调用 remove_read_event(worker->pipe_worker)
  5. Task Worker 正常执行完成,并发送 SW_SERVER_EVENT_FINISH
  6. Event Worker 已经不再监听 worker pipe,无法处理 FINISH 消息。
  7. worker_accept_event() 不会收到该消息,onFinish 或 Task 回调不再触发。
  8. 正在等待 Task 结果的存量请求最终超时。

max_wait_time 无法解决该问题

增大 max_wait_time 只能延长 Reactor 等待其他事件退出的时间。

但是,worker pipe 的读事件在进入等待阶段之前就已经被移除。即使 Task 在 max_wait_time 内正常完成,Event Worker 也不会再读取对应的 FINISH 消息。

所以该问题并不是 Task 执行时间超过了退出超时,而是退出流程过早停止了 FINISH 消息的接收。

问题边界

这里讨论的不是:

  • 退出后新提交的 Task;
  • 尚未开始执行的排队 Task;
  • Task Worker 被强制终止;
  • max_wait_time 设置过短。

这里讨论的是:

  • Task 在退出流程开始前已经成功提交;
  • Task Worker 已经开始执行;
  • Task Worker 最终正常执行完成;
  • 只有返回结果对应的 FINISH 消息没有被 Event Worker 处理。

该问题不依赖 Hyperf。Hyperf 只是依赖 Swoole 的 onFinish 获取 Task 返回值;即使直接使用原生 Swoole 的 Task 回调机制,也同样依赖 SW_SERVER_EVENT_FINISH 的处理。

当前项目使用 Swoole 5.1.5。检查 Swoole 6.2.2 的相关实现后,SW_SERVER_EVENT_FINISH 仍然通过 worker pipe 处理,而 stop_async_worker() 中仍然会移除该 pipe 的读事件,因此最新版本似乎仍存在相同行为。

参见:

期望行为

平滑退出时,可以停止接收新请求,但对于退出前已经提交、且仍被存量请求等待的 Task,以及存量请求提的新task,应继续接收并处理其 FINISH 消息。

worker->pipe_worker 的读事件至少应保留到以下条件之一成立:

  • 已提交 Task 的返回结果已经处理完成;
  • Event Worker 中不再存在依赖 Task 返回值的存量请求;
  • 达到 max_wait_time,执行强制退出。

不应在 Event Worker 刚进入 stop_async_worker() 时,就直接停止处理所有 worker pipe 消息。

想确认:

  1. 在平滑退出开始时立即移除 worker->pipe_worker 的读事件,这是预期设计还是一个缺陷?
  2. Event Worker 是否可以停止接收新任务,同时继续处理已提交 Task 对应的 SW_SERVER_EVENT_FINISH
  3. 是否可以将 worker pipe 读事件的移除延迟到 Reactor 中的存量请求完成之后?
  4. 如果这是预期行为,官方是否有推荐方式保证退出前已提交 Task 的返回结果能够被正常处理?