Skip to the content.

单实例任务执行与恢复

持久队列是数据库中的 Queued 任务。Coravel 每次只触发一个短 Tick,执行器读取到时任务并领取;实际处理独立运行,不占用 Coravel 调度回调直到结束。一次内存唤醒丢失不会丢失数据库任务,下次 Tick 仍能发现它。

注册与资源所有权

处理器以明确的名称和版本注册,白名单中保存固定的泛型反序列化和 DI 解析委托。用户参数不能选择 CLR 类型。注册同时固定 Queue、超时和 SafeToRetry;未知名称/版本或队列不匹配的任务会失败,不会动态激活代码。只有有效载荷反序列化成功后才解析处理器。

Queue 的 SemaphoreSlim 限制当前仍占用资源的执行数。Tick 先预留槽,再调用 TryStart;领取失败就释放。领取成功后在独立 DI scope 中运行处理器,进度和控制查询使用其他短 scope,避免并发访问业务 DbContext。槽一直保留到处理器返回、异步 scope 清理和最终状态发布完成;取消请求或超时都不能提前释放槽。

每次执行的业务 CancellationToken 关联宿主停止和超时。独立观察任务按间隔查询当前 Attempt Token 的状态;数据库中取消、执行已结束或身份失效都会通知处理器停止。观察数据库失败也会取消工作,无法继续确认取消状态时不让处理器无限运行。

如何发布结束状态

处理器及其依赖全部结束后,执行器才发布终态。普通异常记 handler_failed,反序列化异常记 payload_invalid,超时记 execution_timeout;它们不会自动重试。非法结果引用也明确失败,不能把校验异常当成数据库暂时不可用而留下 Running。

持久取消请求优先于结束更新,所以正常完成或异常退出都可能最终成为 Cancelled。宿主停止另行记录旧 Attempt 为 Interrupted;注册为 SafeToRetry 且仍有次数的 Job 会原子重新排队,不安全的留在 Interrupted。工作忽略 CancellationToken 时,执行器继续等待,不能一边让旧执行工作一边释放容量重跑。

结束写入使用独立、有限超时的存储操作,不沿用已经取消的业务 Token。如果数据库确实无法确认写入,执行器记录服务端日志并保留 Running,供下次启动恢复;不能伪造成功。并发资源则在实际工作及清理结束后释放。

启动与停机

StartAsync 在允许 Tick 领取之前扫描遗留 Running 执行。每项恢复都验证原 Token,并通过存储原子结束和重排。只有白名单中明确 SafeToRetry 且队列一致的处理器可以重跑;取消和最大执行次数仍优先。该开关要求业务本身可以幂等重跑,不能让非幂等邮件或外部支付自动安全。

StopAsync 先停止领取并取消当前业务 Token,然后等待已领取任务结束。取消回调本身也可能阻塞,因此取消通知与后续执行等待都受宿主停止 Token 的期限约束。期限可以结束等待,但不能强制终止处理器或释放其容量;此时未完成记录留待恢复。业务处理器应尊重取消并等待其子任务,不能以启动未跟踪后台任务后立即返回的方式绕开容量约束。

当前约定一个数据库由一个执行宿主运行。启动扫描不能与另一执行实例同时使用;多实例需要未来增加持久租约和执行归属,不能仅靠现有 Token 推断实例已失联。