Skip to content

Let cancel_query! cancel a statement after an interrupt closed the socket - #45

Merged
quinnj-bot[bot] merged 3 commits into
mainfrom
claude/cancel-after-interrupt
Nov 5, 2026
Merged

quinnj-bot[bot] merged 3 commits into
mainfrom
claude/cancel-after-interrupt

Conversation

@quinnj-bot

@quinnj-bot quinnj-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Why

An InterruptException that lands while a statement runs closes the socket, because the stream position is unknown, but the statement keeps running on the server. cancel_query! then refused the connection with "cannot cancel query: connection not open", so the driver had no way to stop that statement (#44). The CancelRequest goes over its own connection and needs only the backend pid and cancel key, which the connection keeps, the same way a libpq PGcancel outlives its PGconn.

What changed

  • cancel_query! no longer checks the socket, and drops the trylock block that existed only for that check. It sends the request with the pid and key the connection holds.
  • A fake-server test interrupts a statement, then checks that the cancel arrives with the pid and key from BackendKeyData. A Docker test interrupts pg_sleep on a real server and checks that the statement stops.
  • The docstring and the manual say that the cancel works after an interrupt.

Fixes #44

Scope

Only the refusal changes. The interrupt path stays as it is: the socket closes and the connection cannot be reused, which matches MySQL.jl, where a broken session keeps its connection id so the user can run KILL QUERY. Sending the cancel automatically when an interrupt lands, as psql does, is a separate decision.

Blast radius

cancel_query! on a connection closed with close(conn) now sends a request that the server ignores, because that backend has exited, instead of throwing. No other code read the removed check. With #42 (Terminate on close) the two changes touch different functions and merge cleanly in either order.

Verification

  • The regression test before the fix: cancel after an interrupt closed the socket | 2 pass, 1 error, the error being cannot cancel query: connection not open thrown from cancel_query!. After the fix: 43 of 43 in that file, three runs.
  • Against a local PostgreSQL 14: an interrupted SELECT pg_sleep(30) left the backend active, cancel_query! on the closed connection sent the request, and the server logged canceling statement due to user request and connection to client lost.
  • Review pass over the diff: no other caller of the removed check. The unlocked pid and skey reads match the path a busy connection already took, and a reconnect by another task replaces both fields under its lock, so a stale pair only yields a request the server ignores. cancel_request still throws when it cannot deliver the request or would downgrade TLS or GSS.
  • Full suite with Docker integration (PostgreSQL 16, Julia 1.12.6, macOS arm64): 9303 of 9303 pass, the new Docker case included.

🤖 Generated with Claude Code

quinnj and others added 2 commits November 4, 2026 22:05
The fake server never answers the statement, so the interrupt lands in
the read loop and the driver closes the socket. The cancel must still
reach the server with the pid and key the connection holds. The Docker
case checks that the interrupted statement stops on a real server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cancel_query! refused a connection whose socket was closed, so a
statement interrupted mid-query kept running on the server with no way
to stop it from the driver. The CancelRequest goes over its own
connection and needs only the backend pid and cancel key, which the
connection keeps after the socket closes, as a libpq PGcancel does. Drop
the socket check and the trylock that only guarded it.

Fixes #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The statement an interrupt left running stops when it finishes or is
cancelled. With `reconnect=true`, using the connection again opens a
new session and replaces the stored pid and cancel key, so a cancel
must come first to reach the abandoned statement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@quinnj-bot
quinnj-bot Bot merged commit 2d3424b into main Nov 5, 2026
16 checks passed
@quinnj-bot quinnj-bot Bot mentioned this pull request Nov 5, 2026
quinnj referenced this pull request Nov 5, 2026
Co-authored-by: Jacob Quinn <quinn.jacobd@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

cancel_query! refuses a connection whose socket was closed by an interrupt, while the statement still runs

1 participant