Shutdown Check

搜索文档

查找页面或章节

从空闲端口检测到最后的进程和端口检查,完整走一遍检查流程。

shutdown-check 像部署平台一样对待你的服务:运行真实的启动命令,发送 HTTP 流量,发出 SIGTERM,然后在进程外部观察结果。它不会 import 你的应用,也不会直接调用关闭处理函数。

什么是黑盒关闭测试?

黑盒关闭测试只使用服务对外可见的行为:启动命令、端口、HTTP 路由、信号处理和退出状态。单元测试常常漏掉的问题都能覆盖到,包括 shell 包装进程、框架行为、真实的 socket、进程退出和子进程。

代价是可见性有限。shutdown-check 能看到 HTTP 响应和进程事件,但除非响应本身能证明,否则它无法知道内部的数据库写入或队列消息是否已经完成。具体边界见兼容性与限制。

一次运行会经过哪些阶段?

各阶段总是按以下顺序执行:

阶段shutdown-check 做什么主要失败码
1确认端口空闲SC001
2启动配置的命令SC002
3等待服务就绪SC100–101
4发出测试请求,并确认请求正在处理SC110–111
5确认进程和测试请求仍然存活SC112
6发送 SIGTERMSC113
7可选:重复发送信号SC114
8可选:检查就绪状态撤回和新请求拒绝SC310–312
9等待进行中的响应完成SC200–203
10检查进程退出SC300–301
11确认端口已关闭SC302

所有必需的等待结束后,报告的第一个失败就是最早出现的有效问题。通过时的诊断码是 SC000。

1. 检查端口是否空闲

启动服务之前,shutdown-check 会向就绪检查 URL 发一个短请求,这时连接必须被拒绝。只要收到任何响应、超时,或者出现无法判断的网络错误,都说明无法确认端口空闲,结果为 SC001。

这样可以避免请求打到一个已经在运行的服务上,得出虚假的通过结果。同时也让并行测试的结果可预期:每个测试都需要自己的端口。

2. 启动真实命令

命令数组直接启动,不经过 shell。命令在独立的进程组中运行,cwd 和 env 取自配置。

shutdown-check.json
{
  "command": ["node", "dist/server.js"],
  "cwd": ".",
  "env": { "PORT": "3510", "NODE_ENV": "production" }
}

尽可能使用与生产环境相同的入口。直接启动服务,还能避开那些吞掉 SIGTERM、或者先于子进程中的服务退出的包装进程。

3. 等待服务就绪

shutdown-check 会轮询 baseUrl + readiness.path,直到拿到预期的状态码。连接被拒绝或返回其他状态码时会一直重试,直到超过 readiness.timeoutMs。

  • 进程一直存活,但始终没有就绪:SC100。
  • 进程退出或无法启动:SC101。

就绪检查通过之前不会发送任何测试请求。这样可以把启动失败和关闭失败区分开。

4. 确认请求正在处理

测试会发出一个或多个测试请求,然后等待配置的启动确认(start barrier)。

response-headers 启动确认

每个请求都必须在响应体尚未结束时收到响应头。适用于流式路由,或者在处理完成前就先发出响应头的可控接口。

probe 启动确认

测试请求保持处理中时,另一个独立路由必须从 inactiveStatus 变为 activeStatus。适用于处理函数要等全部工作完成后才发送响应的场景。

如果探测路由一开始就处于活跃状态,结果为 SC110;如果无法确认请求正在处理,结果为 SC111。完整示例见进行中的工作指南。

5. 再次确认进程和请求

从启动确认通过到发出信号,中间有一个很短的间隔。shutdown-check 会确认在这段间隔里,服务和每个测试请求都仍然存活。只要有一个已经结束,结果就是 SC112。

这样,即使测试请求只是勉强慢到能通过启动确认,测试也不会因此误判为通过。

6. 发送 SIGTERM

shutdown-check 把 SIGTERM 发给它启动的那个进程本身。这一阶段不会向整个进程组发信号。正因为如此,包装进程的问题会暴露出来:启动器必须把信号转发给真正的服务。

如果操作系统拒绝发送信号,结果为 SC113。发送成功后,关闭截止时间开始计时,时间线记录 signal sent。

7. 按需重复发送信号

设置了 repeatSignalAfterMs 时,只有在进程和最初的请求都仍在处理中时,shutdown-check 才会再发一次 SIGTERM。这是为了确认第二个信号不会让进程提前退出。

如果无法测试或无法发送重复信号,结果为 SC114。重复发送的延迟要比测试请求的耗时和 shutdown.deadlineMs 都短。

8. 检查流量排空

这些检查都是可选的。

设置 readinessWithdrawal: true 后,shutdown-check 会在发出信号后轮询就绪检查路由。返回非就绪状态码或连接被拒绝,都算作已撤回就绪状态;超时不算。如果就绪状态一直没有变化,结果为 SC310。

设置 newRequests 后,测试接着会在最初的请求仍在处理时,再发一个新的 GET。这个请求必须返回配置的拒绝状态码;如果配置允许,也可以是连接被拒绝。

  • 新请求被接受或超时:SC311。
  • 最初的请求在拒绝测试之前就已结束:SC312。

在这段时间内返回 503 的服务示例,见就绪检查与排空。

9. 等待每个响应完成

每个测试请求都必须在 shutdown.deadlineMs 之前完成,并返回配置的状态码,以及可选的响应体文本。

结果诊断码
请求一直没有结束SC200
连接被重置或提前关闭SC201
最终状态码与 workload.status 不一致SC202
响应体中不包含 workload.bodyIncludesSC203

并发请求会逐个检查。按请求顺序,第一个失败的请求决定诊断码。

10. 检查进程退出

服务必须在同一个关闭截止时间之前退出。

  • 进程仍在运行:SC300。
  • 退出码与 shutdown.exitCode 不一致,或者进程被信号终止:SC301。

默认的预期退出码是 0。对进程管理器和监控系统来说,一次计划内的关闭通常应该表现为正常退出。

11. 确认端口已关闭

启动的进程退出后,shutdown-check 会再请求一次就绪检查 URL,这时连接必须被拒绝。如果仍有响应,多半是某个子进程中的服务还在运行,结果为 SC302。

测试正是靠这一步发现 npm start、shell 脚本或其他启动器自己退出了,却没有停掉真正的服务。

失败后如何清理?

无论结果如何,shutdown-check 都会关闭自己发起的请求并清除定时器。如果服务或子进程还在,工具会向它创建的进程组发送 SIGKILL。清理可以避免失败的测试一直占用端口,影响下一次运行。

清理不等于关闭通过。只有服务在需要清理之前自己完成工作、以预期的退出码退出并关闭端口,才算通过。

怎样使用时间线?

从上往下看,找到 check failed 之前最后一个成功的事件。例如:

  • 没有 service ready:启动或就绪检查失败
  • 没有 work confirmed active:启动确认失败
  • 有 signal sent,但之后请求一直没有完成:排空过程卡住了
  • 有 process exited,但端口仍然开着:有子进程中的服务没有退出

每个事件以及 JSON 中可用的字段,见输出参考。