Skip to content

pgxpool: fix data race on concurrent conn.Release#2564

Open
guerinoni wants to merge 1 commit into
jackc:masterfrom
guerinoni:concurrent-release-fix
Open

pgxpool: fix data race on concurrent conn.Release#2564
guerinoni wants to merge 1 commit into
jackc:masterfrom
guerinoni:concurrent-release-fix

Conversation

@guerinoni
Copy link
Copy Markdown
Contributor

@guerinoni guerinoni commented May 23, 2026

When the same Conn was released from two goroutines at once, both
calls passed the guard, grabbed the same puddle resource, and released it
twice.

Fixes #2260.

I tested with a piece of code that reproduced the bug:

docker run --name some-postgres -e POSTGRES_PASSWORD=postgres -d postgres

export PGX_TEST_DATABASE="host=localhost port=5432 user=postgres password=postgres dbname=postgres"

go run -race .
func main() {
	dsn := os.Getenv("PGX_TEST_DATABASE")
	fmt.Println(dsn)

	cfg, err := pgxpool.ParseConfig(dsn)
	if err != nil {
		panic(err)
	}
	cfg.MaxConns = 8
	cfg.MinConns = 2
	cfg.HealthCheckPeriod = 10 * time.Millisecond // hammer AcquireAllIdle

	pool, err := pgxpool.NewWithConfig(context.Background(), cfg)
	if err != nil {
		panic(err)
	}
	defer pool.Close()

	ctx := context.Background()
	deadline := time.Now().Add(60 * time.Second)
	var wg sync.WaitGroup

	for range 16 {
		wg.Go(func() {
			for time.Now().Before(deadline) {
				c, err := pool.Acquire(ctx)
				if err != nil {
					continue
				}
				_, _ = c.Exec(ctx, "select 1")

				// double release of the SAME *pgxpool.Conn, concurrently.
				var w sync.WaitGroup
				w.Add(2)
				go func() { defer w.Done(); c.Release() }()
				go func() { defer w.Done(); c.Release() }()
				w.Wait()
			}
		})
	}

	wg.Wait()
	fmt.Println("no panic -> ok")
}

When the same Conn was released from two goroutines at once, both
calls passed the guard, grabbed the same puddle resource, and released it
twice.

Fixes jackc#2260.
@jackc
Copy link
Copy Markdown
Owner

jackc commented May 30, 2026

A pgxpool.Conn is not concurrency safe. Multiple goroutines releasing the same pgxpool.Conn is a bug in the caller.

@guerinoni
Copy link
Copy Markdown
Contributor Author

A pgxpool.Conn is not concurrency safe. Multiple goroutines releasing the same pgxpool.Conn is a bug in the caller.

Agreed, but if we can make it no-op we can 'save' some situation with ~zero extra effort :)

@jackc
Copy link
Copy Markdown
Owner

jackc commented May 31, 2026

A pgxpool.Conn is not concurrency safe. Multiple goroutines releasing the same pgxpool.Conn is a bug in the caller.

Agreed, but if we can make it no-op we can 'save' some situation with ~zero extra effort :)

TBH, I think that is counter productive. I want to make application logic errors noisy, not silence them. It seems very unlikely that a pgxpool.Conn could somehow be released from two different goroutines without something very wrong in the calling application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic in pgxpool healthcheck

2 participants