fix(go): back off before re-polling failed batch#284
Conversation
samorev Code Review Report
BLOCKING ISSUES (1)HIGH
Summary
Note:
Review metadatasamorev-assisted review (AI analysis by Tanya301/samorev) |
When a batch was received but finalization failed (a Nack failed, or Ack returned an error), the consumer loop re-polled immediately. The unfinished batch is redelivered by pgque.next_batch at once, so a persistent nack/ack failure (e.g. partial grants) produced a tight loop re-running every handler at full speed, and even one transient ack failure re-executed the whole batch with zero delay. Sleep pollInterval (respecting ctx cancellation) before re-polling on both the nack-failure and ack-error paths. The Ack n==0 stale/double ack case stays warning-only with no sleep. Verified red/green: the new tests counted 52k/236k Receive calls in 400 ms on the unfixed code; with the fix the count stays within the pollInterval bound. https://claude.ai/code/session_01KAaEGkQZmey1D1xCsVGmqv
0d8ad9c to
2ed70d8
Compare
Deep review + real testing (unreviewed pass — prior "samorev" review disregarded)Rebased onto current Fix reviewCorrect and consistent. Both new backoff sleeps use the same ctx-aware
Double-processing semantics (by design, documented). The fix delays re-polling; Non-blocking observations:
Red/green TDD — verified myselfTwo guard tests added (nack-failure, ack-failure) counting With the fix, both pass (~8 Receive calls each). Decisive demonstration of the hot-loop Real testingPostgres 17 +
One pre-existing flake, not a blocker: under full-suite parallel load, Verdict: MERGE-READY. |
samorev Code Review Report
No blocking issues. Three non-blocking observations below; two are already tracked in #299. What was verified
NON-BLOCKING (3)INFO
INFO
LOW
Double-processing semantics (not a finding): the fix delays re-polling; it does not prevent handler re-execution. An unfinished batch is redelivered whole, so every handler re-runs — inherent to PgQ's at-least-once, all-or-nothing Summary
Note:
Review metadatasamorev-assisted review (AI analysis by Tanya301/samorev) |
Bug
In
clients/go/consumer.go, the consumer loop sleptpollIntervalonly on receive error or empty batch. When a batch WAS received but finalization failed —nackFailedtrue (loop didcontinuewith no sleep) orAckreturned an error (fell through to the loop top with no sleep) — the loop re-polled immediately. Because the batch was never finished,pgque.next_batchreturns the SAME batch instantly, so a persistent nack/ack failure (e.g. partial grants where receive works but nack/ack don't) produced a tight loop at full speed: re-receive same batch, re-run all handlers (duplicate side effects), fail again, repeat. Even one transient ack failure re-executed the whole batch's handlers with zero delay.Fix
Sleep
pollInterval(respecting ctx cancellation, sameselect { case <-ctx.Done() ... case <-time.After(...) }pattern used in the receive-error and empty paths) before re-polling on both the nack-failure path and the Ack-error path. TheAckn==0 stale/double-ack case stays warning-only and gains no sleep.Red/green TDD: added
TestConsumer_NackFailure_BacksOffBeforeRepollandTestConsumer_AckFailure_BacksOffBeforeRepollwith a stub backend (redeliverStubBackend) whoseReceivealways returns the same batch and whoseNack/Ackfail; the tests bound the number ofReceivecalls in a 400 ms window with a 50 ms poll interval.Verification
Red (on unfixed code,
go test -run BacksOffBeforeRepoll ./...inclients/go):Green (with fix, in
clients/go):Addresses finding B1 (Go) of #283
https://claude.ai/code/session_01KAaEGkQZmey1D1xCsVGmqv
Generated by Claude Code