Transaction rollback returns `conn closed` instead of `ErrTxDone` when the pgconn is closed
- Dominant language
- Go
- Stars
- 14.3k
- Forks
- 1.1k
- Avg merge
- 6d 9h
- Merged PRs (30d)
- 11
Description
**Describe the bug**
I spend quite some time further debugging #2551, and think that I now found the actual issue I was running into.
When the context is canceled while a transaction query is executed, the low-level `pgconn.PgConn` is closed due to an `i/o timeout` at https://github.com/jackc/pgx/blob/82b212cb99c5e77bb54568a5ca7b9fa2b772dc0a/pgconn/pgconn.go#L1533-L1539
But the outer `pgx.dbTx` state is not set to "closed".
**To Reproduce**
Steps to reproduce the behavior:
```go
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/jackc/pgx/v5"
)
func main() {
dsn := os.Getenv("TEST_DATABASE_POSTGRESQL")
conn, err := pgx.Connect(context.Background(), dsn)
if err != nil {
fmt.Printf("unexpected error: %v\n", err)
os.Exit(1)
}
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
tx, err := conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
fmt.Printf("unexpected error: %v\n", err)
os.Exit(1)
}
// Sleep for longer than the context timeout to trigger a cancellation
_, _ = tx.Exec(ctx, "SELECT pg_sleep(1)")
err = tx.Rollback(context.Background())
fmt.Printf("error on first rollback: %v\n", err)
err = tx.Rollback(context.Background())
fmt.Printf("error on second rollback: %v\n", err)
}
// outputs:
// error on first rollback: conn closed
// error on second rollback: tx is closed
```
**Expected behavior**
The transaction state should match the underlying connection state, or rather the error returned from `tx.Rollback` should be `pgx.ErrTxClosed` when the connection is closed.
It generally seems weird to me that the low-level connection would close itself without notifying the higher-level code that created the connection.
**Actual behavior**
An unexpected, non-assertable, low-level error is returned.
**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**
Potentially related: #1145 #2100
Contributor guide
Research direction
Start with the pgconn closure path in pgconn/pgconn.go around lines 1533-1539, then trace the transaction rollback entry point used by the reproduction. Reproduce the timeout against PostgreSQL and verify that rollback after the underlying connection closes returns pgx.ErrTxClosed rather than the low-level connection error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, postgresql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100