Repository navigation
fix(worker): retry transient balance-sync failures and stop silently dropping them (#645) - #851
Merged
Merged
Conversation
…dropping them (mergepay#645) balance-sync swallowed every per-account error inside syncAccountBalances, so a Horizon/upstream failure that exhausted loadAccount's own read-retry budget (or a transient throw from the accountBalance upsert) was counted as `synced` and the `failed` total stayed dead at zero — the dropped-task behaviour mergepay#645 targets. Route each account's sync through the shared withRetry wrapper (classify and re-attempt only demonstrably-transient failures via classifyJobFailure; fail fast on 4xx), then account honestly: a permanent/indeterminate/exhausted failure increments `failed` and is logged as structured pino with the category and a scrubbed reason. A missing account stays a non-failure. Adds the first test suite for the task (8 cases), proving the retry, bounded- exhaustion, permanent-fail-fast and sibling-isolation paths.
|
@OlamideAdesina Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Closes #645 by hardening the one background worker task processor that still contradicts the issue's goal:
src/worker/tasks/balance-sync.ts.A prior assessment (my evidence comment on #645) found the worker cycle retry/dead-letter machinery already on
mainvia #708/#783 (withRetry,classifyJobFailure,recordCycleFailure). That part is genuinely done. Butbalance-sync.ts— a sibling worker task, explicitly in the issue's scope ("any associated worker task processors"; "prevents dropped tasks") — had the exact failure mode #645 describes, and zero tests:syncAccountBalanceswrapped its whole body intry/catchand swallowed every error (including aloadAccountthat had already exhausted its internal Horizon read-retry budget, and any transient throw from theaccountBalanceupsert). Because nothing rethrew, the outer loop'scatchcould never fire, sosynced++ran andfailedwas dead code pinned at 0. A Horizon/RPC outage reported{ synced: N, failed: 0 }— tasks dropped with a success-shaped result.Change — reuse the repo's existing primitives rather than invent new ones:
withRetrywrapper (src/worker/health.ts) with a smallBALANCE_SYNC_RETRY_POLICY.withRetryclassifies viaclassifyJobFailureand re-attempts only demonstrably-transient failures, so a 4xx fails fast instead of burning attempts; indeterminate and permanent failures throw straight to the accounting.syncAccountBalancesno longer swallows — it lets failures propagate to the wrapper/caller, and keeps the legitimate "account not found" case as a non-failure early-return (preserving existing semantics).failedand emits a structuredpinoerror withcategory,accountId,type, and asafeFailureMessage-scrubbedreason(no secrets/signed XDR in logs).retryPolicy/retrySleep/onRetryseams (the same patternwithRetryalready exposes for cycle tasks) so the behaviour is testable offline with deterministic timing.Tests —
tests/worker/balance-sync.test.ts(8 cases,src/db+stellar+pinomocked, the realwithRetryexercised):synced,onRetryfired,errornever logged;failed,loadAccountcalled exactlymaxAttempts(bounded, not infinite), structurederrorwithcategory: "transient";loadAccountcalled once (the "don't waste retries" guarantee);{0,0}, no network call;{1,1};runBalanceSyncwires production defaults.Non-vacuity (the tests actually catch the bug)
Reverting only
src/worker/tasks/balance-sync.tstomainmakes 5 of the 8 tests fail with exactly the dropped-task symptom — e.g.expected { synced: 1, failed: 0 } to deeply equal { synced: 0, failed: 1 }. The fix flips them green.Checklist
npm run lintpasses (0 errors, 33 warnings — identical to base063571b)npm run buildpasses (tsc -p tsconfig.json, exit 0)npm testpasses (offline — DB/network mocked)pinodead-letter logmergepay-web— N/A, no HTTP contract touchedsafeFailureMessage(scrubs bearer/token/xdr)Gates — base
063571bvs063571b+ this branchMeasured locally (shell Node 26; CI pins 20.x). The change is isolated:
balance-sync.tsis imported by nothing else insrc, and its only test is this new file, so the integration suite is unaffected (no integration test references it).Related issues
Closes #645
(On the worker cycle side, #645 was already satisfied by #708/#783 — see my evidence comment on #645; this PR closes the remaining gap in the
balance-synctask processor, which #708/#783 did not cover.)