|
27 | 27 | // name). The DLQ controller decodes that payload to recover the affected request, then |
28 | 28 | // transitions it to RequestStateRecordedNotGreen — the conservative not-green verdict |
29 | 29 | // for gating (see entity.RequestState) — with an idempotent optimistic-locking write so |
30 | | -// concurrent |
31 | | -// activity (a late successful pipeline transition) wins cleanly. If the request had |
| 30 | +// concurrent activity (a late successful pipeline transition) wins cleanly. If the request had |
32 | 31 | // already been admitted (processing) and was holding a concurrency slot, the |
33 | 32 | // reconciler also releases it by CAS-decrementing the queue's in_flight_count, per |
34 | 33 | // doc/rfc/stovepipe/steps/process.md#in_flight_count-integrity. |
@@ -61,8 +60,16 @@ func TopicKey(main consumer.TopicKey) consumer.TopicKey { |
61 | 60 | // failRequest transitions request to RequestStateRecordedNotGreen if it is not already |
62 | 61 | // in a terminal state. If the request had reached RequestStateProcessing — meaning process's |
63 | 62 | // admit step already CAS-incremented the queue's in_flight_count for it — the queue's |
64 | | -// slot is released first, so a crash between the two writes leaves the count still |
65 | | -// bound to a non-terminal request rather than double-released. |
| 63 | +// slot is released first. Queue and Request are separate entities with no cross-entity |
| 64 | +// transaction, so the two writes cannot be atomic and the ordering picks which crash |
| 65 | +// failure mode we accept: a crash between the writes leaves the request non-terminal, |
| 66 | +// redelivery re-runs reconciliation, and releaseSlot (which tracks no per-request slot |
| 67 | +// ownership) decrements again — transiently over-admitting by one slot until the |
| 68 | +// under-count re-converges at releaseSlot's zero clamp. The reverse order would leak |
| 69 | +// the slot instead: redelivery skips terminal requests, permanently shrinking the |
| 70 | +// queue's capacity toward a wedge. Over-admission is the failure mode we prefer. See |
| 71 | +// doc/rfc/stovepipe/steps/process.md#in_flight_count-integrity for the broader |
| 72 | +// counter-drift story. |
66 | 73 | func failRequest(ctx context.Context, store storage.Storage, logger *zap.SugaredLogger, requestID string) error { |
67 | 74 | request, err := store.GetRequestStore().Get(ctx, requestID) |
68 | 75 | if err != nil { |
@@ -93,7 +100,7 @@ func failRequest(ctx context.Context, store storage.Storage, logger *zap.Sugared |
93 | 100 | updated.State = entity.RequestStateRecordedNotGreen |
94 | 101 | newVersion := request.Version + 1 |
95 | 102 | if err := store.GetRequestStore().Update(ctx, updated, request.Version, newVersion); err != nil { |
96 | | - return fmt.Errorf("failed to update request %s state to failed: %w", requestID, err) |
| 103 | + return fmt.Errorf("failed to update request %s state to recorded_not_green: %w", requestID, err) |
97 | 104 | } |
98 | 105 | logger.Infow("dlq reconcile: request forced terminal not-green", |
99 | 106 | "request_id", requestID, |
|
0 commit comments