Skip to content

Commit 42b1480

Browse files
committed
libpq: Complain about missing BackendKeyData later with PGgetCancel()
PostgreSQL always sends the BackendKeyData message at connection startup, but there are some third party backend implementations out there that don't support cancellation, and don't send the message [1]. While the protocol docs left it up for interpretation if that is valid behavior, libpq in PostgreSQL 17 and below accepted it. It does not seem like the libpq behavior was intentional though, since it did so by sending CancelRequest messages with all zeros to such servers (instead of returning an error or making the cancel a no-op). In version 18 the behavior was changed to return an error when trying to create the cancel object with PGgetCancel() or PGcancelCreate(). This was done without any discussion, as part of supporting different lengths of cancel packets for the new 3.2 version of the protocol. This commit changes the behavior of PGgetCancel() / PGcancel() once more to only return an error when the cancel object is actually used to send a cancellation, instead of when merely creating the object. The reason to do so is that some clients [2] create a cancel object as part of their connection creation logic (thus having the cancel object ready for later when they need it), so if creating the cancel object returns an error, the whole connection attempt fails. By delaying the error, such clients will still be able to connect to the third party backend implementations in question, but when actually trying to cancel a query, the user will be notified that that is not possible for the server that they are connected to. This commit only changes the behavior of the older PGgetCancel() / PQcancel() functions, not the more modern PQcancelCreate() family of functions. I.e. PQcancelCreate() returns a failed connection object (CONNECTION_BAD) if the server didn't send a cancellation key. Unlike the old PQgetCancel() function, we're not aware of any clients in the field that use PQcancelCreate() during connection startup in a way that would prevent connecting to such servers. [1] AWS RDS Proxy is definitely one of them, and CockroachDB might be another. [2] psycopg2 (but not psycopg3). Author: Jelte Fennema-Nio <postgres@jeltef.nl> Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com> Backpatch-through: 18 Discussion: https://www.postgresql.org/message-id/20250617.101056.1437027795118961504.ishii%40postgresql.org
1 parent d9f01a2 commit 42b1480

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,11 @@
537537
The frontend should not respond to this message, but should
538538
continue listening for a ReadyForQuery message.
539539
</para>
540+
<para>
541+
The <productname>PostgreSQL</productname> server will always send this
542+
message, but some third party backend implementations of the protocol
543+
that don't support query cancellation are known not to.
544+
</para>
540545
</listitem>
541546
</varlistentry>
542547

src/interfaces/libpq/fe-cancel.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,24 @@ PQgetCancel(PGconn *conn)
379379

380380
/* Check that we have received a cancellation key */
381381
if (conn->be_cancel_key_len == 0)
382-
return NULL;
382+
{
383+
/*
384+
* In case there is no cancel key, return an all-zero PGcancel object.
385+
* Actually calling PQcancel on this will fail, but we allow creating
386+
* the PGcancel object anyway. Arguably it would be better return NULL
387+
* to indicate that cancellation is not possible, but there'd be no
388+
* way for the caller to distinguish "out of memory" from "server did
389+
* not send a cancel key". Also, this is how PGgetCancel() has always
390+
* behaved, and if we changed it, some clients would stop working
391+
* altogether with servers that don't support cancellation. (The
392+
* modern PQcancelCreate() function returns a failed connection object
393+
* instead.)
394+
*
395+
* The returned dummy object has cancel_pkt_len == 0; we check for
396+
* that in PQcancel() to identify it as a dummy.
397+
*/
398+
return calloc(1, sizeof(PGcancel));
399+
}
383400

384401
cancel_req_len = offsetof(CancelRequestPacket, cancelAuthCode) + conn->be_cancel_key_len;
385402
cancel = malloc(offsetof(PGcancel, cancel_req) + cancel_req_len);
@@ -544,6 +561,15 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
544561
return false;
545562
}
546563

564+
if (cancel->cancel_pkt_len == 0)
565+
{
566+
/* This is a dummy PGcancel object, see PQgetCancel */
567+
strlcpy(errbuf, "PQcancel() -- no cancellation key received", errbufsize);
568+
/* strlcpy probably doesn't change errno, but be paranoid */
569+
SOCK_ERRNO_SET(save_errno);
570+
return false;
571+
}
572+
547573
/*
548574
* We need to open a temporary connection to the postmaster. Do this with
549575
* only kernel calls.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy