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