@@ -644,6 +644,36 @@ func TestTxSendBatchClosed(t *testing.T) {
644644 require .Error (t , err )
645645}
646646
647+ // When a query is cancelled mid-flight the underlying connection is closed, but
648+ // the Tx handle has not been finalized. The first Rollback then fails to send
649+ // ROLLBACK over the dead connection. That error must be matchable as
650+ // pgconn.ErrConnClosed (transport gone) rather than ErrTxClosed (handle already
651+ // finalized), so callers can tell the two apart. A second Rollback returns
652+ // ErrTxClosed. See https://github.com/jackc/pgx/issues/2557.
653+ func TestTxRollbackOnClosedConnReturnsErrConnClosed (t * testing.T ) {
654+ t .Parallel ()
655+
656+ conn := mustConnectString (t , os .Getenv ("PGX_TEST_DATABASE" ))
657+ defer closeConn (t , conn )
658+
659+ tx , err := conn .Begin (context .Background ())
660+ require .NoError (t , err )
661+
662+ ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
663+ defer cancel ()
664+
665+ _ , err = tx .Exec (ctx , "select pg_sleep(5)" )
666+ require .Error (t , err )
667+ require .True (t , conn .IsClosed ())
668+
669+ err = tx .Rollback (context .Background ())
670+ require .ErrorIs (t , err , pgconn .ErrConnClosed )
671+ require .NotErrorIs (t , err , pgx .ErrTxClosed )
672+
673+ err = tx .Rollback (context .Background ())
674+ require .ErrorIs (t , err , pgx .ErrTxClosed )
675+ }
676+
647677func TestBeginTxNonFatalErrorKeepsConnAlive (t * testing.T ) {
648678 t .Parallel ()
649679
0 commit comments