Files

pgproxy prepared target sessions

2026-09-22: protocol-authenticated idle standby

Reason and behavior

Previously target protocol connections were created only after a downstream client needed them. Handler.PrepareSession now eagerly opens and authenticates one real single-node target session without a downstream client. It clears PreparedTarget on the setup snapshot to prevent recursive borrowing. The parent gateway pool owns publishing, periodic heartbeats, replacement, rejection backoff and shutdown.

PostgreSQL uses pgconn.Ping (a comment-only query, no transaction or business write). The initial database is AuthSource, or postgres when omitted. A different client database override never checks out the default standby. Existing runtime parameters and TLS initialization remain in open.

Checkout checks current target credential validity before accessing the pool. A prepared value is checked against target identity/account, database/authSource, outbound route, TLS, compression and session options. The request context is used for a fresh heartbeat before handing the session to protocol serving code. A failed heartbeat closes the standby and falls back to the existing new-connection path; a canceled request returns its context error. No business command is replayed. Checked-out connections are never returned to this standby pool: transactions, cursors and subscriptions cannot be shared with another client.

Preparation is bounded by the configured connect timeout (15 seconds by default). Heartbeats have a five-second upper bound and respect earlier context cancellation. Their cancellation callback is stopped, or joined if already running, before returning ownership. Canceling a completed setup or heartbeat context cannot close a successfully handed-off session.

Setup failures preserve only typed authentication, permission and host-identity rejection categories using outbound.FailureKind. Unknown errors use a generic message. Original server-controlled error text is not retained in the returned error chain, and there is no string-based password-error guessing. The parent pool can stop reconnecting explicit rejections instead of repeatedly attempting login.

Scope and compatibility

Only this protocol directory is changed by this adapter task. No schema migration, production data change, new explicit transaction or business write is introduced. Disabled persistence and clustered targets continue through the original open path. Existing protocol serving/audit behavior remains in place. Shared pool wiring and project-wide change-rationale documentation belong to the parent integration task.

Actual validation

Go 1.25.0, GOFLAGS=-p=2, GOTMPDIR=artifacts/portable-validation/build-work and its cache were used for:

  • go test ./internal/protocol/mysqlproxy ./internal/protocol/pgproxy ./internal/protocol/mongoproxy ./internal/protocol/redisproxy -count=1 -timeout=120s — passed.
  • go vet ./internal/protocol/mysqlproxy ./internal/protocol/pgproxy ./internal/protocol/mongoproxy ./internal/protocol/redisproxy — passed.
  • Prepared fixture coverage includes eager authentication, Ping, same-connection checkout with a second heartbeat, failed-heartbeat replacement, cancellation, blocked setup/heartbeat deadlines, credential validation, identity/database mismatches, explicit typed authentication rejection and credential-safe errors.
  • MySQL/PostgreSQL also verify database overrides leave the default standby available; MongoDB verifies pre-auth hello isolation and custom AuthSource on authentication, idle Ping and checkout Ping; Redis checks buffering/deadlines.
  • The attempted targeted race run was unavailable because this environment has CGO disabled (go: -race requires cgo). No race-pass claim is made.

These are local protocol fixtures, not live database or production-server tests. No frontend build, embedded asset build, deployment or commit was performed here.