jackc / jackc/pgx

`BeforeConnect` receives `context.Background()` (instead of the pool context) from the healthcheck path

Open
#2,545 5 comments 2 reactions 0 assignees View on GitHub
bug
Dominant language
Go
Stars
14.3k
Forks
1.1k
Avg merge
6d 9h
Merged PRs (30d)
11

Description

**Describe the bug**
Long story short, we use `BeforeConnect` to implement IAM Authentication with AWS / Aurora, and I traced back some panics to a missing logger in the context passed to BeforeConnect. I would expect the hook to receive a useful context (for logging, tracing, fetching credentials, ...), but while that happens for `Pool.Acquire`, the background goroutine refilling the pool to satisfy the minimum size calls it with a bare `context.Background()`.

**To Reproduce**
Steps to reproduce the behavior:

```go
package main

import (
"context"
"fmt"
"time"

"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)

type key int

const myKey key = 0

func main() {
ctx := context.WithValue(context.Background(), myKey, "value")

poolConfig, _ := pgxpool.ParseConfig("postgres://user:password@127.0.0.1:1/mydb")

// Fail fast
poolConfig.ConnConfig.ConnectTimeout = 200 * time.Millisecond

// Prepare connections in the background
poolConfig.MinConns = 1
poolConfig.HealthCheckPeriod = 300 * time.Millisecond

var calls int
poolConfig.BeforeConnect = func(bcCtx context.Context, cc *pgx.ConnConfig) error {
calls++
if v := bcCtx.Value(myKey); v == nil {
fmt.Printf("call %d: BeforeConnect ctx MISSING value\n", calls)
} else {
fmt.Printf("call %d: BeforeConnect ctx has value=%q\n", calls, v)
}
return nil
}

pool, err := pgxpool.NewWithConfig(ctx, poolConfig)
if err != nil {
panic(err)
}
defer pool.Close()

// Let the background healthcheck fire a few times.
time.Sleep(2 * time.Second)
}
```

**Expected behavior**
```
call 1: ctx has value="value"
call 2: ctx has value="value"
call 3: ctx has value="value"
```

**Actual behavior**
```
call 1: ctx has value="value"
call 2: ctx MISSING value
call 3: ctx MISSING value
```

Call 1 comes from the initial idle creation goroutine in `NewWithConfig`. The subsequent ones come from `backgroundHealthCheck` + `checkMinConns`.

**Impact**
Anyone relying on `context.Value` propagation in `BeforeConnect` (e.g. injecting a request-scoped logger or AWS credential cache) sees inconsistent errors: the hook works on `Acquire`, it works on initial pool warm-up, but it randomly breaks on background refills. In our case it caused a nil-pointer panic while trying to log a message about the authentication.

Either re-use the context passed to `NewWithConfig`, expose a new `Config.HealthCheckContext`, or at the very least document the behavior to avoid unpleasant surprises. I can have a go at the first option if there's agreement.

**Version**
- Go: go1.26.2 darwin/arm64
- pgx: v5.9.1
- puddle: v2.2.2

Contributor guide

Open the contributing guide

Research direction

Start at pgxpool.NewWithConfig and trace the initial idle creation path alongside backgroundHealthCheck and checkMinConns. Compare the contexts reaching BeforeConnect from initial creation, Acquire, and healthcheck refills. Done means the intended context behavior is defined and the reproduction is covered by tests for repeated healthcheck-driven connections.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, go, postgresql
Domain
database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.