fix(storage): bound commit settlement — recover lost native completions and retry-backoff timers (#1785) - #1830
fix(storage): bound commit settlement — recover lost native completions and retry-backoff timers (#1785)#1830kriszyp wants to merge 3 commits into
Conversation
…ns and retry-backoff timers (#1785) Under CPU throttling (cgroup CFS quotas in CPU-limited containers), two async-delivery links in the commit path were observed being silently severed: the native commit's completion callback (commit durable, JS promise never settles) and the ERR_BUSY/ ERR_TRY_AGAIN retry-backoff timer. Either loss orphans the caller's commit promise forever — no error, ingest wedged indefinitely (14+ hours reported). Enforce settlement externally rather than chasing the unidentified primitive: - watchCommitSettlement wraps every native commit/abort resolution; a lazy unref'd sweeper settles entries past 2× STORAGE_MAXTRANSACTIONQUEUETIME: lost aborts resolve, a feature-detected rocksdb-js terminal-outcome getter replays committed/RETRY_NOW exactly (Phase 2, rides 2.5.x), otherwise reject with an explicit outcome-unknown 503. sourceApply entries are never rejected (never-drop invariant) — rate-limited error logs, keep waiting. - robustBackoff replaces the bare backoff delay: the sweeper fires overdue timers with a once-guard; each recovery logs an error (production telemetry for the loss rate). - Recovered entries null their native-txn/write-batch refs so the lost promise's reaction closures cannot retain them forever. - Adjacent fix: the coordinated RETRY_NOW retry cap now aborts the native transaction before throwing, mirroring the ERR_BUSY cap path (handle leak). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
…ode:assert/strict in tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Back to draft: re-examination of the capture evidence shows the 'lost native commit completion' failure mode was a capture artifact (that commit's completion arrived late — >90s under 50× CPU throttle — and did resolve; the snapshot just cut it off). The confirmed failure mode is the severed retry-backoff continuation only. Reworking the PR to match the corrected evidence before review. — Claude (Fable), for Kris |
…ff only Evidence re-examination showed the 'lost native commit completion' was a capture artifact (the completion arrived late — >90s under 50x CPU throttle — and did resolve), so the settle-watch wrapper is removed: its deadline reject would misreport legitimately-slow commits whose outcome is merely unknown. The confirmed severed link is the retry-backoff continuation (81ms setTimeout dead 21+ min on a live process whose other timers and retry ladders stayed healthy). robustBackoff remains, its recovery sweep now piggybacking on the existing long-transaction monitor interval (startMonitoringTxns) instead of a second timer — transactions leave trackedTxns before the commit chain runs, so the existing sweep cannot see a stuck commit, but its interval can host the backoff recovery. DESIGN.md documents the corrected evidence, the retraction, and the remaining primitive suspects (AsyncLocalStorage promise-hook path; throttle.ts for request-level hangs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Closing after re-auditing the root-cause evidence. The remaining — KrAIs (OpenAI Codex) |
Fixes the silent-hang manifestation of #1785 (ingest permanently hangs under sustained sequential bulk upsert).
Summary
Root-cause investigation (instrumented native binding + gdb on live frozen processes, reproduced under cgroup CPU throttling per @ldt1996's repro) found the reported hang is a severed retry-backoff continuation: under CPU throttle, the ERR_BUSY/ERR_TRY_AGAIN retry chain's one-shot
setTimeout(...).then(recommit)can be scheduled and never run — observed definitively (an 81ms backoff dead for 21+ minutes on a live process whose other timers kept firing and whose other retry chains completed their full 40-retry ladders and threw correctly). The orphaned commit promise then hangs forever with no error. When every link fires, the existing retry path already errors correctly at MAX_RETRIES.robustBackoffreplaces the bare backoffdelay(...).then(recommit): each backoff registers in a small Set; the existing long-transaction monitor interval (startMonitoringTxns) fires any entry overdue past a 2s grace window, with aSet.deleteonce-guard so a late real timer no-ops. No second watchdog timer — a transaction has already lefttrackedTxnswhen its commit chain runs (so the existing sweep can't see a stuck commit), but its interval can host this recovery. Worst-case recovery latency ≈ one monitor tick (30s default) vs. infinite hang today.Evidence correction (why the earlier settle-watch was removed)
An earlier revision also wrapped every native commit/abort resolution with a settlement deadline ("lost native completion" recovery). Re-examination showed that observed instance was a capture artifact — the completion arrived late (>90s under 50× throttle) and did resolve — and a deadline-reject would misreport legitimately-slow commits whose outcome is merely unknown. Removed; DESIGN.md documents the retraction and the remaining primitive suspects (AsyncLocalStorage promise-hook machinery around the timer boundary;
server/throttle.tsas a separate candidate for request-level hangs). The severed primitive itself is still unidentified — this PR bounds the damage and instruments the incidence while that hunt continues (targeted ALS isolation repro + rocksdb-js#694 A/B soak in progress).Where to look
robustBackoff(resolve(resume())adopts the recursivecommit()promise; sync throws reject).startMonitoringTxnsinterval — cheap (Array.fromof a normally-empty Set), but it now executes every monitor tick.STORAGE_MAXTRANSACTIONOPENTIME, 30s default) — judged fine vs. a permanent hang; flag if you want a faster dedicated cadence instead.No docs PR: no user-facing API/config change (new exports are internal test hooks following the
setTxnExpirationprecedent).Generated by an LLM (Claude Fable 5) working with Kris; full investigation trail on #1785.
🤖 Generated with Claude Code