Skip to content

Recover the connection when a second Ctrl-C interrupts the query cancel - #1651

Open
DiegoDAF wants to merge 2 commits into
dbcli:mainfrom
DiegoDAF:upstream/ctrl-c-recovery
Open

DiegoDAF wants to merge 2 commits into
dbcli:mainfrom
DiegoDAF:upstream/ctrl-c-recovery

Conversation

@DiegoDAF

@DiegoDAF DiegoDAF commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Description

Pressing Ctrl-C twice on a long query can leave the connection unusable: every later statement, rollback included, fails with

sending query failed: another command is already in progress

and the query keeps running on the server. We hit it over an SSH tunnel to a remote server, but a tunnel is not required, just enough round-trip time.

psycopg's Connection.wait() answers the first Ctrl-C by sending a cancel and then reading the server's reply. A second Ctrl-C during that cleanup interrupts it: the cancel may never go out and the reply is never read. execute_command() then prints "cancelled query" without looking at the connection.

After a cancelled query, pgcli now checks whether the connection is still busy. If it is, it cancels again and reads the pending reply, using psycopg.pq only and waiting at most 5 seconds. If the server does not answer, or yet another Ctrl-C arrives, it cancels (at most 2 seconds, ignoring Ctrl-C meanwhile so the query does not outlive the session) and closes the connection, so the next statement goes through the existing reconnect path. A connection that is not busy is left alone, so session state survives an ordinary Ctrl-C.

Reproduced end to end by driving pgcli with pexpect through a TCP proxy that adds 150 ms each way: two Ctrl-C 0.15 s or 0.4 s apart left the connection busy and the pg_sleep running on the server. With this change the next statement works and the sleep is cancelled, also with three, four or six quick Ctrl-C.

5 tests against a live server, all failing on main; two of them also fail if the last-resort path only closes the connection, or lets a Ctrl-C skip its cancel.

The cleanup gap itself is in psycopg, reported as psycopg/psycopg#1444. A fix there would leave the connection closed instead of busy; this change is still needed to cancel the query and keep the session when the connection can be recovered.

Checklist

  • I have added this contribution to the changelog.rst.
  • I have added my name to the AUTHORS file (or it is already there).
  • I installed pre-commit hooks (pip install pre-commit && pre-commit install).
  • Please squash merge this pull request (uncheck if you would like us to merge as multiple commits)

On the third box: .pre-commit-config.yaml pins an older ruff-pre-commit than the one tox -e style installs, and the two disagree on formatting, so I ran ruff check and ruff format matching the CI instead, both clean.

psycopg answers Ctrl-C by cancelling the query and then reading the
server's reply. A second Ctrl-C during that cleanup, easy over an SSH
tunnel where it takes a round trip or two, cut it short: the cancel
could be lost, the query kept running on the server and every later
statement failed with "another command is already in progress".

After a cancelled query pgcli now checks the connection. If it is
still busy it cancels again and reads the reply (psycopg.pq only, at
most 5s). If the server does not answer, or yet another Ctrl-C lands,
it cancels (at most 2s) and closes the connection, and the next
statement reconnects through the usual closed-connection path.

Four tests, all failing without the change; one also fails if the
last-resort path only closes the connection.
When the user interrupts the recovery itself, pgcli cancels the query
and closes the connection. A further Ctrl-C during that cancel skipped
it, so with four quick Ctrl-C over a tunnel the query kept running on
the server after the session gave up on it.

Ctrl-C is now ignored for the at most 2 seconds the cancel takes (main
thread only; elsewhere signal handlers cannot change). The new test
fails without the change.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant