Skip to content

fix(#4961): PostgresqlListenWakeup falls back to a timeout wait when the DB is unreachable#4965

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/4961-listen-notify-highwater-resilience
Jul 17, 2026
Merged

fix(#4961): PostgresqlListenWakeup falls back to a timeout wait when the DB is unreachable#4965
jeremydmiller merged 1 commit into
masterfrom
fix/4961-listen-notify-highwater-resilience

Conversation

@jeremydmiller

@jeremydmiller jeremydmiller commented Jul 17, 2026

Copy link
Copy Markdown
Member

Closes #4961 (Marten-side mitigation — ships independently of JasperFx).

The bug

When UseListenNotifyForEventAppends = true, a brief DB outage (restart / failover / maintenance) could permanently freeze all projection progress until the process was restarted: PostgresqlListenWakeup.WaitAsyncensureListeningAsyncOpenConnectionAsync throws when the DB is unreachable, and the HighWaterAgent poll loop awaits the wakeup outside any try/catch, so that throw ended the high-water loop.

This PR

WaitAsync now guards ensureListeningAsync: OperationCanceledException still propagates (shutdown); any other exception is logged at Warning and falls back to Task.Delay(timeout). receiveLoop already nulls _connection, so the next call retries the LISTEN and self-heals once the DB returns. Defect B is the only place that throws a connection error into the unguarded loop, so this resolves the reported symptom on its own.

Deterministic repro wait_async_falls_back_to_timeout_when_the_database_is_unreachable (unreachable port) asserts WaitAsync doesn't throw and returns promptly. All 4 tests in the file pass (net10.0).

Follow-up (separate task)

The upstream defense-in-depth (#524, defects A + C — HighWaterAgent guards every IDaemonWakeup.WaitAsync and the watchdog observes the real loop task) ships in JasperFx 2.28.0. The 2.27.0 → 2.28.0 bump is deferred to its own PR because it currently regresses Marten's binary-COPY rebuild (rebuild_with_bulk_copy_inserts23505 duplicate key) — that integration is being handled alongside the rebuild-optimization work.

🤖 Generated with Claude Code

…the database is unreachable

WaitAsync now guards ensureListeningAsync: OperationCanceledException still propagates
(shutdown), but any other exception — a dropped/unreachable database during a restart,
failover, or maintenance window — is logged at Warning and falls back to a plain
Task.Delay(timeout) instead of escaping. The HighWaterAgent poll loop awaits the wakeup
outside any try/catch, so an unguarded throw here permanently froze all projection progress
until the process was restarted (marten#4961). receiveLoop already nulls _connection, so the
next call retries the LISTEN and self-heals once the database returns.

This is the Marten-side mitigation and ships independently of JasperFx (per the #4961 hand-off):
defect B is the only place that throws a connection error into the unguarded high-water loop.
The upstream defense-in-depth (#524, defects A + C, in JasperFx 2.28.0) is a separate
follow-up — the 2.27.0 -> 2.28.0 bump is deferred because it currently regresses Marten's
binary-COPY rebuild (rebuild_with_bulk_copy_inserts, 23505 duplicate key), which is being
handled as its own task alongside the rebuild-optimization work.

Adds a deterministic repro: wait_async_falls_back_to_timeout_when_the_database_is_unreachable
points the wakeup at an unreachable port and asserts WaitAsync does not throw and returns
promptly. All 4 tests in the file pass (net10.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKKBPsFJ83jd2o4hyGJnj6
@jeremydmiller jeremydmiller changed the title fix(#4961): LISTEN/NOTIFY high-water resilience + JasperFx 2.28.0 (defense-in-depth for jasperfx#524) fix(#4961): PostgresqlListenWakeup falls back to a timeout wait when the DB is unreachable Jul 17, 2026
@jeremydmiller
jeremydmiller force-pushed the fix/4961-listen-notify-highwater-resilience branch from 9fc08c2 to aa93f2a Compare July 17, 2026 18:28
@jeremydmiller
jeremydmiller merged commit 7cf8035 into master Jul 17, 2026
9 checks passed
@jeremydmiller
jeremydmiller deleted the fix/4961-listen-notify-highwater-resilience branch July 17, 2026 18:44
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