Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions pgxpool/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1060,11 +1060,34 @@ func TestConnReleaseWhenBeginFail(t *testing.T) {
tx, err := db.BeginTx(ctx, pgx.TxOptions{
IsoLevel: pgx.TxIsoLevel("foo"),
})
assert.Error(t, err)
if !assert.Zero(t, tx) {
err := tx.Rollback(ctx)
assert.NoError(t, err)
}
require.Error(t, err)
require.Zero(t, tx)

require.EqualValues(t, 1, db.Stat().TotalConns())

var n int
require.NoError(t, db.QueryRow(ctx, "select 1").Scan(&n))
require.EqualValues(t, 1, n)
}

func TestConnDestroyedWhenBeginFailsFatally(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()

controllerConn, err := pgx.Connect(ctx, os.Getenv("PGX_TEST_DATABASE"))
require.NoError(t, err)
defer controllerConn.Close(ctx)
pgxtest.SkipCockroachDB(t, controllerConn, "Server does not support pg_terminate_backend() (https://github.com/cockroachdb/cockroach/issues/35897)")

db, err := pgxpool.New(ctx, os.Getenv("PGX_TEST_DATABASE"))
require.NoError(t, err)
defer db.Close()

tx, err := db.BeginTx(ctx, pgx.TxOptions{BeginQuery: "select pg_terminate_backend(pg_backend_pid())"})
require.Error(t, err)
require.Zero(t, tx)

for range 1000 {
if db.Stat().TotalConns() == 0 {
Expand All @@ -1073,7 +1096,7 @@ func TestConnReleaseWhenBeginFail(t *testing.T) {
time.Sleep(time.Millisecond)
}

assert.EqualValues(t, 0, db.Stat().TotalConns())
require.EqualValues(t, 0, db.Stat().TotalConns())
}

func TestTxBeginFuncNestedTransactionCommit(t *testing.T) {
Expand Down
17 changes: 14 additions & 3 deletions tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,12 @@ func (c *Conn) Begin(ctx context.Context) (Tx, error) {
func (c *Conn) BeginTx(ctx context.Context, txOptions TxOptions) (Tx, error) {
_, err := c.Exec(ctx, txOptions.beginSQL())
if err != nil {
// begin should never fail unless there is an underlying connection issue or
// a context timeout. In either case, the connection is possibly broken.
c.die()
// begin kills the connection upon receiving fatal, panic or non PGError errors,
// but does not otherwise as the connection should be reusable.
var pgErr *pgconn.PgError
if !errors.As(err, &pgErr) || isConnectionFatal(pgErr) {
c.die()
}
return nil, err
}

Expand All @@ -112,6 +115,14 @@ func (c *Conn) BeginTx(ctx context.Context, txOptions TxOptions) (Tx, error) {
}, nil
}

func isConnectionFatal(pgErr *pgconn.PgError) bool {
severity := pgErr.SeverityUnlocalized
if severity == "" {
severity = pgErr.Severity
}
return severity == "FATAL" || severity == "PANIC"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably unrelated to this PR, but I wish these magic strings were constants that could be traced through the source.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I don't mind doing a follow up for that, but should take part in a different PR.

}

// Tx represents a database transaction.
//
// Tx is an interface instead of a struct to enable connection pools to be implemented without relying on internal pgx
Expand Down
44 changes: 44 additions & 0 deletions tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,3 +643,47 @@ func TestTxSendBatchClosed(t *testing.T) {
_, err = br.Query()
require.Error(t, err)
}

func TestBeginTxNonFatalErrorKeepsConnAlive(t *testing.T) {
t.Parallel()

conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn)

pgxtest.SkipCockroachDB(t, conn, "Server returns a different severity/error for an invalid BEGIN")

ctx := context.Background()

_, err := conn.BeginTx(ctx, pgx.TxOptions{BeginQuery: "begin transaction isolation level nonsense"})
require.Error(t, err)

var pgErr *pgconn.PgError
require.True(t, errors.As(err, &pgErr))
require.NotEqual(t, "FATAL", pgErr.SeverityUnlocalized)

require.False(t, conn.IsClosed())

var n int
require.NoError(t, conn.QueryRow(ctx, "select 1").Scan(&n))
require.Equal(t, 1, n)
}

func TestBeginTxFatalErrorKillsConn(t *testing.T) {
t.Parallel()

conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
defer closeConn(t, conn)

pgxtest.SkipCockroachDB(t, conn, "Server does not support pg_terminate_backend()")

ctx := context.Background()

_, err := conn.BeginTx(ctx, pgx.TxOptions{BeginQuery: "select pg_terminate_backend(pg_backend_pid())"})
require.Error(t, err)

var pgErr *pgconn.PgError
require.True(t, errors.As(err, &pgErr))
require.Equal(t, "FATAL", pgErr.SeverityUnlocalized)

require.True(t, conn.IsClosed())
}