Skip to content

Retry batched completeJobs/failJobs instead of exiting the retry loop on first failure - #645

Open
ujjawal-yadav wants to merge 1 commit into
graphile:mainfrom
ujjawal-yadav:fix/636-batch-retry
Open

ujjawal-yadav wants to merge 1 commit into
graphile:mainfrom
ujjawal-yadav:fix/636-batch-retry

Conversation

@ujjawal-yadav

Copy link
Copy Markdown

Description

Fixes #636.

batch() retries batched completeJobs/failJobs via a loop governed by BATCH_RETRY_OPTIONS (20 attempts, backoff, per-code delays from RETRYABLE_ERROR_CODES). But its catch ended with an unconditional break, so the loop always exited after the first attempt — the backoff branch and BATCH_RETRY_OPTIONS were dead code, and a batched completion/failure was never retried.

A single transient error (e.g. a 40P01 deadlock, a 40001 serialization failure, or a dropped connection) therefore tore the worker pool down and left the jobs locked. Since those jobs had already run their task, they were re-executed once the lock expired (duplicate execution).

The fix makes the catch behave like makeEnhancedWithPgClient.withRetries (used by the rest of the SQL layer): a retryable error falls through and is retried with backoff (up to maxAttempts, then it throws); a non-retryable error fails fast. It also resets the backpressure deferred in allgood() so the pending refcount stays balanced now that a hold/release cycle can repeat across retries.

Tests

__tests__/batchRetry.test.ts, against real Postgres via the existing harness: a 40P01 deadlock on the first attempt is retried and the runner survives (asserting the completion was attempted ≥2 times) — fails on main; and a non-retryable error fails fast with a single attempt.

Notes

Complementary to #643 (fixes #641): that PR improves error reporting on the unbatched path and touches the batch() call sites, whereas this fixes the retry loop inside batch() — a different region of main.ts. A patch changeset is included.

batch()'s retry loop (governed by BATCH_RETRY_OPTIONS) ended its catch with
`break`, so the retry code never ran: a single transient error (deadlock /
serialization failure / dropped connection) on a batched completion tore the
worker pool down and left the jobs locked, so they were re-executed later
(duplicate execution). See graphile#636.

Fall through to the next iteration on a *retryable* error so the completion is
retried with backoff, and fail fast on a non-retryable error (throwing after a
single attempt) rather than looping maxAttempts times - matching
makeEnhancedWithPgClient.withRetries. Also clear `backpressure` in allgood()
after resolving it so the pending refcount stays balanced across a
retry-then-succeed sequence.

Adds regression tests: a transient 40P01 deadlock on the batched completion is
retried and the runner survives (asserting the retry actually fired), and a
non-retryable error fails fast with a single attempt.

Fixes graphile#636.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant