Skip to content

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

@zepatrik

Description

@zepatrik

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

pgx/pgconn/pgconn.go

Lines 1533 to 1539 in 82b212c

msg, err := mrr.pgConn.receiveMessage()
if err != nil {
mrr.pgConn.contextWatcher.Unwatch()
mrr.err = normalizeTimeoutError(mrr.ctx, err)
mrr.closed = true
mrr.pgConn.asyncClose()
return nil, mrr.err

But the outer pgx.dbTx state is not set to "closed".

To Reproduce

Steps to reproduce the behavior:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions