jackc / jackc/pgx

Transaction rollback returns `conn closed` instead of `ErrTxDone` when the pgconn is closed

Open
#2,557 3 comments 0 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**

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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.