Describe the bug
CopyFrom panics if during its execution the connection is terminated.
Stack trace
panic: close of closed channel
goroutine 1 [running]:
github.com/jackc/pgx/v5/pgconn.(*PgConn).CopyFrom(0x14000470008, {0x10105af18, 0x1015c9a80}, {0x1010525e0, 0x14000434a80}, {0x14000450400, 0xff})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go:1373 +0x820
github.com/jackc/pgx/v5.(*copyFrom).run(0x140004349c0, {0x10105af18?, 0x1015c9a80?})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/copy_from.go:202 +0x43c
github.com/jackc/pgx/v5.(*Conn).CopyFrom(0x1400042e900, {0x10105af18, 0x1015c9a80}, {0x140004156d0, 0x1, 0x1}, {0x14000421ba0, 0xd, 0xd}, {0x101059918, ...})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/copy_from.go:275 +0x108
github.com/jackc/pgx/v5.(*dbTx).CopyFrom(0x1010568e0?, {0x10105af18?, 0x1015c9a80?}, {0x140004156d0?, 0x1?, 0x0?}, {0x14000421ba0?, 0x0?, 0x0?}, {0x101059918?, ...})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/tx.go:266 +0x60
...
Cause
Misaligned implementation of CopyFrom against receiveMessage.
Detailed explanation and error flow
file: pgconn/pgconn.go
CopyFrom initiates copying of data into the stream, enters bufferingReceive mode, and waits in a loop for <-signalMessageChan.
psql: SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE ...; is called to terminate the connection.
pgConn.frontend.Receive() receives the message from the server and sets:
bufferingReceiveMsg = FATAL: terminating connection due to administrator command (SQLSTATE 57P01)
bufferingReceiveErr = nil
- then closes
signalMessageChan.
CopyFrom goroutine selects the <-signalMessageChan case and:
- checks that the receive was successful (
bufferingReceiveErr is nil);
- proceeds to execute:
msg, _ = receiveMessage().
receiveMessage:
- executes
peekMessage() that, in bufferingReceive mode, just returns bufferingReceiveMsg and bufferingReceiveErr;
- handles the message as
pgproto3.ErrorResponse:
- checks
pgConn.config.OnPgHandler is set and calls the handler, which returns false as the error is FATAL;
- closes the connection and the cleanup channel;
- returns
nil, err.
CopyFrom:
msg, _ = receiveMessage()
msg is nil, and the error is discarded [bug];
- tries to handle
msg, but it is nil, so the switch goes default, which reinitializes bufferingReceive mode and loops to wait for a message on the select.
pgConn.frontend.Receive() this time returns an error:
bufferingReceiveErr = <error>
CopyFrom resumes again due to <-signalMessageChan>, but this time:
bufferingReceiveErr is set, so it:
- closes the connection (again);
- closes the cleanup channel (again ⇒ panic).
To Reproduce
Steps to reproduce the behavior:
- run
pg_terminate_backend(pid) while CopyFrom is running
Version
- PostgreSQL:
PostgreSQL 14.18 (Ubuntu 14.18-0ubuntu0.22.04.1)
- pgx:
v5.7.5
Additional context
Describe the bug
CopyFrom panics if during its execution the connection is terminated.
Stack trace
panic: close of closed channel
goroutine 1 [running]:
github.com/jackc/pgx/v5/pgconn.(*PgConn).CopyFrom(0x14000470008, {0x10105af18, 0x1015c9a80}, {0x1010525e0, 0x14000434a80}, {0x14000450400, 0xff})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go:1373 +0x820
github.com/jackc/pgx/v5.(*copyFrom).run(0x140004349c0, {0x10105af18?, 0x1015c9a80?})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/copy_from.go:202 +0x43c
github.com/jackc/pgx/v5.(*Conn).CopyFrom(0x1400042e900, {0x10105af18, 0x1015c9a80}, {0x140004156d0, 0x1, 0x1}, {0x14000421ba0, 0xd, 0xd}, {0x101059918, ...})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/copy_from.go:275 +0x108
github.com/jackc/pgx/v5.(*dbTx).CopyFrom(0x1010568e0?, {0x10105af18?, 0x1015c9a80?}, {0x140004156d0?, 0x1?, 0x0?}, {0x14000421ba0?, 0x0?, 0x0?}, {0x101059918?, ...})
/Users/me/repos/myapp/vendor/github.com/jackc/pgx/v5/tx.go:266 +0x60
...
Cause
Misaligned implementation of
CopyFromagainstreceiveMessage.Detailed explanation and error flow
file:
pgconn/pgconn.goCopyFrominitiates copying of data into the stream, entersbufferingReceivemode, and waits in a loop for<-signalMessageChan.psql:SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE ...;is called to terminate the connection.pgConn.frontend.Receive()receives the message from the server and sets:bufferingReceiveMsg = FATAL: terminating connection due to administrator command (SQLSTATE 57P01)bufferingReceiveErr = nilsignalMessageChan.CopyFromgoroutine selects the<-signalMessageChancase and:bufferingReceiveErrisnil);msg, _ = receiveMessage().receiveMessage:peekMessage()that, inbufferingReceivemode, just returnsbufferingReceiveMsgandbufferingReceiveErr;pgproto3.ErrorResponse:pgConn.config.OnPgHandleris set and calls the handler, which returnsfalseas the error is FATAL;nil, err.CopyFrom:msg, _ = receiveMessage()msgisnil, and the error is discarded [bug];msg, but it isnil, so theswitchgoesdefault, which reinitializesbufferingReceivemode and loops to wait for a message on theselect.pgConn.frontend.Receive()this time returns an error:bufferingReceiveErr = <error>CopyFromresumes again due to<-signalMessageChan>, but this time:bufferingReceiveErris set, so it:To Reproduce
Steps to reproduce the behavior:
pg_terminate_backend(pid)whileCopyFromis runningVersion
PostgreSQL 14.18 (Ubuntu 14.18-0ubuntu0.22.04.1)v5.7.5Additional context