Skip to content

Never hold transactions or contended locks across external I/O - #20

Merged
ewhauser merged 1 commit into
mainfrom
lock-network-discipline
Aug 3, 2026
Merged

Never hold transactions or contended locks across external I/O#20
ewhauser merged 1 commit into
mainfrom
lock-network-discipline

Conversation

@ewhauser

@ewhauser ewhauser commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Fixes #18, fixes #19 — and codifies the maintainer's rule as constraint C-C6 in SYNC_ENGINE.md, with the per-entity observation session as the sole documented exception and the advisory-lock ordering rule written down beside the key definitions.

  • Repository observation lock spans GitHub network work and causes batch timeouts #18: repository observation lock narrowed to a post-hydration, apply-only window; deterministic regressions (per-instance fake response gates) prove a slow batch can't starve concurrent ones past statement_timeout, with C-C2 resolving out-of-order repo metadata. Two-lock ordering enumerated across coordinator, discovery, membership resolution, drift, and sweeps.
  • Failed advisory unlock can return a still-locked session to pgxpool #19: unlock now checks pg_advisory_unlock's result; release-to-pool only after confirmed unlock; failed/false/ambiguous cleanup hijacks and closes the physical connection (backend termination frees the lock server-side — poll-verified); bounded WithoutCancel cleanup window so routine cancellation doesn't churn connections; swallowed defer errors surfaced via bounded slog warnings.
  • C-C6 audit: every transaction and held lock in the codebase audited (table in SYNC_ENGINE.md); one extra violation found and fixed — example-api streamed SSE while holding its snapshot transaction; now bounded materialize → commit → write with explicit overflow resync semantics.

Built and adversarially reviewed by paired Codex sol-xhigh agents (7 findings fixed, one critical). Full gate green locally: build, vet, DB-backed suite, -race -count=3 on changed packages, lint, gen, tidy.

🤖 Generated with Claude Code

Fixes two v0.3.0 production bugs and codifies the underlying rule as
constraint C-C6 in SYNC_ENGINE.md: database transactions and shared
locks must not overlap HTTP/GraphQL/REST calls or client network
writes; external fetches finish first and results are materialized
before client writes. The sole documented exception is C-C1's
dedicated per-entity observation session (never an open transaction),
now re-justified where the locks are defined, with the advisory-key
acquisition-ordering rule documented beside the key definitions.

Issue #18: the coordinator held the shared repository observation lock
across the entire GitHub batch -- fetch, hydration, and all writes --
so concurrent batches for one repository serialized on
pg_advisory_lock and died on statement_timeout, with retries
amplifying contention. The repository lock is now a post-hydration,
apply-only window around ApplyRepositoryObserved. Deterministic
regressions with per-instance fake response gates prove a delayed
batch cannot starve a concurrent one past statement_timeout, and C-C2
resolves older-response-applies-last interleavings. Lock ordering
across every two-lock site was enumerated and is consistent.

Issue #19: CloseContext released the observation's dedicated
connection back to pgxpool even when the session-lock release failed,
so a later borrower could inherit a locked session. Cleanup now runs
under a bounded context.WithoutCancel window, checks
pg_advisory_unlock's boolean result (false means the lock was not
held -- also a destroy-and-log signal), releases to the pool only
after confirmed unlock, and otherwise hijacks and closes the physical
connection so backend termination frees the lock server-side --
verified by polling pg_stat_activity and pg_locks. Defer-swallowed
cleanup failures now surface as bounded slog warnings.

The C-C6 audit covered every transaction and held lock in the
codebase; one additional violation was found and fixed: the example
API streamed SSE snapshot rows while holding its repeatable-read
transaction. Snapshots now preflight count/size bounds (100k entities
/ 64MiB, at most two concurrent materializations), commit, then
write; overflow emits an explicit resync advisory instead of a partial
snapshot. Also fixed en route: a graceful-shutdown race in the
example API's tailer startup, and a streamclient test that measured
backoff with a fixed sleep instead of polling the actual lock wait
(production behavior unchanged).

Fixes #18
Fixes #19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ewhauser
ewhauser merged commit 7c4383e into main Aug 3, 2026
5 checks passed
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.

Failed advisory unlock can return a still-locked session to pgxpool Repository observation lock spans GitHub network work and causes batch timeouts

1 participant