fix(billing): 重排任务复用钱包预留

平台额度跨窗口重排时保留任务已有的钱包预留,后续 Worker 领取复用同一幂等键,避免重复 reserve/release 事务。\n\n补齐排队任务取消后的原子结算释放,并允许任务 attempt 持久化软负载、满载原因和选择原因等有界路由快照。\n\n验证:go test ./...;go vet ./...;钱包预留复用与排队取消 PostgreSQL 集成测试。
This commit is contained in:
2026-08-03 18:09:47 +08:00
parent 13faf1d072
commit c8d04ca731
4 changed files with 147 additions and 1 deletions
+10
View File
@@ -730,6 +730,12 @@ func (s *Service) executeWithToken(ctx context.Context, task store.GatewayTask,
var firstPreprocessing parameterPreprocessingLog
var walletReservations []store.WalletBillingReservation
walletReservationFinalized := false
retainWalletReservationForQueue := func() {
// A queued task still owns its billing hold. Keeping the same active
// reservation lets the next Worker claim reuse its idempotency key
// instead of emitting another reserve/release pair.
walletReservationFinalized = true
}
defer func() {
if !walletReservationFinalized && len(walletReservations) > 0 {
_ = s.store.ReleaseTaskBillingReservations(context.WithoutCancel(ctx), walletReservations, "task_not_settled")
@@ -844,6 +850,7 @@ candidatesLoop:
if queueErr != nil {
return Result{}, queueErr
}
retainWalletReservationForQueue()
return Result{Task: queued, Output: queued.Result}, &TaskQueuedError{Delay: delay}
}
return Result{}, admissionErr
@@ -1098,6 +1105,7 @@ candidatesLoop:
if queueErr != nil {
return Result{}, queueErr
}
retainWalletReservationForQueue()
return Result{Task: queued, Output: queued.Result}, &TaskQueuedError{Delay: delay}
}
attemptNo = s.recordFailedAttempt(ctx, failedAttemptRecord{
@@ -1222,6 +1230,7 @@ candidatesLoop:
if queueErr != nil {
return Result{}, queueErr
}
retainWalletReservationForQueue()
return Result{Task: queued, Output: queued.Result}, &TaskQueuedError{Delay: 0}
}
if task.AsyncMode && errors.Is(lastErr, store.ErrRateLimited) && store.RateLimitRetryable(lastErr) {
@@ -1232,6 +1241,7 @@ candidatesLoop:
if queueErr != nil {
return Result{}, queueErr
}
retainWalletReservationForQueue()
return Result{Task: queued, Output: queued.Result}, &TaskQueuedError{Delay: delay}
}
extraMetrics := []map[string]any{}