feat: add the cross-machine wake for Postgres — LISTEN/NOTIFY#23
Merged
Conversation
Completes the wake tiers: OutboxWaker (in-process, 0.4.0), WakeSocket pair
(same machine, 0.4.0), and now LISTEN/NOTIFY through the database for workers
on other machines.
Producer side: new PostgresOutboxStore({ wakeChannel }) piggybacks pg_notify
on the enqueue transaction — Postgres delivers it ON COMMIT and drops it on
rollback, so the wake is atomic with the event becoming visible and needs no
post-commit discipline. Worker side: PostgresWakeListener holds a dedicated
(non-pooled) LISTEN connection feeding the loop's OutboxWaker, reconnecting
with a fixed delay on drops. Best-effort by the same contract as every tier:
missed notifications are not recovered — polling remains the delivery
backstop.
Hardening from an adversarial review pass:
- channels: identifier allow-list + the 63-byte Postgres limit (beyond it
LISTEN silently truncates while pg_notify RAISES — aborting the caller's
BUSINESS transaction on every enqueue) + double-quoting so LISTEN stays
case-aligned with pg_notify's exact-string channel
- the pg end-during-connect deadlock: a client ended mid-connect never
settles connect(); the session parks on the connection's end/error events
FIRST and races connect against them, so stop() cannot hang
- a throwing custom WakeSignal routes to onError instead of crashing the
worker from inside pg's socket-data handler
- reconnectDelayMs validated (0/negative/NaN would hot-loop a PG outage)
- documented factories set keepAlive: true — a LISTEN socket is pure-receive,
so without TCP keepalives a half-open death is never detected
Tests: 20 hermetic listener specs over scriptable fake connections + pglite
pg_notify observation + a gated real-Postgres round-trip proving
delivered-on-commit / dropped-on-rollback. 100% coverage; complexity <=15.
Docs: README wake tiers, api-reference (incl. the 0.4.0 waker gap), CHANGELOG.
📊
|
| Metric | PR | Base | Diff | |
|---|---|---|---|---|
| Statements | ████████████████████ |
2070/2070 (100%) |
1841/1841 (100%) |
⚪ 0% |
| Branches | ████████████████████ |
279/279 (100%) |
236/236 (100%) |
⚪ 0% |
| Functions | ████████████████████ |
88/88 (100%) |
78/78 (100%) |
⚪ 0% |
| Lines | ████████████████████ |
2070/2070 (100%) |
1841/1841 (100%) |
⚪ 0% |
🧾 Changed files
| File | Statements | Branches | Diff | |
|---|---|---|---|---|
messaging/dialects/postgres/outbox-store.ts |
████████████████████ |
137/137 (100%) |
15/15 (100%) |
⚪ 0% |
messaging/dialects/postgres/wake.ts |
████████████████████ |
205/205 (100%) |
40/40 (100%) |
🆕 new |
Updated for d5701d2 | Compared against base branch
⏱️ Performance Report
🐢 Slowest test suites
🐌 Slowest individual tests
Updated for |
🧠 Cognitive Complexity Report
🧩 Most complex functions
🧾 Changed files
Updated for |
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
The third and final wake tier: workers on other machines get the wake through the one thing they already share — the database. Postgres
LISTEN/NOTIFY, Postgres-only by nature (SQLite/MySQL keep theWakeSockettier; for SQLite that's the whole story anyway).The commit-atomicity is the elegant part: because
pg_notifyruns inside the caller's transaction, the wake needs no post-commit discipline — Postgres holds it until commit and drops it on rollback, so the signal is atomic with the event becoming visible. Best-effort contract as always: notifications missed during a reconnect gap are not recovered; polling stays the delivery backstop.Hardening (from a 2-agent adversarial review before this PR)
The review traced two serious issues that are fixed here, with regression tests:
client.end()during an in-flightconnect()means the connect promise never settles (pg skips its connect callback once_endingis set) — an un-racedawait connect()would hangstop()forever, blocking graceful shutdown until SIGKILL. The session now parks on the connection'send/errorevents first and racesconnect()against them.NAMEDATALEN - 1,LISTENsilently truncates butpg_notifyraises — and since it rides the enqueue transaction, a 64+-char "valid-looking" channel would abort the caller's business transaction on every enqueue.assertValidWakeChannelnow enforces the cap (verified live against Postgres 16 by the reviewer).WakeSignalroutes toonErrorinstead of crashing the worker from inside pg's socket-data handler;reconnectDelayMsis validated (0/NaN would hot-loop a PG outage); documented factories setkeepAlive: trueso a half-open LISTEN socket (NAT/firewall idle eviction) is detected by the OS instead of parking forever.The review also refuted the NOTIFY-flood DoS concern:
OutboxWakerlatches, so N notifications collapse to one follow-up tick — no amplification beyond what the (already-authenticated) DB user could do directly.Tests / gates
pg_notifyfromenqueue.wake.tsandoutbox-store.ts; complexity ≤15; core typecheck clean.website/docs/api-reference.md(also closes the 0.4.0 gap —WorkerLoopOptions.wakerand the wake tiers were undocumented on the site), CHANGELOG.Note:
Release and security checksis expected red on the pre-existing website (docusaurus) advisories — unrelated; the published-tarball audit is green.No version bump; next release (0.5.0) ships this.