Skip to content

TOC/TOU bug on transaction rollback #2551

@zepatrik

Description

@zepatrik

Describe the bug

I have observed flaky behavior in a test that causes a transaction to be rolled back by canceling the context. I think it is caused by a toctou issue here:

pgx/tx.go

Lines 210 to 215 in c00cd65

if tx.closed {
return ErrTxClosed
}
_, err := tx.conn.Exec(ctx, "rollback")
tx.closed = true

To Reproduce

It is a hard to reproduce issue without lots of setup, this is what is happening roughly:

func TestRollbackOnContextCanceled(t *testing.T) {
	dsn := dockertest.RunTestPostgreSQL(t) // or simply load from the env

	db, err := sql.Open("pgx/v5", dsn)
	require.NoError(t, err)
	t.Cleanup(func() { _ = db.Close() })
	require.EventuallyWithT(t, func(collect *assert.CollectT) {
		assert.NoError(collect, db.PingContext(t.Context()))
	}, 10*time.Second, 100*time.Millisecond)

	ctx, cancel := context.WithCancel(t.Context())
	t.Cleanup(cancel)

	tx, err := db.BeginTx(ctx, nil)
	require.NoError(t, err)

	_, err = tx.ExecContext(ctx, "SELECT 1")
	require.NoError(t, err)

	cancel()
	time.Sleep(1 * time.Nanosecond) // give the database/sql auto-rollback some time to kick in

	// this races now with the database/sql auto-rollback on context cancellation
	// https://github.com/golang/go/blob/07840ceeed4afd10324a552e8c87a8ee363aa24a/src/database/sql/sql.go#L2211-L2226
	// depending on the outcome, we either get sql.ErrTxDone or "conn closed"
	require.NoError(t, tx.Rollback())
}

Expected behavior

The close bool flag should be managed atomically, similar to how it is done in the database/sql package: https://github.com/golang/go/blob/07840ceeed4afd10324a552e8c87a8ee363aa24a/src/database/sql/sql.go#L2331

Actual behavior

Flaky behavior.

Version

  • Go: $ go version -> go version go1.26.2 darwin/arm64
  • PostgreSQL: $ psql --no-psqlrc --tuples-only -c 'select version()' -> PostgreSQL 16.13 (Debian 16.13-1.pgdg13+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
  • pgx: $ grep 'github.com/jackc/pgx/v[0-9]' go.mod -> v5.9.2

Additional context

...

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions