平台取消了一轮又重跑一遍,/status 因此返回了 52 分钟的 HTTP 503
我们的探测器跑在 Cloudflare Workers 上。cron 触发器每五分钟调用它一次,每次调用领取到期的 server 并检查它们。平台偶尔会在一次调用进行到一半时把它取消,片刻之后再把同一个 cron 事件投递一次。之前的一篇文章讲的是被落下的 server 要等多久;这一篇讲的是取消对我们公开状态页的影响。
被取消的一轮留下了什么
一轮领取一台 server 时,会写一行任务记录,带上请求时间。工作结束时,探测器再写入结束时间。被取消的调用走不到这一步,之后也没有任何东西会回头把这一行关上。
重新投递的那次调用不会马上再领这台 server:未结束、且不到 15 分钟的任务算作在途,这样两次调用不会同时检查同一台 server。15 分钟过后,后面的某一轮会检查这台 server,并让一个新任务正常结束。旧的那一行仍然开着。
/status 原来怎么处理
/zh/status 报告的是探测器本身有没有在跑,外部监控会定时访问它。其中一条规则是:过去一小时内请求的任务,只要有一个超过 8 分钟仍未结束,页面就返回 HTTP 503。2026-10-08 之前,这条规则不看这台 server 之后有没有被重新检查。被取消的一轮留下的那一行,会让页面从它的第 8 分钟一直 503 到第 60 分钟它离开一小时窗口为止,共 52 分钟。
2026-10-07 这天,大约两小时之内发生了两次。
- 15:50 UTC。 这次调用运行了 69.3 秒(CPU 205 毫秒)、完成 8 台 server 之后,被平台取消。15:51:18,一次计划时间相同的调用开始运行,检查了另外 11 台。两个在 15:50:54 请求的任务留在了未结束状态。/status 从 15:58:55 开始返回 503,直到 16:51 左右。两台 server 在 16:07:03 之前都已重新检查完。
- 17:55 UTC。 这次调用运行 150.5 秒后被平台取消。一个 17:57:09 请求的任务根本还没开始。重新投递的调用在 17:57:39 开始。/status 从 18:05:12 开始返回 503,直到 18:57 左右。这台 server 在 18:12:50 已重新检查完。
两次都一样:大约 44 分钟里,页面说有任务卡住了,而对应的 server 其实已经重新检查过。
为什么不干脆不算它们
最简单的改法是不再计算这类任务。我们没有这样做,原因有两个。第一,那个 503 不是假话:确实有任务被请求了却没有结束。第二,真正的故障看起来也是这样。探测器领取任务后,如果在写出结果之前卡死或崩溃,留下的是同样的一行。页面上的其他信号最多可能有一小时看不到这种情况,「卡住」这项检查正是为它设的。把未结束任务藏起来的规则,也会把这种情况一起藏起来。
所以问题要窄得多。如果同一台 server 之后已有更新的任务检查完并结束,这个未结束的任务说明探测器在工作;如果后面什么都没有,那就是我们不知道。
现在的规则
自 2026-10-08 起,一个未结束的任务只有同时满足以下三条,才算卡住:
- 它是过去一小时内请求的;
- 从请求起已经超过 8 分钟仍未结束;
- 同一台 server 之后没有更新的任务结束。
满足前两条、但不满足第三条的任务单独计数,即「已放弃并重做」。只有卡住的任务会让 /status 返回 503。「更新」指请求时间更晚;已经领取但还没结束的新任务不算,别的 server 的已结束任务也不算。8 分钟和一小时窗口都没有改。
/status 现在显示什么
两个数始终显示,为 0 时也显示。第一行是「过去 1 小时内超过 8 分钟仍未完成、且同一 target 之后没有新任务完成的探测任务」,后面跟着个数;第二行是「过去 1 小时内超过 8 分钟仍未完成、但同一 target 之后已有新任务完成的探测任务」,后面跟着它的个数。
第一个数大于 0 时,页面返回 503,并写明原因:「过去 1 小时内请求的探测任务中,至少有一个超过 8 分钟仍未完成,且同一 target 之后没有新任务完成。出现这种情况时,本页返回 HTTP 503。」
这几行的措辞也改成了「请求」。页面显示的时间是任务被请求的时间,而 17:57 那个任务请求了却从未开始。
仍然存在的窗口
一次取消之后,这台 server 要等到未结束任务请求后 15 分钟才能再被领取。所以 /status 仍会从第 8 分钟开始返回 503,直到新任务结束,通常在第 15 到第 20 分钟之间。按新规则,17:57 那个任务对应 7 分 40 秒的 503(18:05:09 到 18:12:50),而不是 52 分钟;15:50 那两个任务对应 8 分 9 秒。
我们保留了这个窗口。决定它长短的是 15 分钟的在途守卫;另一种做法是把「同一台 server 有更新的任务正在运行」也算数,那会改变规则本身,只为省下一两分钟。取消之后,可用性监测仍可能报警一次。那次报警说的是确实发生过的事。
新规则覆盖不到的情况
自 2026-10-07 的另一次修复起,探测器先把 server 的下次检查时间往后推,再把任务标为结束。如果它停在这两次写入之间,或者第二次写入失败,run 已经写好,下次检查在 12 小时之后,任务却仍未结束。一小时内不会有更新的任务,所以它会一直算作卡住,直到离开窗口。
我们见过一个这样的任务。2026-10-08 05:11:09 UTC 请求的一个任务,它的 run 在 05:11:09.632 开始,它的 server 下次检查时间已推到 17:10,任务本身却没有结束时间。那次调用的 Worker 日志显示,是第二次写入失败了:05:11:30.803,探测器记下写结束时间的那次更新返回了错误码 522(连接超时),然后接着跑完这一轮。这次调用正常结束,平台没有取消它。新规则当天晚些时候上线,也会同样把这个任务算作卡住。
2026-10-01 以来,有七轮留下了未结束的任务,共 50 个。其中六轮,每台 server 都由一个更新的任务重新检查并结束,最晚在未结束任务请求后 24 分钟以内。第七轮就是 05:11 那一个。
我们怎么核的
/status 只读一个数据库函数,它只返回计数和时间,不含任何单台 server 的信息;上面的规则就是这个函数自 2026-10-08 起的定义。2026-10-07 的时间来自任务表、那两轮 Worker 的实时日志,以及我们当天下午对 /status 的轮询。7 分 40 秒是上线前在生产数据上运行新函数得到的,那个事务最后回滚了。10 月 1 日以来的数字,统计的是 2026-10-01 00:00 UTC 以来请求、没有结束时间、且在 2026-10-08 13:17 UTC 时已请求超过一小时的全部任务;一个任务算被取代,是指同一台 server 有一个请求时间更晚、且有结束时间的任务。七轮的原因没有逐一追查;2026-10-07 那两次,是平台自己的记录里能看到调用被取消的两次;05:11 那个任务的原因来自那次调用的 Worker 日志。所有数据库数字都来自只读查询。