test(testutil): wait for a login through PgBouncer before returning its URL - #112
Conversation
…ts URL StartPostgresBehindPgBouncer waited only for the pooler's port to be listening. The host side of a published port accepts a client before the container's process does, so a client that arrived in that window was cut off on its first read, which surfaced as a connection reset in the tests that use the fixture. The fixture now polls until a login through the pooler runs a statement on the server behind it — the property the returned URL promises — and reports the last login error if that never happens. Amp-Thread-ID: https://ampcode.com/threads/T-01a07fb2-9632-732b-bd7b-2f143a81bc06
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
🤖 Adversarial review 1/2 — does the readiness wait hold in the failure it guards against? The change is right in kind: a bound port is not readiness, and the property the returned URL promises is a login that reaches the server behind the pooler. Two things about this wait do not survive contact with the state it exists for, and both are cheap. What I ran.
The survivors are expected — no test fails on a race that did not happen on that run — but they are why the two constants and the probe body are held in place by their comments alone, and both findings below are about exactly those. 1. The deadline is shorter than PgBouncer's own login timeout, so in the failure this guards against the probe makes one attempt and reports no errorI reproduced the state the wait exists for: the pinned image, published, with PgBouncer's own log says what it did with that client: It parks a client whose server is not yet available and closes it after
Calling The case where a CI reader most needs the reason is the one that produces none, and the 30 s buys a single attempt rather than three hundred. The fix is a per-attempt bound, not a bigger deadline: a 2. The cause both new comments name is not the one left open here
What is left open is PgBouncer's own state, and its log names it: bound and accepting is not the same as having a usable server pool, so it holds the client and closes it with 3.
|
|
🤖 Review 2/2 — two lenses: OSS adoption, and integration ease for importers. Lens 1: OSS adoptionThis fixture carries a claim no peer tool in the survey can make, which is why its flakiness costs more than a test. The adoption path this protects is the default one, not an edge case. A hosted PostgreSQL platform hands an operator a pooled connection string by default, so the first thing an evaluator on such a platform does is point pg-sprite at it. What they meet is a refusal — Pinning the pooler image is the right instinct and the comment says why — "the pooling behaviour under test is the whole point of the fixture, so the version is not left to a floating tag." That discipline is what makes the 30 s / 60 s ordering in finding 1 of 1/2 worth fixing rather than tuning: One small thing while in Lens 2: integration ease for schemabot and other orchestratorsNothing an importer compiles against changes here — and that is the observation, not the absence of one. The fixture lives in That is the shape of thing worth exporting once it settles. Not in this PR, and not as the whole fixture: a small The error class is what matters to an importer, and the flake was crossing it. An orchestrator branches on the typed refusal — One forward-looking note. Approving. The direction is right, the fixture is the right place for it, and both findings in 1/2 are follow-ups: the per-attempt bound is the one I would take before merge, since without it the readiness failure is silent in exactly the state it was written for. This review was generated by Claude Code (claude-opus-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Approving. The readiness wait is the right property to gate on, go build/go vet/gofmt are clean, and both pooled dbconn tests pass on this head. Findings are in the two review comments above — the one I would take before merge is the per-attempt bound, since the 30 s deadline sits under PgBouncer's 60 s client_login_timeout and the wait therefore reports nothing in exactly the state it was written for.
This stamp was left by Claude Code (claude-opus-5).
…the cause PgBouncer holds a client it cannot yet hand a server for its whole client_login_timeout, which is longer than the readiness deadline, so a probe with no bound of its own spent the deadline on one held attempt and left no error for the deadline to report. Each attempt now carries its own timeout, so the poll interval applies and the last login error is what a reader sees. The comments attributed the window to the published port's host side; the listening-port wait already checks the bind inside the container, and what remains open is the pooler's own readiness to serve a login. The close error of a probe connection is collected with the attempt's other assertions rather than logged from the condition goroutine. docs/testing.md states the readiness the fixture now proves. Amp-Thread-ID: https://ampcode.com/threads/T-01a07fb2-9632-732b-bd7b-2f143a81bc06
|
🤖 Adversarial review response — created by Kiran's code review agent (Amp, Claude) — block/pg-sprite pull/112, follow-up commit All three findings in 1/2 and the
Source: block/pg-sprite#112, review comments 5652184395 and 5652184618 and review 5190209963 at head |
Why
The PgBouncer fixture waited only for the pooler's published port to be listening. Docker's host-side port forwarder accepts a client before PgBouncer inside the container does, so a test client that arrived in that window was accepted and then cut off on its first read — surfacing in CI as
read: connection reset by peerin thepkg/dbconnsession-state tests, intermittently across PostgreSQL versions.What
StartPostgresBehindPgBouncernow polls until a login through the pooler runsSELECT 1on the server behind it — the property the returned URL promises — with a named deadline and poll interval, and fails with the last login error if that never happens. No test timeouts were changed.Verification
scripts/test-flaky.sh TestNewPoolRefusesAConnectionThatDiscardsSessionTimeouts 20 ./pkg/dbconn/— 20/20 passgo test -race -run TestNewPool ./pkg/dbconn/— both pool-mode tests passmake lint— 0 issues