diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index a2bbf33d02..d3e87056f2 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -265,7 +265,7 @@ PGconn *PQsetdb(char *pghost,
PQconnectStartParamsPQconnectStartParams
PQconnectStartPQconnectStart
- PQconnectPollPQconnectPoll
+ PQconnectPollPQconnectPoll
nonblocking connection
@@ -2622,17 +2622,19 @@ int PQserverVersion(const PGconn *conn);
- PQerrorMessagePQerrorMessage
+
+ PQerrorMessagePQerrorMessage
+ error messagein PGconn
+
- error message Returns the error message
- most recently generated by an operation on the connection.
+ Returns the error message most recently generated by
+ an operation on the connection.
char *PQerrorMessage(const PGconn *conn);
-
@@ -5287,7 +5289,7 @@ int PQisBusy(PGconn *conn);
/
can also attempt to cancel a command that is still being processed
by the server; see . But regardless of
- the return value of , the application
+ the return value of , the application
must continue with the normal result-reading sequence using
. A successful cancellation will
simply cause the command to terminate sooner than it would have
@@ -6030,14 +6032,429 @@ int PQsetSingleRowMode(PGconn *conn);
Canceling Queries in Progress
- canceling
- SQL command
+ canceling SQL queries
+
+
+ query cancellation
-
- A client application can request cancellation of a command that is
- still being processed by the server, using the functions described in
- this section.
+
+ Functions for Sending Cancel Requests
+
+
+ PQcancelCreatePQcancelCreate
+
+
+
+ Prepares a connection over which a cancel request can be sent.
+
+PGcancelConn *PQcancelCreate(PGconn *conn);
+
+
+
+
+ creates a
+ PGcancelConnPGcancelConn
+ object, but it won't instantly start sending a cancel request over this
+ connection. A cancel request can be sent over this connection in a
+ blocking manner using and in a
+ non-blocking manner using .
+ The return value can be passed to
+ to check if the PGcancelConn object was
+ created successfully. The PGcancelConn object
+ is an opaque structure that is not meant to be accessed directly by the
+ application. This PGcancelConn object can be
+ used to cancel the query that's running on the original connection in a
+ thread-safe way.
+
+
+
+ Many connection parameters of the original client will be reused when
+ setting up the connection for the cancel request. Importantly, if the
+ original connection requires encryption of the connection and/or
+ verification of the target host (using sslmode or
+ gssencmode), then the connection for the cancel
+ request is made with these same requirements. Any connection options
+ that are only used during authentication or after authentication of the
+ client are ignored though, because cancellation requests do not require
+ authentication and the connection is closed right after the cancellation
+ request is submitted.
+
+
+
+ Note that when PQcancelCreate returns a non-null
+ pointer, you must call when you
+ are finished with it, in order to dispose of the structure and any
+ associated memory blocks. This must be done even if the cancel request
+ failed or was abandoned.
+
+
+
+
+
+ PQcancelBlockingPQcancelBlocking
+
+
+
+ Requests that the server abandons processing of the current command
+ in a blocking manner.
+
+int PQcancelBlocking(PGcancelConn *cancelConn);
+
+
+
+
+ The request is made over the given PGcancelConn,
+ which needs to be created with .
+ The return value of
+ is 1 if the cancel request was successfully
+ dispatched and 0 if not. If it was unsuccessful, the error message can be
+ retrieved using .
+
+
+
+ Successful dispatch of the cancellation is no guarantee that the request
+ will have any effect, however. If the cancellation is effective, the
+ command being canceled will terminate early and return an error result.
+ If the cancellation fails (say, because the server was already done
+ processing the command), then there will be no visible result at all.
+
+
+
+
+
+
+ PQcancelStartPQcancelStart
+ PQcancelPollPQcancelPoll
+
+
+
+ Requests that the server abandons processing of the current command
+ in a non-blocking manner.
+
+int PQcancelStart(PGcancelConn *cancelConn);
+
+PostgresPollingStatusType PQcancelPoll(PGcancelConn *cancelConn);
+
+
+
+
+ The request is made over the given PGcancelConn,
+ which needs to be created with .
+ The return value of
+ is 1 if the cancellation request could be started and 0 if not.
+ If it was unsuccessful, the error message can be
+ retrieved using .
+
+
+
+ If PQcancelStart succeeds, the next stage
+ is to poll libpq so that it can proceed with
+ the cancel connection sequence.
+ Use to obtain the descriptor of the
+ socket underlying the database connection.
+ (Caution: do not assume that the socket remains the same
+ across PQcancelPoll calls.)
+ Loop thus: If PQcancelPoll(cancelConn) last returned
+ PGRES_POLLING_READING, wait until the socket is ready to
+ read (as indicated by select(),
+ poll(), or similar system function).
+ Then call PQcancelPoll(cancelConn) again.
+ Conversely, if PQcancelPoll(cancelConn) last returned
+ PGRES_POLLING_WRITING, wait until the socket is ready
+ to write, then call PQcancelPoll(cancelConn) again.
+ On the first iteration, i.e., if you have yet to call
+ PQcancelPoll(cancelConn), behave as if it last returned
+ PGRES_POLLING_WRITING. Continue this loop until
+ PQcancelPoll(cancelConn) returns
+ PGRES_POLLING_FAILED, indicating the connection procedure
+ has failed, or PGRES_POLLING_OK, indicating cancel
+ request was successfully dispatched.
+
+
+
+ Successful dispatch of the cancellation is no guarantee that the request
+ will have any effect, however. If the cancellation is effective, the
+ command being canceled will terminate early and return an error result.
+ If the cancellation fails (say, because the server was already done
+ processing the command), then there will be no visible result at all.
+
+
+
+ At any time during connection, the status of the connection can be
+ checked by calling .
+ If this call returns CONNECTION_BAD, then
+ the cancel procedure has failed; if the call returns
+ CONNECTION_OK, then cancel request was
+ successfully dispatched.
+ Both of these states are equally detectable from the return value of
+ PQcancelPoll, described above.
+ Other states might also occur during (and only during) an asynchronous
+ connection procedure.
+ These indicate the current stage of the connection procedure and might
+ be useful to provide feedback to the user for example.
+ These statuses are:
+
+
+
+ CONNECTION_ALLOCATED
+
+
+ Waiting for a call to or
+ , to actually open the
+ socket. This is the connection state right after
+ calling
+ or . No connection to the
+ server has been initiated yet at this point. To actually start
+ sending the cancel request use or
+ .
+
+
+
+
+
+ CONNECTION_STARTED
+
+
+ Waiting for connection to be made.
+
+
+
+
+
+ CONNECTION_MADE
+
+
+ Connection OK; waiting to send.
+
+
+
+
+
+ CONNECTION_AWAITING_RESPONSE
+
+
+ Waiting for a response from the server.
+
+
+
+
+
+ CONNECTION_SSL_STARTUP
+
+
+ Negotiating SSL encryption.
+
+
+
+
+
+ CONNECTION_GSS_STARTUP
+
+
+ Negotiating GSS encryption.
+
+
+
+
+
+ Note that, although these constants will remain (in order to maintain
+ compatibility), an application should never rely upon these occurring in a
+ particular order, or at all, or on the status always being one of these
+ documented values. An application might do something like this:
+
+switch(PQcancelStatus(conn))
+{
+ case CONNECTION_STARTED:
+ feedback = "Connecting...";
+ break;
+
+ case CONNECTION_MADE:
+ feedback = "Connected to server...";
+ break;
+.
+.
+.
+ default:
+ feedback = "Connecting...";
+}
+
+
+
+
+ The connect_timeout connection parameter is ignored
+ when using PQcancelPoll; it is the application's
+ responsibility to decide whether an excessive amount of time has elapsed.
+ Otherwise, PQcancelStart followed by a
+ PQcancelPoll loop is equivalent to
+ .
+
+
+
+
+
+
+ PQcancelStatusPQcancelStatus
+
+
+
+ Returns the status of the cancel connection.
+
+ConnStatusType PQcancelStatus(const PGcancelConn *cancelConn);
+
+
+
+
+ The status can be one of a number of values. However, only three of
+ these are seen outside of an asynchronous cancel procedure:
+ CONNECTION_ALLOCATED,
+ CONNECTION_OK and
+ CONNECTION_BAD. The initial state of a
+ PGcancelConn that's successfully created using
+ is CONNECTION_ALLOCATED.
+ A cancel request that was successfully dispatched
+ has the status CONNECTION_OK. A failed
+ cancel attempt is signaled by status
+ CONNECTION_BAD. An OK status will
+ remain so until or
+ is called.
+
+
+
+ See the entry for with regards
+ to other status codes that might be returned.
+
+
+
+ Successful dispatch of the cancellation is no guarantee that the request
+ will have any effect, however. If the cancellation is effective, the
+ command being canceled will terminate early and return an error result.
+ If the cancellation fails (say, because the server was already done
+ processing the command), then there will be no visible result at all.
+
+
+
+
+
+
+ PQcancelSocketPQcancelSocket
+
+
+
+ Obtains the file descriptor number of the cancel connection socket to
+ the server.
+
+int PQcancelSocket(const PGcancelConn *cancelConn);
+
+
+
+
+ A valid descriptor will be greater than or equal to 0;
+ a result of -1 indicates that no server connection is currently open.
+ This might change as a result of calling any of the functions
+ in this section on the PQcancelConn
+ (except for and
+ PQcancelSocket itself).
+
+
+
+
+
+
+ PQcancelErrorMessagePQcancelErrorMessage
+ error messagein PGcancelConn
+
+
+
+
+ Returns the error message most recently generated by an
+ operation on the cancel connection.
+
+char *PQcancelErrorMessage(const PGcancelConn *cancelconn);
+
+
+
+
+ Nearly all libpq functions that take a
+ PGcancelConn will set a message for
+ if they fail.
+ Note that by libpq convention,
+ a nonempty result
+ can consist of multiple lines, and will include a trailing newline.
+ The caller should not free the result directly.
+ It will be freed when the associated
+ PGcancelConn handle is passed to
+ . The result string should not be
+ expected to remain the same across operations on the
+ PGcancelConn structure.
+
+
+
+
+
+ PQcancelFinishPQcancelFinish
+
+
+ Closes the cancel connection (if it did not finish sending the
+ cancel request yet). Also frees memory used by the
+ PGcancelConn object.
+
+void PQcancelFinish(PGcancelConn *cancelConn);
+
+
+
+
+ Note that even if the cancel attempt fails (as
+ indicated by ), the
+ application should call
+ to free the memory used by the PGcancelConn
+ object.
+ The PGcancelConn pointer must not be used
+ again after has been called.
+
+
+
+
+
+ PQcancelResetPQcancelReset
+
+
+ Resets the PGcancelConn so it can be reused for a new
+ cancel connection.
+
+void PQcancelReset(PGcancelConn *cancelConn);
+
+
+
+
+ If the PGcancelConn is currently used to send a cancel
+ request, then this connection is closed. It will then prepare the
+ PGcancelConn object such that it can be used to send a
+ new cancel request.
+
+
+
+ This can be used to create one PGcancelConn
+ for a PGconn and reuse it multiple times
+ throughout the lifetime of the original PGconn.
+
+
+
+
+
+
+
+ Obsolete Functions for Sending Cancel Requests
+
+
+ These functions represent older methods of sending cancel requests.
+ Although they still work, they are deprecated due to not sending the cancel
+ requests in an encrypted manner, even when the original connection
+ specified sslmode or gssencmode to
+ require encryption. Thus these older methods are heavily discouraged from
+ being used in new code, and it is recommended to change existing code to
+ use the new functions instead.
+
@@ -6046,7 +6463,7 @@ int PQsetSingleRowMode(PGconn *conn);
Creates a data structure containing the information needed to cancel
- a command issued through a particular database connection.
+ a command using .
PGcancel *PQgetCancel(PGconn *conn);
@@ -6054,10 +6471,11 @@ PGcancel *PQgetCancel(PGconn *conn);
creates a
- PGcancelPGcancel object
- given a PGconn connection object. It will return
- NULL if the given conn is NULL or an invalid
- connection. The PGcancel object is an opaque
+ PGcancelPGcancel
+ object given a PGconn connection object.
+ It will return NULL if the given conn
+ is NULL or an invalid connection.
+ The PGcancel object is an opaque
structure that is not meant to be accessed directly by the
application; it can only be passed to
or .
@@ -6088,36 +6506,38 @@ void PQfreeCancel(PGcancel *cancel);
- Requests that the server abandon processing of the current command.
+ is a deprecated and insecure
+ variant of , but one that can be
+ used safely from within a signal handler.
int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize);
- The return value is 1 if the cancel request was successfully
- dispatched and 0 if not. If not, errbuf is filled
- with an explanatory error message. errbuf
- must be a char array of size errbufsize (the
- recommended size is 256 bytes).
+ only exists because of backwards
+ compatibility reasons. should be
+ used instead. The only benefit that has
+ is that it can be safely invoked from a signal handler, if the
+ errbuf is a local variable in the signal handler.
+ However, this is generally not considered a big enough benefit to be
+ worth the security issues that this function has.
- Successful dispatch is no guarantee that the request will have
- any effect, however. If the cancellation is effective, the current
- command will terminate early and return an error result. If the
- cancellation fails (say, because the server was already done
- processing the command), then there will be no visible result at
- all.
+ The PGcancel object is read-only as far as
+ is concerned, so it can also be invoked
+ from a thread that is separate from the one manipulating the
+ PGconn object.
- can safely be invoked from a signal
- handler, if the errbuf is a local variable in the
- signal handler. The PGcancel object is read-only
- as far as is concerned, so it can
- also be invoked from a thread that is separate from the one
- manipulating the PGconn object.
+ The return value of is 1 if the
+ cancel request was successfully dispatched and 0 if not.
+ If not, errbuf is filled with an explanatory
+ error message.
+ errbuf must be a char array of size
+ errbufsize (the recommended size is 256 bytes).
@@ -6129,13 +6549,21 @@ int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize);
- is a deprecated variant of
- .
+ is a deprecated and insecure
+ variant of .
int PQrequestCancel(PGconn *conn);
+
+ only exists because of backwards
+ compatibility reasons. should be
+ used instead. There is no benefit to using
+ over
+ .
+
+
Requests that the server abandon processing of the current
command. It operates directly on the
@@ -6150,8 +6578,7 @@ int PQrequestCancel(PGconn *conn);
-
-
+
@@ -9362,7 +9789,7 @@ int PQisthreadsafe();
The deprecated functions and
are not thread-safe and should not be
used in multithread programs.
- can be replaced by .
+ can be replaced by .
can be replaced by
.
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 088592deb1..9fbd3d3407 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -193,3 +193,12 @@ PQsendClosePrepared 190
PQsendClosePortal 191
PQchangePassword 192
PQsendPipelineSync 193
+PQcancelBlocking 194
+PQcancelStart 195
+PQcancelCreate 196
+PQcancelPoll 197
+PQcancelStatus 198
+PQcancelSocket 199
+PQcancelErrorMessage 200
+PQcancelReset 201
+PQcancelFinish 202
diff --git a/src/interfaces/libpq/fe-cancel.c b/src/interfaces/libpq/fe-cancel.c
index d69b8f9f9f..6bbd126baf 100644
--- a/src/interfaces/libpq/fe-cancel.c
+++ b/src/interfaces/libpq/fe-cancel.c
@@ -22,6 +22,17 @@
#include "port/pg_bswap.h"
+/*
+ * pg_cancel_conn (backing struct for PGcancelConn) is a wrapper around a
+ * PGconn to send cancellations using PQcancelBlocking and PQcancelStart.
+ * This isn't just a typedef because we want the compiler to complain when a
+ * PGconn is passed to a function that expects a PGcancelConn, and vice versa.
+ */
+struct pg_cancel_conn
+{
+ PGconn conn;
+};
+
/*
* pg_cancel (backing struct for PGcancel) stores all data necessary to send a
* cancel request.
@@ -41,6 +52,289 @@ struct pg_cancel
};
+/*
+ * PQcancelCreate
+ *
+ * Create and return a PGcancelConn, which can be used to securely cancel a
+ * query on the given connection.
+ *
+ * This requires either following the non-blocking flow through
+ * PQcancelStart() and PQcancelPoll(), or the blocking PQcancelBlocking().
+ */
+PGcancelConn *
+PQcancelCreate(PGconn *conn)
+{
+ PGconn *cancelConn = pqMakeEmptyPGconn();
+ pg_conn_host originalHost;
+
+ if (cancelConn == NULL)
+ return NULL;
+
+ /* Check we have an open connection */
+ if (!conn)
+ {
+ libpq_append_conn_error(cancelConn, "passed connection was NULL");
+ return (PGcancelConn *) cancelConn;
+ }
+
+ if (conn->sock == PGINVALID_SOCKET)
+ {
+ libpq_append_conn_error(cancelConn, "passed connection is not open");
+ return (PGcancelConn *) cancelConn;
+ }
+
+ /*
+ * Indicate that this connection is used to send a cancellation
+ */
+ cancelConn->cancelRequest = true;
+
+ if (!pqCopyPGconn(conn, cancelConn))
+ return (PGcancelConn *) cancelConn;
+
+ /*
+ * Compute derived options
+ */
+ if (!pqConnectOptions2(cancelConn))
+ return (PGcancelConn *) cancelConn;
+
+ /*
+ * Copy cancellation token data from the original connnection
+ */
+ cancelConn->be_pid = conn->be_pid;
+ cancelConn->be_key = conn->be_key;
+
+ /*
+ * Cancel requests should not iterate over all possible hosts. The request
+ * needs to be sent to the exact host and address that the original
+ * connection used. So we manually create the host and address arrays with
+ * a single element after freeing the host array that we generated from
+ * the connection options.
+ */
+ pqReleaseConnHosts(cancelConn);
+ cancelConn->nconnhost = 1;
+ cancelConn->naddr = 1;
+
+ cancelConn->connhost = calloc(cancelConn->nconnhost, sizeof(pg_conn_host));
+ if (!cancelConn->connhost)
+ goto oom_error;
+
+ originalHost = conn->connhost[conn->whichhost];
+ if (originalHost.host)
+ {
+ cancelConn->connhost[0].host = strdup(originalHost.host);
+ if (!cancelConn->connhost[0].host)
+ goto oom_error;
+ }
+ if (originalHost.hostaddr)
+ {
+ cancelConn->connhost[0].hostaddr = strdup(originalHost.hostaddr);
+ if (!cancelConn->connhost[0].hostaddr)
+ goto oom_error;
+ }
+ if (originalHost.port)
+ {
+ cancelConn->connhost[0].port = strdup(originalHost.port);
+ if (!cancelConn->connhost[0].port)
+ goto oom_error;
+ }
+ if (originalHost.password)
+ {
+ cancelConn->connhost[0].password = strdup(originalHost.password);
+ if (!cancelConn->connhost[0].password)
+ goto oom_error;
+ }
+
+ cancelConn->addr = calloc(cancelConn->naddr, sizeof(AddrInfo));
+ if (!cancelConn->connhost)
+ goto oom_error;
+
+ cancelConn->addr[0].addr = conn->raddr;
+ cancelConn->addr[0].family = conn->raddr.addr.ss_family;
+
+ cancelConn->status = CONNECTION_ALLOCATED;
+ return (PGcancelConn *) cancelConn;
+
+oom_error:
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(cancelConn, "out of memory");
+ return (PGcancelConn *) cancelConn;
+}
+
+
+/*
+ * PQcancelBlocking
+ *
+ * Send a cancellation request in a blocking fashion.
+ * Returns 1 if successful 0 if not.
+ */
+int
+PQcancelBlocking(PGcancelConn *cancelConn)
+{
+ if (!PQcancelStart(cancelConn))
+ return 0;
+ return pqConnectDBComplete(&cancelConn->conn);
+}
+
+/*
+ * PQcancelStart
+ *
+ * Starts sending a cancellation request in a non-blocking fashion. Returns
+ * 1 if successful 0 if not.
+ */
+int
+PQcancelStart(PGcancelConn *cancelConn)
+{
+ if (!cancelConn || cancelConn->conn.status == CONNECTION_BAD)
+ return 0;
+
+ if (cancelConn->conn.status != CONNECTION_ALLOCATED)
+ {
+ libpq_append_conn_error(&cancelConn->conn,
+ "cancel request is already being sent on this connection");
+ cancelConn->conn.status = CONNECTION_BAD;
+ return 0;
+ }
+
+ return pqConnectDBStart(&cancelConn->conn);
+}
+
+/*
+ * PQcancelPoll
+ *
+ * Poll a cancel connection. For usage details see PQconnectPoll.
+ */
+PostgresPollingStatusType
+PQcancelPoll(PGcancelConn *cancelConn)
+{
+ PGconn *conn = &cancelConn->conn;
+ int n;
+
+ /*
+ * We leave most of the connection establishement to PQconnectPoll, since
+ * it's very similar to normal connection establishment. But once we get
+ * to the CONNECTION_AWAITING_RESPONSE we need to start doing our own
+ * thing.
+ */
+ if (conn->status != CONNECTION_AWAITING_RESPONSE)
+ {
+ return PQconnectPoll(conn);
+ }
+
+ /*
+ * At this point we are waiting on the server to close the connection,
+ * which is its way of communicating that the cancel has been handled.
+ */
+
+ n = pqReadData(conn);
+
+ if (n == 0)
+ return PGRES_POLLING_READING;
+
+#ifndef WIN32
+
+ /*
+ * If we receive an error report it, but only if errno is non-zero.
+ * Otherwise we assume it's an EOF, which is what we expect from the
+ * server.
+ *
+ * We skip this for Windows, because Windows is a bit special in its EOF
+ * behaviour for TCP. Sometimes it will error with an ECONNRESET when
+ * there is a clean connection closure. See these threads for details:
+ * https://www.postgresql.org/message-id/flat/90b34057-4176-7bb0-0dbb-9822a5f6425b%40greiz-reinsdorf.de
+ *
+ * https://www.postgresql.org/message-id/flat/CA%2BhUKG%2BOeoETZQ%3DQw5Ub5h3tmwQhBmDA%3DnuNO3KG%3DzWfUypFAw%40mail.gmail.com
+ *
+ * PQcancel ignores such errors and reports success for the cancellation
+ * anyway, so even if this is not always correct we do the same here.
+ */
+ if (n < 0 && errno != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ return PGRES_POLLING_FAILED;
+ }
+#endif
+
+ /*
+ * We don't expect any data, only connection closure. So if we strangely
+ * do receive some data we consider that an error.
+ */
+ if (n > 0)
+ {
+ libpq_append_conn_error(conn, "received unexpected response from server");
+ conn->status = CONNECTION_BAD;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Getting here means that we received an EOF, which is what we were
+ * expecting -- the cancel request has completed.
+ */
+ cancelConn->conn.status = CONNECTION_OK;
+ resetPQExpBuffer(&conn->errorMessage);
+ return PGRES_POLLING_OK;
+}
+
+/*
+ * PQcancelStatus
+ *
+ * Get the status of a cancel connection.
+ */
+ConnStatusType
+PQcancelStatus(const PGcancelConn *cancelConn)
+{
+ return PQstatus(&cancelConn->conn);
+}
+
+/*
+ * PQcancelSocket
+ *
+ * Get the socket of the cancel connection.
+ */
+int
+PQcancelSocket(const PGcancelConn *cancelConn)
+{
+ return PQsocket(&cancelConn->conn);
+}
+
+/*
+ * PQcancelErrorMessage
+ *
+ * Get the socket of the cancel connection.
+ */
+char *
+PQcancelErrorMessage(const PGcancelConn *cancelConn)
+{
+ return PQerrorMessage(&cancelConn->conn);
+}
+
+/*
+ * PQcancelReset
+ *
+ * Resets the cancel connection, so it can be reused to send a new cancel
+ * request.
+ */
+void
+PQcancelReset(PGcancelConn *cancelConn)
+{
+ pqClosePGconn(&cancelConn->conn);
+ cancelConn->conn.status = CONNECTION_ALLOCATED;
+ cancelConn->conn.whichhost = 0;
+ cancelConn->conn.whichaddr = 0;
+ cancelConn->conn.try_next_host = false;
+ cancelConn->conn.try_next_addr = false;
+}
+
+/*
+ * PQcancelFinish
+ *
+ * Closes and frees the cancel connection.
+ */
+void
+PQcancelFinish(PGcancelConn *cancelConn)
+{
+ PQfinish(&cancelConn->conn);
+}
+
/*
* PQgetCancel: get a PGcancel structure corresponding to a connection.
*
@@ -145,7 +439,7 @@ optional_setsockopt(int fd, int protoid, int optid, int value)
/*
- * PQcancel: request query cancel
+ * PQcancel: old, non-encrypted, but signal-safe way of requesting query cancel
*
* The return value is true if the cancel request was successfully
* dispatched, false if not (in which case an error message is available).
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d4e10a0c4f..01e49c6975 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -616,8 +616,17 @@ pqDropServerData(PGconn *conn)
conn->write_failed = false;
free(conn->write_err_msg);
conn->write_err_msg = NULL;
- conn->be_pid = 0;
- conn->be_key = 0;
+
+ /*
+ * Cancel connections need to retain their be_pid and be_key across
+ * PQcancelReset invocations, otherwise they would not have access to the
+ * secret token of the connection they are supposed to cancel.
+ */
+ if (!conn->cancelRequest)
+ {
+ conn->be_pid = 0;
+ conn->be_key = 0;
+ }
}
@@ -923,6 +932,45 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
return true;
}
+/*
+ * Copy over option values from srcConn to dstConn
+ *
+ * Don't put anything cute here --- intelligence should be in
+ * connectOptions2 ...
+ *
+ * Returns true on success. On failure, returns false and sets error message of
+ * dstConn.
+ */
+bool
+pqCopyPGconn(PGconn *srcConn, PGconn *dstConn)
+{
+ const internalPQconninfoOption *option;
+
+ /* copy over connection options */
+ for (option = PQconninfoOptions; option->keyword; option++)
+ {
+ if (option->connofs >= 0)
+ {
+ const char **tmp = (const char **) ((char *) srcConn + option->connofs);
+
+ if (*tmp)
+ {
+ char **dstConnmember = (char **) ((char *) dstConn + option->connofs);
+
+ if (*dstConnmember)
+ free(*dstConnmember);
+ *dstConnmember = strdup(*tmp);
+ if (*dstConnmember == NULL)
+ {
+ libpq_append_conn_error(dstConn, "out of memory");
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
/*
* connectOptions1
*
@@ -2308,10 +2356,18 @@ pqConnectDBStart(PGconn *conn)
* Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
* anything else looks at it.)
+ *
+ * Cancel requests are special though, they should only try one host and
+ * address, and these fields have already been set up in PQcancelCreate,
+ * so leave these fields alone for cancel requests.
*/
- conn->whichhost = -1;
- conn->try_next_addr = false;
- conn->try_next_host = true;
+ if (!conn->cancelRequest)
+ {
+ conn->whichhost = -1;
+ conn->try_next_host = true;
+ conn->try_next_addr = false;
+ }
+
conn->status = CONNECTION_NEEDED;
/* Also reset the target_server_type state if needed */
@@ -2453,7 +2509,10 @@ pqConnectDBComplete(PGconn *conn)
/*
* Now try to advance the state machine.
*/
- flag = PQconnectPoll(conn);
+ if (conn->cancelRequest)
+ flag = PQcancelPoll((PGcancelConn *) conn);
+ else
+ flag = PQconnectPoll(conn);
}
}
@@ -2578,13 +2637,17 @@ keep_going: /* We will come back to here until there is
* Oops, no more hosts.
*
* If we are trying to connect in "prefer-standby" mode, then drop
- * the standby requirement and start over.
+ * the standby requirement and start over. Don't do this for
+ * cancel requests though, since we are certain the list of
+ * servers won't change as the target_server_type option is not
+ * applicable to those connections.
*
* Otherwise, an appropriate error message is already set up, so
* we just need to set the right status.
*/
if (conn->target_server_type == SERVER_TYPE_PREFER_STANDBY &&
- conn->nconnhost > 0)
+ conn->nconnhost > 0 &&
+ !conn->cancelRequest)
{
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY_PASS2;
conn->whichhost = 0;
@@ -3226,6 +3289,29 @@ keep_going: /* We will come back to here until there is
}
#endif /* USE_SSL */
+ /*
+ * For cancel requests this is as far as we need to go in the
+ * connection establishment. Now we can actually send our
+ * cancellation request.
+ */
+ 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)
+ {
+ libpq_append_conn_error(conn, "could not send cancel packet: %s",
+ SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+ goto error_return;
+ }
+ conn->status = CONNECTION_AWAITING_RESPONSE;
+ return PGRES_POLLING_READING;
+ }
+
/*
* Build the startup packet.
*/
@@ -3975,8 +4061,14 @@ keep_going: /* We will come back to here until there is
}
}
- /* We can release the address list now. */
- release_conn_addrinfo(conn);
+ /*
+ * For non cancel requests we can release the address list
+ * now. For cancel requests we never actually resolve
+ * addresses and instead the addrinfo exists for the lifetime
+ * of the connection.
+ */
+ if (!conn->cancelRequest)
+ release_conn_addrinfo(conn);
/*
* Contents of conn->errorMessage are no longer interesting
@@ -4344,6 +4436,7 @@ freePGconn(PGconn *conn)
free(conn->events[i].name);
}
+ release_conn_addrinfo(conn);
pqReleaseConnHosts(conn);
free(conn->client_encoding_initial);
@@ -4495,6 +4588,13 @@ release_conn_addrinfo(PGconn *conn)
static void
sendTerminateConn(PGconn *conn)
{
+ /*
+ * The Postgres cancellation protocol does not have a notion of a
+ * Terminate message, so don't send one.
+ */
+ if (conn->cancelRequest)
+ return;
+
/*
* Note that the protocol doesn't allow us to send Terminate messages
* during the startup phase.
@@ -4548,7 +4648,14 @@ pqClosePGconn(PGconn *conn)
conn->pipelineStatus = PQ_PIPELINE_OFF;
pqClearAsyncResult(conn); /* deallocate result */
pqClearConnErrorState(conn);
- release_conn_addrinfo(conn);
+
+ /*
+ * Release addrinfo, but since cancel requests never change their addrinfo
+ * we don't do that. Otherwise we would have to rebuild it during a
+ * PQcancelReset.
+ */
+ if (!conn->cancelRequest)
+ release_conn_addrinfo(conn);
/* Reset all state obtained from server, too */
pqDropServerData(conn);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 2c06044a75..09b485bd2b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -79,7 +79,9 @@ typedef enum
CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */
CONNECTION_CHECK_TARGET, /* Internal state: checking target server
* properties. */
- CONNECTION_CHECK_STANDBY /* Checking if server is in standby mode. */
+ CONNECTION_CHECK_STANDBY, /* Checking if server is in standby mode. */
+ CONNECTION_ALLOCATED /* Waiting for connection attempt to be
+ * started. */
} ConnStatusType;
typedef enum
@@ -166,6 +168,11 @@ typedef enum
*/
typedef struct pg_conn PGconn;
+/* PGcancelConn encapsulates a cancel connection to the backend.
+ * The contents of this struct are not supposed to be known to applications.
+ */
+typedef struct pg_cancel_conn PGcancelConn;
+
/* PGresult encapsulates the result of a query (or more precisely, of a single
* SQL command --- a query string given to PQsendQuery can contain multiple
* commands and thus return multiple PGresult objects).
@@ -322,16 +329,34 @@ extern PostgresPollingStatusType PQresetPoll(PGconn *conn);
/* Synchronous (blocking) */
extern void PQreset(PGconn *conn);
+/* Create a PGcancelConn that's used to cancel a query on the given PGconn */
+extern PGcancelConn *PQcancelCreate(PGconn *conn);
+
+/* issue a cancel request in a non-blocking manner */
+extern int PQcancelStart(PGcancelConn *cancelConn);
+
+/* issue a blocking cancel request */
+extern int PQcancelBlocking(PGcancelConn *cancelConn);
+
+/* poll a non-blocking cancel request */
+extern PostgresPollingStatusType PQcancelPoll(PGcancelConn *cancelConn);
+extern ConnStatusType PQcancelStatus(const PGcancelConn *cancelConn);
+extern int PQcancelSocket(const PGcancelConn *cancelConn);
+extern char *PQcancelErrorMessage(const PGcancelConn *cancelConn);
+extern void PQcancelReset(PGcancelConn *cancelConn);
+extern void PQcancelFinish(PGcancelConn *cancelConn);
+
+
/* request a cancel structure */
extern PGcancel *PQgetCancel(PGconn *conn);
/* free a cancel structure */
extern void PQfreeCancel(PGcancel *cancel);
-/* issue a cancel request */
+/* deprecated version of PQcancelBlocking, but one which is signal-safe */
extern int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize);
-/* backwards compatible version of PQcancel; not thread-safe */
+/* deprecated version of PQcancel; not thread-safe */
extern int PQrequestCancel(PGconn *conn);
/* Accessor functions for PGconn objects */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3abcd180d6..9c05f11a6e 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -409,6 +409,10 @@ struct pg_conn
char *require_auth; /* name of the expected auth method */
char *load_balance_hosts; /* load balance over hosts */
+ bool cancelRequest; /* true if this connection is used to send a
+ * cancel request, instead of being a normal
+ * connection that's used for queries */
+
/* Optional file to write trace info to */
FILE *Pfdebug;
int traceFlags;
@@ -669,6 +673,7 @@ extern void pqClosePGconn(PGconn *conn);
extern int pqPacketSend(PGconn *conn, char pack_type,
const void *buf, size_t buf_len);
extern bool pqGetHomeDirectory(char *buf, int bufsize);
+extern bool pqCopyPGconn(PGconn *srcConn, PGconn *dstConn);
extern bool pqParseIntParam(const char *value, int *result, PGconn *conn,
const char *context);
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index c6c7b1c3a1..1fe15ee889 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -215,6 +215,7 @@ static void
test_cancel(PGconn *conn)
{
PGcancel *cancel;
+ PGcancelConn *cancelConn;
PGconn *monitorConn;
char errorbuf[256];
@@ -251,6 +252,126 @@ test_cancel(PGconn *conn)
pg_fatal("failed to run PQrequestCancel: %s", PQerrorMessage(conn));
confirm_query_canceled(conn);
+ /* test PQcancelBlocking */
+ send_cancellable_query(conn, monitorConn);
+ cancelConn = PQcancelCreate(conn);
+ if (!PQcancelBlocking(cancelConn))
+ pg_fatal("failed to run PQcancelBlocking: %s", PQcancelErrorMessage(cancelConn));
+ confirm_query_canceled(conn);
+ PQcancelFinish(cancelConn);
+
+ /* test PQcancelCreate and then polling with PQcancelPoll */
+ send_cancellable_query(conn, monitorConn);
+ cancelConn = PQcancelCreate(conn);
+ if (!PQcancelStart(cancelConn))
+ pg_fatal("bad cancel connection: %s", PQcancelErrorMessage(cancelConn));
+ while (true)
+ {
+ struct timeval tv;
+ fd_set input_mask;
+ fd_set output_mask;
+ PostgresPollingStatusType pollres = PQcancelPoll(cancelConn);
+ int sock = PQcancelSocket(cancelConn);
+
+ if (pollres == PGRES_POLLING_OK)
+ break;
+
+ FD_ZERO(&input_mask);
+ FD_ZERO(&output_mask);
+ switch (pollres)
+ {
+ case PGRES_POLLING_READING:
+ pg_debug("polling for reads\n");
+ FD_SET(sock, &input_mask);
+ break;
+ case PGRES_POLLING_WRITING:
+ pg_debug("polling for writes\n");
+ FD_SET(sock, &output_mask);
+ break;
+ default:
+ pg_fatal("bad cancel connection: %s", PQcancelErrorMessage(cancelConn));
+ }
+
+ if (sock < 0)
+ pg_fatal("sock did not exist: %s", PQcancelErrorMessage(cancelConn));
+
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+
+ while (true)
+ {
+ if (select(sock + 1, &input_mask, &output_mask, NULL, &tv) < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ pg_fatal("select() failed: %m");
+ }
+ break;
+ }
+ }
+ if (PQcancelStatus(cancelConn) != CONNECTION_OK)
+ pg_fatal("unexpected cancel connection status: %s", PQcancelErrorMessage(cancelConn));
+ confirm_query_canceled(conn);
+
+ /*
+ * test PQcancelReset works on the cancel connection and it can be reused
+ * afterwards
+ */
+ PQcancelReset(cancelConn);
+
+ send_cancellable_query(conn, monitorConn);
+ if (!PQcancelStart(cancelConn))
+ pg_fatal("bad cancel connection: %s", PQcancelErrorMessage(cancelConn));
+ while (true)
+ {
+ struct timeval tv;
+ fd_set input_mask;
+ fd_set output_mask;
+ PostgresPollingStatusType pollres = PQcancelPoll(cancelConn);
+ int sock = PQcancelSocket(cancelConn);
+
+ if (pollres == PGRES_POLLING_OK)
+ break;
+
+ FD_ZERO(&input_mask);
+ FD_ZERO(&output_mask);
+ switch (pollres)
+ {
+ case PGRES_POLLING_READING:
+ pg_debug("polling for reads\n");
+ FD_SET(sock, &input_mask);
+ break;
+ case PGRES_POLLING_WRITING:
+ pg_debug("polling for writes\n");
+ FD_SET(sock, &output_mask);
+ break;
+ default:
+ pg_fatal("bad cancel connection: %s", PQcancelErrorMessage(cancelConn));
+ }
+
+ if (sock < 0)
+ pg_fatal("sock did not exist: %s", PQcancelErrorMessage(cancelConn));
+
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+
+ while (true)
+ {
+ if (select(sock + 1, &input_mask, &output_mask, NULL, &tv) < 0)
+ {
+ if (errno == EINTR)
+ continue;
+ pg_fatal("select() failed: %m");
+ }
+ break;
+ }
+ }
+ if (PQcancelStatus(cancelConn) != CONNECTION_OK)
+ pg_fatal("unexpected cancel connection status: %s", PQcancelErrorMessage(cancelConn));
+ confirm_query_canceled(conn);
+
+ PQcancelFinish(cancelConn);
+
fprintf(stderr, "ok\n");
}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a3052a181d..aa7a25b8f8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1763,6 +1763,7 @@ PG_Locale_Strategy
PG_Lock_Status
PG_init_t
PGcancel
+PGcancelConn
PGcmdQueueEntry
PGconn
PGdataValue