Skip to content

CopyFrom panics if during its execution the connection is terminated #2364

Description

@mdraus47

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

  1. CopyFrom initiates copying of data into the stream, enters bufferingReceive mode, and waits in a loop for <-signalMessageChan.
  2. psql: SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE ...; is called to terminate the connection.
  3. 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.
  4. CopyFrom goroutine selects the <-signalMessageChan case and:
    • checks that the receive was successful (bufferingReceiveErr is nil);
    • proceeds to execute: msg, _ = receiveMessage().
  5. 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.
  6. 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.
  7. pgConn.frontend.Receive() this time returns an error:
    • bufferingReceiveErr = <error>
  8. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions