Skip to content

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

@MartinodF

Description

@MartinodF

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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions