</varlistentry>
<varlistentry>
- <term>Int32(12)</term>
+ <term>Int32</term>
<listitem>
<para>
Length of message contents in bytes, including self.
</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>
</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>
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 */
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);
* 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)
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);
/* 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
* fields in the ProcSignal slots.
*/
void
-SendCancelRequest(int backendPID, int32 cancelAuthCode)
+SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len)
{
Assert(backendPID != 0);
}
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);
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);
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;
}
return STATUS_OK;
}
+/*
+ * 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
* 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;
}
/*
{
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. */
}
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/procnumber.h"
+#include "storage/procsignal.h"
ProtocolVersion FrontendProtocol;
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;
/*
*/
SharedInvalBackendInit(false);
- ProcSignalInit(MyCancelKeyValid, MyCancelKey);
+ ProcSignalInit(MyCancelKey, MyCancelKeyLength);
/*
* Also set up timeout handlers needed for backend operation. We need
*
* 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
/* 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
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[];
PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
} 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
+
/*
* s 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);
/*-------------------------------------------------------------------------
*
* 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
{
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 */
* 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 */
};
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
*/
* 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
PQgetCancel(PGconn *conn)
{
PGcancel *cancel;
+ int cancel_req_len;
+ CancelRequestPacket *req;
if (!conn)
return NULL;
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;
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:
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)
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)
{
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 */
* 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 */
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;
}
}
*/
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)));
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);
* 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:
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.
/* 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 */
int result_is_int,
const PQArgBlock *args, int nargs);
+/* === in fe-cancel.c === */
+
+extern int PQsendCancelRequest(PGconn *cancelConn);
+
/* === in fe-misc.c === */
/*
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");
}
}
+# 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();