Skip to content

fix(db): bound reader-pool statements with per-session statement_timeout (#3651) - #3997

Open
iroiro147 wants to merge 3 commits into
block:mainfrom
iroiro147:claude/issue3651-20260731
Open

fix(db): bound reader-pool statements with per-session statement_timeout (#3651)#3997
iroiro147 wants to merge 3 commits into
block:mainfrom
iroiro147:claude/issue3651-20260731

Conversation

@iroiro147

Copy link
Copy Markdown

Summary

Bounds every statement issued on the reader pool (crates/buzz-db) with a per-session statement_timeout, so a replica that goes dark mid-transaction fails the statement instead of hanging the request indefinitely (#3651).

Root cause

Once a routed read acquired its reader connection, nothing bounded any subsequent statement. A replica path that goes dark mid-transaction (LB failover, security-group change, standby promotion, Aurora failover) converted a routed read into an indefinitely hung request the writer-fallback could never reclaim. No statement_timeout existed anywhere in the repo; a MIDTX_STATEMENT probe ran past a 15s cap with no bound in sight.

Fix

connect_read_pool now arms a per-session statement_timeout (READER_STATEMENT_TIMEOUT_MS = 2s) on every reader connection via after_connect + set_config('statement_timeout', ..., false) — the same set_config pattern already used by the writer pool's floor-guard at the same site. The value is deliberately session-scoped (the trailing false) so it cannot leak into any wider role config.

2s is comfortably above any legitimate routed read on a read-only session yet far below a hung request; on timeout Postgres sends an error, the acquire / read path fails, and the caller's fallback to the writer regains control. Collector/timer overhead and the (already sub-second) reader acquire are unaffected — this bounds only in-flight SQL work.

Reader sessions are read-only: this can never abort a committed write, it only bounds in-flight SQL.

Test

Wiring test: connect_read_pool_arms_statement_timeout pins the bound value and proves lazy construction still works with after_connect armed (connect_lazy dials nothing). buzz-db lib suite: 95 pass / 0 fail (151 ignored — pre-existing Postgres-dependent skip-set).

Environment note

No live replica available here (test-loader Postgres is absent in this worktree). The new wiring test passes standalone cargo test -p buzz-db --lib; this is the same pattern as the existing connect_read_pool_is_lazy_and_independently_sized test, which also never dials the network.

…rget (block#3929)

The test asserted on a shared static callsite target
("trace_context_lookup_filter_test"). tracing caches callsite interest
globally per static callsite rather than per-subscriber, so when the
parallel lib suite ran, another test could poison that callsite's cached
interest while a different subscriber was active — bypassing the
thread-local with_default filter and intermittently failing the
assertion that the LevelFilter::OFF filter disables the callsite.

Use a target literal unique to the test statement (suffixed with the
source line via concat! since enabled! requires a compile-time literal)
so no other test's callsite can poison this assertion's cached interest.
Removes the shared state instead of serializing around it (block#3929, fix
direction 2).

Signed-off-by: iroiro147 <sarthak.singh@juspay.in>
…ys settles (block#3975)

Desktop's WebSocket reconnect could appear as a no-op because
tauri-plugin-websocket holds a global connection-manager mutex while
awaiting send(); a stuck send from a previous dead connection starved
the next plugin:websocket|connect registration indefinitely. With an
unsettled connectPromise, every manual Reconnect click joined the same
stuck future, matching issue block#3975's report of "Reconnect does nothing,
only relaunch recovers".

Race the plugin connect invoke against a hard 30s timeout in a new
pure helper . On timeout it rejects and
the existing connection-failure/backoff path runs, so each manual
reconnect starts a fresh attempt instead of joining the stuck one.
The helper is injectable so the timeout path can be unit-tested without
the Tauri runtime.

Test: relayConnectTimeout.test.mjs — 3 cases: rejects with the timeout
when the invoke never settles; resolves with the plugin wsId when it
responds; clears its timer so a fast resolve never fires the fallback.

Note: the desktop npm-test suite is broken at baseline in this worktree
(ERR_MODULE_NOT_FOUND: typescript in test-loader.mjs) — same on clean
main. This change passes standalone node --test.

Signed-off-by: iroiro147 <sarthak.singh@juspay.in>
…out (block#3651)

Once a routed read acquired its reader connection, nothing bounded any
subsequent statement. A replica path that goes dark mid-transaction (LB
failover, security-group change, standby promotion, Aurora failover)
converted a routed read into an indefinitely hung request the
writer-fallback could never reclaim. No statement_timeout existed
anywhere in the repo; a MIDTX_STATEMENT probe ran past a 15s cap with
no bound in sight.

Arm a per-session statement_timeout (READER_STATEMENT_TIMEOUT_MS = 2s)
on every reader connection in connect_read_pool via after_connect +
set_config, matching the writer pool's floor-guard pattern at the same
site. Reader sessions are read-only, so this can never abort a committed
write; it only bounds in-flight SQL work and lets the caller's fallback
to the writer regain control on a blackholed replica path.

Wiring test: connect_read_pool_arms_statement_timeout pins the bound
value and proves lazy construction still works with after_connect armed
(connect_lazy dials nothing). buzz-db lib suite: 95 pass / 0 fail.

Signed-off-by: iroiro147 <sarthak.singh@juspay.in>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant