fix(queue): 回收失效任务并阻止不确定提交重放

Worker 进程心跳不能证明单个 River job goroutine 仍存活,改以任务执行租约作为回收所有权栅栏。\n\n排队任务若上一次 attempt 在租约中断时处于 submitting 或 response_received,则转入人工复核并生成 release 记录,不再重新提交上游;明确被上游拒绝的响应仍允许重试。\n\n验证:go vet ./...;env -u AI_GATEWAY_TEST_DATABASE_URL go test ./... -count=1;真实 PostgreSQL 下相关 store 与 River 集成测试通过。
This commit is contained in:
2026-07-30 19:15:19 +08:00
parent 9e06ed6162
commit f33d6d64e0
4 changed files with 256 additions and 65 deletions
+13 -5
View File
@@ -254,18 +254,26 @@ WHERE id = $1`, yielded.RiverJobID).Scan(&yieldedState); err != nil {
if err != nil {
t.Fatalf("recover orphaned River jobs: %v", err)
}
if recovered != 1 {
t.Fatalf("recovered jobs=%d, want 1", recovered)
if recovered != 2 {
t.Fatalf("recovered jobs=%d, want 2", recovered)
}
var orphanedState, protectedState string
var orphanedState, protectedState, yieldedRecoveredState string
if err := db.Pool().QueryRow(ctx, `SELECT state::text FROM river_job WHERE id = $1`, orphaned.RiverJobID).Scan(&orphanedState); err != nil {
t.Fatalf("read orphaned River job state: %v", err)
}
if err := db.Pool().QueryRow(ctx, `SELECT state::text FROM river_job WHERE id = $1`, protected.RiverJobID).Scan(&protectedState); err != nil {
t.Fatalf("read protected River job state: %v", err)
}
if orphanedState != "retryable" || protectedState != "running" {
t.Fatalf("River states orphaned=%s protected=%s, want retryable/running", orphanedState, protectedState)
if err := db.Pool().QueryRow(ctx, `SELECT state::text FROM river_job WHERE id = $1`, yielded.RiverJobID).Scan(&yieldedRecoveredState); err != nil {
t.Fatalf("read yielded River job state: %v", err)
}
if orphanedState != "retryable" || protectedState != "running" || yieldedRecoveredState != "retryable" {
t.Fatalf(
"River states orphaned=%s protected=%s yielded=%s, want retryable/running/retryable",
orphanedState,
protectedState,
yieldedRecoveredState,
)
}
recovered, err = db.RecoverOrphanedAsyncRiverJobs(ctx, 30*time.Second, 10)
if err != nil || recovered != 0 {