I was reading pgconn's connection setup code (just learning the codebase, no agenda) and got stuck on the fallback branch in connectPreferred. The function walks through every host the user passed with target_session_attrs=prefer-standby (or prefer-primary), and if none of them match the preference, it falls back to whichever host connects at all.
The fallback loop reused a ctx variable from the preferred-pass loop above it. The preferred pass had been overwriting ctx with a per-hostConnectTimeout-bounded context each iteration. So by the time control fell through to the fallback, ctx held the deadline of whichever host the loop tried last, and that deadline had usually already burned.
If you set ConnectTimeout=3s and handed it three preferred-standby hosts, the whole nine-second wall budget would tick away during the preferred pass, and the fallback would inherit a dead context. You'd see context deadline exceeded even though octx, the caller's original context, still had plenty of time.
Fix is one extra context.WithTimeout(octx, c.ConnectTimeout) so the fallback gets its own fresh budget, same pattern the loop uses per host:
ctx, cancel = context.WithTimeout(octx, c.ConnectTimeout)
defer cancel()
pgConn, err = connectOne(ctx, c, fc, true)Five-line diff. Tagged Fixes #2172. Reviewed and merged by jackc.
The thing that stuck with me is how easy it is to leak a context variable in Go without realizing. The loop variable problem (for _, x := range xs capturing the wrong x in a closure) gets a ton of attention. Loop-body assignments to an outer-scope context, much less.