aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/protocol.sgml29
-rw-r--r--src/backend/storage/ipc/procsignal.c23
-rw-r--r--src/backend/tcop/backend_startup.c55
-rw-r--r--src/backend/tcop/postgres.c15
-rw-r--r--src/backend/utils/init/globals.c5
-rw-r--r--src/backend/utils/init/postinit.c2
-rw-r--r--src/include/libpq/pqcomm.h8
-rw-r--r--src/include/miscadmin.h4
-rw-r--r--src/include/storage/procsignal.h14
-rw-r--r--src/interfaces/libpq/fe-cancel.c102
-rw-r--r--src/interfaces/libpq/fe-connect.c15
-rw-r--r--src/interfaces/libpq/fe-protocol3.c45
-rw-r--r--src/interfaces/libpq/libpq-int.h7
-rw-r--r--src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl12
14 files changed, 252 insertions, 84 deletions
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 628da4cd7cc..4dab0cb4923 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -4062,7 +4062,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</varlistentry>
<varlistentry>
- <term>Int32(12)</term>
+ <term>Int32</term>
<listitem>
<para>
Length of message contents in bytes, including self.
@@ -4080,14 +4080,29 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</varlistentry>
<varlistentry>
- <term>Int32</term>
+ <term>Byte<replaceable>n</replaceable></term>
<listitem>
<para>
- The secret key of this backend.
+ The secret key of this backend. This field extends to the end of the
+ message, indicated by the length field.
+ </para>
+ <para>
+ The maximum key length is 256 bytes. The
+ <productname>PostgreSQL</productname> server only sends keys up to
+ 32 bytes, but the larger maximum size allows for future server
+ versions, as well as connection poolers and other middleware, to use
+ longer keys. One possible use case is augmenting the server's key
+ with extra information. Middleware is therefore also encouraged to
+ not use up all of the bytes, in case multiple middleware
+ applications are layered on top of each other, each of which may
+ wrap the key with extra data.
</para>
</listitem>
</varlistentry>
</variablelist>
+ <para>
+ Before protocol version 3.2, the secret key was always 4 bytes long.
+ </para>
</listitem>
</varlistentry>
@@ -4293,14 +4308,18 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</varlistentry>
<varlistentry>
- <term>Int32</term>
+ <term>Byte<replaceable>n</replaceable></term>
<listitem>
<para>
- The secret key for the target backend.
+ The secret key for the target backend. This field extends to the end of the
+ message, indicated by the length field. The maximum key length is 256 bytes.
</para>
</listitem>
</varlistentry>
</variablelist>
+ <para>
+ Before protocol version 3.2, the secret key was always 4 bytes long.
+ </para>
</listitem>
</varlistentry>
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7d201965503..b7c39a4c5f0 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -63,8 +63,8 @@
typedef struct
{
pg_atomic_uint32 pss_pid;
- bool pss_cancel_key_valid;
- int32 pss_cancel_key;
+ int pss_cancel_key_len; /* 0 means no cancellation is possible */
+ char pss_cancel_key[MAX_CANCEL_KEY_LENGTH];
volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
slock_t pss_mutex; /* protects the above fields */
@@ -148,8 +148,7 @@ ProcSignalShmemInit(void)
SpinLockInit(&slot->pss_mutex);
pg_atomic_init_u32(&slot->pss_pid, 0);
- slot->pss_cancel_key_valid = false;
- slot->pss_cancel_key = 0;
+ slot->pss_cancel_key_len = 0;
MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
@@ -163,12 +162,13 @@ ProcSignalShmemInit(void)
* Register the current process in the ProcSignal array
*/
void
-ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
+ProcSignalInit(char *cancel_key, int cancel_key_len)
{
ProcSignalSlot *slot;
uint64 barrier_generation;
uint32 old_pss_pid;
+ Assert(cancel_key_len >= 0 && cancel_key_len <= MAX_CANCEL_KEY_LENGTH);
if (MyProcNumber < 0)
elog(ERROR, "MyProcNumber not set");
if (MyProcNumber >= NumProcSignalSlots)
@@ -199,8 +199,9 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation);
- slot->pss_cancel_key_valid = cancel_key_valid;
- slot->pss_cancel_key = cancel_key;
+ if (cancel_key_len > 0)
+ memcpy(slot->pss_cancel_key, cancel_key, cancel_key_len);
+ slot->pss_cancel_key_len = cancel_key_len;
pg_atomic_write_u32(&slot->pss_pid, MyProcPid);
SpinLockRelease(&slot->pss_mutex);
@@ -254,8 +255,7 @@ CleanupProcSignalState(int status, Datum arg)
/* Mark the slot as unused */
pg_atomic_write_u32(&slot->pss_pid, 0);
- slot->pss_cancel_key_valid = false;
- slot->pss_cancel_key = 0;
+ slot->pss_cancel_key_len = 0;
/*
* Make this slot look like it's absorbed all possible barriers, so that
@@ -725,7 +725,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
* fields in the ProcSignal slots.
*/
void
-SendCancelRequest(int backendPID, int32 cancelAuthCode)
+SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len)
{
Assert(backendPID != 0);
@@ -754,7 +754,8 @@ SendCancelRequest(int backendPID, int32 cancelAuthCode)
}
else
{
- match = slot->pss_cancel_key_valid && slot->pss_cancel_key == cancelAuthCode;
+ match = slot->pss_cancel_key_len == cancel_key_len &&
+ timingsafe_bcmp(slot->pss_cancel_key, cancel_key, cancel_key_len) == 0;
SpinLockRelease(&slot->pss_mutex);
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index 84e1c6f2831..dde8d5b3517 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -60,6 +60,7 @@ ConnectionTiming conn_timing = {.ready_for_use = TIMESTAMP_MINUS_INFINITY};
static void BackendInitialize(ClientSocket *client_sock, CAC_state cac);
static int ProcessSSLStartup(Port *port);
static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done);
+static void ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen);
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
static void process_startup_packet_die(SIGNAL_ARGS);
static void StartupPacketTimeoutHandler(void);
@@ -565,28 +566,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
if (proto == CANCEL_REQUEST_CODE)
{
- /*
- * The client has sent a cancel request packet, not a normal
- * start-a-new-connection packet. Perform the necessary processing.
- * Nothing is sent back to the client.
- */
- CancelRequestPacket *canc;
- int backendPID;
- int32 cancelAuthCode;
-
- if (len != sizeof(CancelRequestPacket))
- {
- ereport(COMMERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("invalid length of startup packet")));
- return STATUS_ERROR;
- }
- canc = (CancelRequestPacket *) buf;
- backendPID = (int) pg_ntoh32(canc->backendPID);
- cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode);
-
- if (backendPID != 0)
- SendCancelRequest(backendPID, cancelAuthCode);
+ ProcessCancelRequestPacket(port, buf, len);
/* Not really an error, but we don't want to proceed further */
return STATUS_ERROR;
}
@@ -887,6 +867,37 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
}
/*
+ * The client has sent a cancel request packet, not a normal
+ * start-a-new-connection packet. Perform the necessary processing. Nothing
+ * is sent back to the client.
+ */
+static void
+ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen)
+{
+ CancelRequestPacket *canc;
+ int len;
+
+ if (pktlen < offsetof(CancelRequestPacket, cancelAuthCode))
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid length of query cancel packet")));
+ return;
+ }
+ len = pktlen - offsetof(CancelRequestPacket, cancelAuthCode);
+ if (len == 0 || len > 256)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid length of query cancel key")));
+ return;
+ }
+
+ canc = (CancelRequestPacket *) pkt;
+ SendCancelRequest(pg_ntoh32(canc->backendPID), canc->cancelAuthCode, len);
+}
+
+/*
* Send a NegotiateProtocolVersion to the client. This lets the client know
* that they have either requested a newer minor protocol version than we are
* able to speak, or at least one protocol option that we don't understand, or
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index aec65007bb6..89189848862 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4259,16 +4259,20 @@ PostgresMain(const char *dbname, const char *username)
* Generate a random cancel key, if this is a backend serving a
* connection. InitPostgres() will advertise it in shared memory.
*/
- Assert(!MyCancelKeyValid);
+ Assert(MyCancelKeyLength == 0);
if (whereToSendOutput == DestRemote)
{
- if (!pg_strong_random(&MyCancelKey, sizeof(int32)))
+ int len;
+
+ len = (MyProcPort == NULL || MyProcPort->proto >= PG_PROTOCOL(3, 2))
+ ? MAX_CANCEL_KEY_LENGTH : 4;
+ if (!pg_strong_random(&MyCancelKey, len))
{
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random cancel key")));
}
- MyCancelKeyValid = true;
+ MyCancelKeyLength = len;
}
/*
@@ -4323,10 +4327,11 @@ PostgresMain(const char *dbname, const char *username)
{
StringInfoData buf;
- Assert(MyCancelKeyValid);
+ Assert(MyCancelKeyLength > 0);
pq_beginmessage(&buf, PqMsg_BackendKeyData);
pq_sendint32(&buf, (int32) MyProcPid);
- pq_sendint32(&buf, (int32) MyCancelKey);
+
+ pq_sendbytes(&buf, MyCancelKey, MyCancelKeyLength);
pq_endmessage(&buf);
/* Need not flush since ReadyForQuery will do it. */
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index b844f9fdaef..2152aad97d9 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -24,6 +24,7 @@
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/procnumber.h"
+#include "storage/procsignal.h"
ProtocolVersion FrontendProtocol;
@@ -48,8 +49,8 @@ pg_time_t MyStartTime;
TimestampTz MyStartTimestamp;
struct ClientSocket *MyClientSocket;
struct Port *MyProcPort;
-bool MyCancelKeyValid = false;
-int32 MyCancelKey = 0;
+char MyCancelKey[MAX_CANCEL_KEY_LENGTH];
+uint8 MyCancelKeyLength = 0;
int MyPMChildSlot;
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 7958ea11b73..c09c4d404ba 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -753,7 +753,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
*/
SharedInvalBackendInit(false);
- ProcSignalInit(MyCancelKeyValid, MyCancelKey);
+ ProcSignalInit(MyCancelKey, MyCancelKeyLength);
/*
* Also set up timeout handlers needed for backend operation. We need
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 0aceb7147c7..d11069cf8dc 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -128,7 +128,12 @@ typedef uint32 AuthRequest;
*
* The cancel request code must not match any protocol version number
* we're ever likely to use. This random choice should do.
+ *
+ * Before PostgreSQL v18 and the protocol version bump from 3.0 to 3.2, the
+ * cancel key was always 4 bytes. With protocol version 3.2, it's variable
+ * length.
*/
+
#define CANCEL_REQUEST_CODE PG_PROTOCOL(1234,5678)
typedef struct CancelRequestPacket
@@ -136,7 +141,8 @@ typedef struct CancelRequestPacket
/* Note that each field is stored in network byte order! */
MsgType cancelRequestCode; /* code to identify a cancel request */
uint32 backendPID; /* PID of client's backend */
- uint32 cancelAuthCode; /* secret key to authorize cancel */
+ char cancelAuthCode[FLEXIBLE_ARRAY_MEMBER]; /* secret key to
+ * authorize cancel */
} CancelRequestPacket;
/* Application-Layer Protocol Negotiation is required for direct connections
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 603d0424354..0d8528b2875 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -191,8 +191,8 @@ extern PGDLLIMPORT pg_time_t MyStartTime;
extern PGDLLIMPORT TimestampTz MyStartTimestamp;
extern PGDLLIMPORT struct Port *MyProcPort;
extern PGDLLIMPORT struct Latch *MyLatch;
-extern PGDLLIMPORT bool MyCancelKeyValid;
-extern PGDLLIMPORT int32 MyCancelKey;
+extern PGDLLIMPORT char MyCancelKey[];
+extern PGDLLIMPORT uint8 MyCancelKeyLength;
extern PGDLLIMPORT int MyPMChildSlot;
extern PGDLLIMPORT char OutputFileName[];
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 022fd8ed933..016dfd9b3f6 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -57,15 +57,25 @@ typedef enum
} ProcSignalBarrierType;
/*
+ * Length of query cancel keys generated.
+ *
+ * Note that the protocol allows for longer keys, or shorter, but this is the
+ * length we actually generate. Client code, and the server code that handles
+ * incoming cancellation packets from clients, mustn't use this hardcoded
+ * length.
+ */
+#define MAX_CANCEL_KEY_LENGTH 32
+
+/*
* prototypes for functions in procsignal.c
*/
extern Size ProcSignalShmemSize(void);
extern void ProcSignalShmemInit(void);
-extern void ProcSignalInit(bool cancel_key_valid, int32 cancel_key);
+extern void ProcSignalInit(char *cancel_key, int cancel_key_len);
extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
ProcNumber procNumber);
-extern void SendCancelRequest(int backendPID, int32 cancelAuthCode);
+extern void SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len);
extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
extern void WaitForProcSignalBarrier(uint64 generation);
diff --git a/src/interfaces/libpq/fe-cancel.c b/src/interfaces/libpq/fe-cancel.c
index 7ebaa335bba..e84e64bf2a7 100644
--- a/src/interfaces/libpq/fe-cancel.c
+++ b/src/interfaces/libpq/fe-cancel.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* fe-cancel.c
- * functions related to setting up a connection to the backend
+ * functions related to query cancellation
*
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -41,7 +41,6 @@ struct pg_cancel
{
SockAddr raddr; /* Remote address */
int be_pid; /* PID of to-be-canceled backend */
- int be_key; /* cancel key of to-be-canceled backend */
int pgtcp_user_timeout; /* tcp user timeout */
int keepalives; /* use TCP keepalives? */
int keepalives_idle; /* time between TCP keepalives */
@@ -49,6 +48,10 @@ struct pg_cancel
* retransmits */
int keepalives_count; /* maximum number of TCP keepalive
* retransmits */
+
+ /* Pre-constructed cancel request packet starts here */
+ int32 cancel_pkt_len; /* in network-byte-order */
+ char cancel_req[FLEXIBLE_ARRAY_MEMBER]; /* CancelRequestPacket */
};
@@ -83,6 +86,13 @@ PQcancelCreate(PGconn *conn)
return (PGcancelConn *) cancelConn;
}
+ /* Check that we have received a cancellation key */
+ if (conn->be_cancel_key_len == 0)
+ {
+ libpq_append_conn_error(cancelConn, "no cancellation key received");
+ return (PGcancelConn *) cancelConn;
+ }
+
/*
* Indicate that this connection is used to send a cancellation
*/
@@ -101,7 +111,15 @@ PQcancelCreate(PGconn *conn)
* Copy cancellation token data from the original connection
*/
cancelConn->be_pid = conn->be_pid;
- cancelConn->be_key = conn->be_key;
+ if (conn->be_cancel_key != NULL)
+ {
+ cancelConn->be_cancel_key = malloc(conn->be_cancel_key_len);
+ if (!conn->be_cancel_key)
+ goto oom_error;
+ memcpy(cancelConn->be_cancel_key, conn->be_cancel_key, conn->be_cancel_key_len);
+ }
+ cancelConn->be_cancel_key_len = conn->be_cancel_key_len;
+ cancelConn->pversion = conn->pversion;
/*
* Cancel requests should not iterate over all possible hosts. The request
@@ -349,6 +367,8 @@ PGcancel *
PQgetCancel(PGconn *conn)
{
PGcancel *cancel;
+ int cancel_req_len;
+ CancelRequestPacket *req;
if (!conn)
return NULL;
@@ -356,13 +376,17 @@ PQgetCancel(PGconn *conn)
if (conn->sock == PGINVALID_SOCKET)
return NULL;
- cancel = malloc(sizeof(PGcancel));
+ /* Check that we have received a cancellation key */
+ if (conn->be_cancel_key_len == 0)
+ return NULL;
+
+ cancel_req_len = offsetof(CancelRequestPacket, cancelAuthCode) + conn->be_cancel_key_len;
+ cancel = malloc(offsetof(PGcancel, cancel_req) + cancel_req_len);
if (cancel == NULL)
return NULL;
memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr));
- cancel->be_pid = conn->be_pid;
- cancel->be_key = conn->be_key;
+
/* We use -1 to indicate an unset connection option */
cancel->pgtcp_user_timeout = -1;
cancel->keepalives = -1;
@@ -405,6 +429,13 @@ PQgetCancel(PGconn *conn)
goto fail;
}
+ req = (CancelRequestPacket *) &cancel->cancel_req;
+ req->cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
+ req->backendPID = pg_hton32(conn->be_pid);
+ memcpy(req->cancelAuthCode, conn->be_cancel_key, conn->be_cancel_key_len);
+ /* include the length field itself in the length */
+ cancel->cancel_pkt_len = pg_hton32(cancel_req_len + 4);
+
return cancel;
fail:
@@ -412,6 +443,42 @@ fail:
return NULL;
}
+/*
+ * PQsendCancelRequest
+ * Submit a CancelRequest message, but don't wait for it to finish
+ *
+ * Returns: 1 if successfully submitted
+ * 0 if error (conn->errorMessage is set)
+ */
+int
+PQsendCancelRequest(PGconn *cancelConn)
+{
+ CancelRequestPacket req;
+
+ /* Start the message. */
+ if (pqPutMsgStart(0, cancelConn))
+ return STATUS_ERROR;
+
+ /* Send the message body. */
+ memset(&req, 0, offsetof(CancelRequestPacket, cancelAuthCode));
+ req.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
+ req.backendPID = pg_hton32(cancelConn->be_pid);
+ if (pqPutnchar((char *) &req, offsetof(CancelRequestPacket, cancelAuthCode), cancelConn))
+ return STATUS_ERROR;
+ if (pqPutnchar(cancelConn->be_cancel_key, cancelConn->be_cancel_key_len, cancelConn))
+ return STATUS_ERROR;
+
+ /* Finish the message. */
+ if (pqPutMsgEnd(cancelConn))
+ return STATUS_ERROR;
+
+ /* Flush to ensure backend gets it. */
+ if (pqFlush(cancelConn))
+ return STATUS_ERROR;
+
+ return STATUS_OK;
+}
+
/* PQfreeCancel: free a cancel structure */
void
PQfreeCancel(PGcancel *cancel)
@@ -465,11 +532,8 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize)
int save_errno = SOCK_ERRNO;
pgsocket tmpsock = PGINVALID_SOCKET;
int maxlen;
- struct
- {
- uint32 packetlen;
- CancelRequestPacket cp;
- } crp;
+ char recvbuf;
+ int cancel_pkt_len;
if (!cancel)
{
@@ -571,15 +635,15 @@ retry3:
goto cancel_errReturn;
}
- /* Create and send the cancel request packet. */
-
- crp.packetlen = pg_hton32((uint32) sizeof(crp));
- crp.cp.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
- crp.cp.backendPID = pg_hton32(cancel->be_pid);
- crp.cp.cancelAuthCode = pg_hton32(cancel->be_key);
+ cancel_pkt_len = pg_ntoh32(cancel->cancel_pkt_len);
retry4:
- if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp))
+
+ /*
+ * Send the cancel request packet. It starts with the message length at
+ * cancel_pkt_len, followed by the actual packet.
+ */
+ if (send(tmpsock, (char *) &cancel->cancel_pkt_len, cancel_pkt_len, 0) != cancel_pkt_len)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
@@ -596,7 +660,7 @@ retry4:
* read to obtain any data, we are just waiting for EOF to be signaled.
*/
retry5:
- if (recv(tmpsock, (char *) &crp, 1, 0) < 0)
+ if (recv(tmpsock, &recvbuf, 1, 0) < 0)
{
if (SOCK_ERRNO == EINTR)
/* Interrupted system call - we'll just try again */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 5e3275ffd76..715b5d5aff4 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -696,7 +696,12 @@ pqDropServerData(PGconn *conn)
if (!conn->cancelRequest)
{
conn->be_pid = 0;
- conn->be_key = 0;
+ if (conn->be_cancel_key != NULL)
+ {
+ free(conn->be_cancel_key);
+ conn->be_cancel_key = NULL;
+ }
+ conn->be_cancel_key_len = 0;
}
}
@@ -3692,13 +3697,7 @@ keep_going: /* We will come back to here until there is
*/
if (conn->cancelRequest)
{
- CancelRequestPacket cancelpacket;
-
- packetlen = sizeof(cancelpacket);
- cancelpacket.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
- cancelpacket.backendPID = pg_hton32(conn->be_pid);
- cancelpacket.cancelAuthCode = pg_hton32(conn->be_key);
- if (pqPacketSend(conn, 0, &cancelpacket, packetlen) != STATUS_OK)
+ if (PQsendCancelRequest(conn) != STATUS_OK)
{
libpq_append_conn_error(conn, "could not send cancel packet: %s",
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 7ba49ea4592..d85910f41fc 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -48,6 +48,7 @@ static int getRowDescriptions(PGconn *conn, int msgLength);
static int getParamDescriptions(PGconn *conn, int msgLength);
static int getAnotherTuple(PGconn *conn, int msgLength);
static int getParameterStatus(PGconn *conn);
+static int getBackendKeyData(PGconn *conn, int msgLength);
static int getNotify(PGconn *conn);
static int getCopyStart(PGconn *conn, ExecStatusType copytype);
static int getReadyForQuery(PGconn *conn);
@@ -308,9 +309,7 @@ pqParseInput3(PGconn *conn)
* just as easy to handle it as part of the main loop.
* Save the data and continue processing.
*/
- if (pqGetInt(&(conn->be_pid), 4, conn))
- return;
- if (pqGetInt(&(conn->be_key), 4, conn))
+ if (getBackendKeyData(conn, msgLength))
return;
break;
case PqMsg_RowDescription:
@@ -1524,6 +1523,46 @@ getParameterStatus(PGconn *conn)
return 0;
}
+/*
+ * parseInput subroutine to read a BackendKeyData message.
+ * Entry: 'v' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ * returns EOF if not enough data.
+ */
+static int
+getBackendKeyData(PGconn *conn, int msgLength)
+{
+ uint8 cancel_key_len;
+
+ if (conn->be_cancel_key)
+ {
+ free(conn->be_cancel_key);
+ conn->be_cancel_key = NULL;
+ conn->be_cancel_key_len = 0;
+ }
+
+ if (pqGetInt(&(conn->be_pid), 4, conn))
+ return EOF;
+
+ cancel_key_len = 5 + msgLength - (conn->inCursor - conn->inStart);
+
+ conn->be_cancel_key = malloc(cancel_key_len);
+ if (conn->be_cancel_key == NULL)
+ {
+ libpq_append_conn_error(conn, "out of memory");
+ /* discard the message */
+ return EOF;
+ }
+ if (pqGetnchar(conn->be_cancel_key, cancel_key_len, conn))
+ {
+ free(conn->be_cancel_key);
+ conn->be_cancel_key = NULL;
+ return EOF;
+ }
+ conn->be_cancel_key_len = cancel_key_len;
+ return 0;
+}
+
/*
* Attempt to read a Notify response message.
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 232e0b00f75..25de3e95055 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -546,7 +546,8 @@ struct pg_conn
/* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */
- int be_key; /* key of backend --- needed for cancels */
+ char *be_cancel_key; /* query cancellation key and its length */
+ uint16 be_cancel_key_len;
pgParameterStatus *pstatus; /* ParameterStatus data */
int client_encoding; /* encoding id */
bool std_strings; /* standard_conforming_strings */
@@ -766,6 +767,10 @@ extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
int result_is_int,
const PQArgBlock *args, int nargs);
+/* === in fe-cancel.c === */
+
+extern int PQsendCancelRequest(PGconn *cancelConn);
+
/* === in fe-misc.c === */
/*
diff --git a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
index 9691b850418..61524bdbd8f 100644
--- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
+++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
@@ -49,11 +49,11 @@ for my $testname (@tests)
push @extraargs, "-t" => $traceout;
}
- # Execute the test
+ # Execute the test using the latest protocol version.
$node->command_ok(
[
'libpq_pipeline', @extraargs,
- $testname, $node->connstr('postgres')
+ $testname, $node->connstr('postgres') . " max_protocol_version=latest"
],
"libpq_pipeline $testname");
@@ -72,6 +72,14 @@ for my $testname (@tests)
}
}
+# There were changes to query cancellation in protocol version 3.2, so
+# test separately that it still works the old protocol version too.
+$node->command_ok(
+ [
+ 'libpq_pipeline', 'cancel', $node->connstr('postgres') . " max_protocol_version=3.0"
+ ],
+ "libpq_pipeline cancel with protocol 3.0");
+
$node->stop('fast');
done_testing();