diff options
-rw-r--r-- | doc/src/sgml/libpq.sgml | 507 | ||||
-rw-r--r-- | src/interfaces/libpq/exports.txt | 9 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-cancel.c | 296 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-connect.c | 129 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-fe.h | 31 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-int.h | 5 | ||||
-rw-r--r-- | src/test/modules/libpq_pipeline/libpq_pipeline.c | 121 | ||||
-rw-r--r-- | src/tools/pgindent/typedefs.list | 1 |
8 files changed, 1044 insertions, 55 deletions
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index a2bbf33d029..d3e87056f2c 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -265,7 +265,7 @@ PGconn *PQsetdb(char *pghost, <varlistentry id="libpq-PQconnectStartParams"> <term><function>PQconnectStartParams</function><indexterm><primary>PQconnectStartParams</primary></indexterm></term> <term><function>PQconnectStart</function><indexterm><primary>PQconnectStart</primary></indexterm></term> - <term><function>PQconnectPoll</function><indexterm><primary>PQconnectPoll</primary></indexterm></term> + <term id="libpq-PQconnectPoll"><function>PQconnectPoll</function><indexterm><primary>PQconnectPoll</primary></indexterm></term> <listitem> <para> <indexterm><primary>nonblocking connection</primary></indexterm> @@ -2622,17 +2622,19 @@ int PQserverVersion(const PGconn *conn); </varlistentry> <varlistentry id="libpq-PQerrorMessage"> - <term><function>PQerrorMessage</function><indexterm><primary>PQerrorMessage</primary></indexterm></term> + <term> + <function>PQerrorMessage</function><indexterm><primary>PQerrorMessage</primary></indexterm> + <indexterm><primary>error message</primary><secondary>in <structname>PGconn</structname></secondary></indexterm> + </term> <listitem> <para> - <indexterm><primary>error message</primary></indexterm> 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. <synopsis> char *PQerrorMessage(const PGconn *conn); </synopsis> - </para> <para> @@ -5287,7 +5289,7 @@ int PQisBusy(PGconn *conn); <xref linkend="libpq-PQsendQuery"/>/<xref linkend="libpq-PQgetResult"/> can also attempt to cancel a command that is still being processed by the server; see <xref linkend="libpq-cancel"/>. But regardless of - the return value of <xref linkend="libpq-PQcancel"/>, the application + the return value of <xref linkend="libpq-PQcancelBlocking"/>, the application must continue with the normal result-reading sequence using <xref linkend="libpq-PQgetResult"/>. A successful cancellation will simply cause the command to terminate sooner than it would have @@ -6030,14 +6032,429 @@ int PQsetSingleRowMode(PGconn *conn); <title>Canceling Queries in Progress</title> <indexterm zone="libpq-cancel"> - <primary>canceling</primary> - <secondary>SQL command</secondary> + <primary>canceling SQL queries</primary> + </indexterm> + <indexterm zone="libpq-cancel"> + <primary>query cancellation</primary> </indexterm> - <para> - A client application can request cancellation of a command that is - still being processed by the server, using the functions described in - this section. + <sect2 id="libpq-cancel-functions"> + <title>Functions for Sending Cancel Requests</title> + <variablelist> + <varlistentry id="libpq-PQcancelCreate"> + <term><function>PQcancelCreate</function><indexterm><primary>PQcancelCreate</primary></indexterm></term> + + <listitem> + <para> + Prepares a connection over which a cancel request can be sent. +<synopsis> +PGcancelConn *PQcancelCreate(PGconn *conn); +</synopsis> + </para> + + <para> + <xref linkend="libpq-PQcancelCreate"/> creates a + <structname>PGcancelConn</structname><indexterm><primary>PGcancelConn</primary></indexterm> + 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 <xref linkend="libpq-PQcancelBlocking"/> and in a + non-blocking manner using <xref linkend="libpq-PQcancelStart"/>. + The return value can be passed to <xref linkend="libpq-PQcancelStatus"/> + to check if the <structname>PGcancelConn</structname> object was + created successfully. The <structname>PGcancelConn</structname> object + is an opaque structure that is not meant to be accessed directly by the + application. This <structname>PGcancelConn</structname> object can be + used to cancel the query that's running on the original connection in a + thread-safe way. + </para> + + <para> + 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 <literal>sslmode</literal> or + <literal>gssencmode</literal>), 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. + </para> + + <para> + Note that when <function>PQcancelCreate</function> returns a non-null + pointer, you must call <xref linkend="libpq-PQcancelFinish"/> 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. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelBlocking"> + <term><function>PQcancelBlocking</function><indexterm><primary>PQcancelBlocking</primary></indexterm></term> + + <listitem> + <para> + Requests that the server abandons processing of the current command + in a blocking manner. +<synopsis> +int PQcancelBlocking(PGcancelConn *cancelConn); +</synopsis> + </para> + + <para> + The request is made over the given <structname>PGcancelConn</structname>, + which needs to be created with <xref linkend="libpq-PQcancelCreate"/>. + The return value of <xref linkend="libpq-PQcancelBlocking"/> + is 1 if the cancel request was successfully + dispatched and 0 if not. If it was unsuccessful, the error message can be + retrieved using <xref linkend="libpq-PQcancelErrorMessage"/>. + </para> + + <para> + 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. + </para> + + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelStart"> + <term><function>PQcancelStart</function><indexterm><primary>PQcancelStart</primary></indexterm></term> + <term><function>PQcancelPoll</function><indexterm><primary>PQcancelPoll</primary></indexterm></term> + + <listitem> + <para> + Requests that the server abandons processing of the current command + in a non-blocking manner. +<synopsis> +int PQcancelStart(PGcancelConn *cancelConn); + +PostgresPollingStatusType PQcancelPoll(PGcancelConn *cancelConn); +</synopsis> + </para> + + <para> + The request is made over the given <structname>PGcancelConn</structname>, + which needs to be created with <xref linkend="libpq-PQcancelCreate"/>. + The return value of <xref linkend="libpq-PQcancelStart"/> + is 1 if the cancellation request could be started and 0 if not. + If it was unsuccessful, the error message can be + retrieved using <xref linkend="libpq-PQcancelErrorMessage"/>. + </para> + + <para> + If <function>PQcancelStart</function> succeeds, the next stage + is to poll <application>libpq</application> so that it can proceed with + the cancel connection sequence. + Use <xref linkend="libpq-PQcancelSocket"/> to obtain the descriptor of the + socket underlying the database connection. + (Caution: do not assume that the socket remains the same + across <function>PQcancelPoll</function> calls.) + Loop thus: If <function>PQcancelPoll(cancelConn)</function> last returned + <symbol>PGRES_POLLING_READING</symbol>, wait until the socket is ready to + read (as indicated by <function>select()</function>, + <function>poll()</function>, or similar system function). + Then call <function>PQcancelPoll(cancelConn)</function> again. + Conversely, if <function>PQcancelPoll(cancelConn)</function> last returned + <symbol>PGRES_POLLING_WRITING</symbol>, wait until the socket is ready + to write, then call <function>PQcancelPoll(cancelConn)</function> again. + On the first iteration, i.e., if you have yet to call + <function>PQcancelPoll(cancelConn)</function>, behave as if it last returned + <symbol>PGRES_POLLING_WRITING</symbol>. Continue this loop until + <function>PQcancelPoll(cancelConn)</function> returns + <symbol>PGRES_POLLING_FAILED</symbol>, indicating the connection procedure + has failed, or <symbol>PGRES_POLLING_OK</symbol>, indicating cancel + request was successfully dispatched. + </para> + + <para> + 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. + </para> + + <para> + At any time during connection, the status of the connection can be + checked by calling <xref linkend="libpq-PQcancelStatus"/>. + If this call returns <symbol>CONNECTION_BAD</symbol>, then + the cancel procedure has failed; if the call returns + <function>CONNECTION_OK</function>, then cancel request was + successfully dispatched. + Both of these states are equally detectable from the return value of + <function>PQcancelPoll</function>, 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: + + <variablelist> + <varlistentry id="libpq-cancel-connection-allocated"> + <term><symbol>CONNECTION_ALLOCATED</symbol></term> + <listitem> + <para> + Waiting for a call to <xref linkend="libpq-PQcancelStart"/> or + <xref linkend="libpq-PQcancelBlocking"/>, to actually open the + socket. This is the connection state right after + calling <xref linkend="libpq-PQcancelCreate"/> + or <xref linkend="libpq-PQcancelReset"/>. No connection to the + server has been initiated yet at this point. To actually start + sending the cancel request use <xref linkend="libpq-PQcancelStart"/> or + <xref linkend="libpq-PQcancelBlocking"/>. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-cancel-connection-started"> + <term><symbol>CONNECTION_STARTED</symbol></term> + <listitem> + <para> + Waiting for connection to be made. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-cancel-connection-made"> + <term><symbol>CONNECTION_MADE</symbol></term> + <listitem> + <para> + Connection OK; waiting to send. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-cancel-connection-awaiting-response"> + <term><symbol>CONNECTION_AWAITING_RESPONSE</symbol></term> + <listitem> + <para> + Waiting for a response from the server. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-cancel-connection-ssl-startup"> + <term><symbol>CONNECTION_SSL_STARTUP</symbol></term> + <listitem> + <para> + Negotiating SSL encryption. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-cancel-connection-gss-startup"> + <term><symbol>CONNECTION_GSS_STARTUP</symbol></term> + <listitem> + <para> + Negotiating GSS encryption. + </para> + </listitem> + </varlistentry> + </variablelist> + + 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: +<programlisting> +switch(PQcancelStatus(conn)) +{ + case CONNECTION_STARTED: + feedback = "Connecting..."; + break; + + case CONNECTION_MADE: + feedback = "Connected to server..."; + break; +. +. +. + default: + feedback = "Connecting..."; +} +</programlisting> + </para> + + <para> + The <literal>connect_timeout</literal> connection parameter is ignored + when using <function>PQcancelPoll</function>; it is the application's + responsibility to decide whether an excessive amount of time has elapsed. + Otherwise, <function>PQcancelStart</function> followed by a + <function>PQcancelPoll</function> loop is equivalent to + <xref linkend="libpq-PQcancelBlocking"/>. + </para> + + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelStatus"> + <term><function>PQcancelStatus</function><indexterm><primary>PQcancelStatus</primary></indexterm></term> + + <listitem> + <para> + Returns the status of the cancel connection. +<synopsis> +ConnStatusType PQcancelStatus(const PGcancelConn *cancelConn); +</synopsis> + </para> + + <para> + The status can be one of a number of values. However, only three of + these are seen outside of an asynchronous cancel procedure: + <literal>CONNECTION_ALLOCATED</literal>, + <literal>CONNECTION_OK</literal> and + <literal>CONNECTION_BAD</literal>. The initial state of a + <function>PGcancelConn</function> that's successfully created using + <xref linkend="libpq-PQcancelCreate"/> is <literal>CONNECTION_ALLOCATED</literal>. + A cancel request that was successfully dispatched + has the status <literal>CONNECTION_OK</literal>. A failed + cancel attempt is signaled by status + <literal>CONNECTION_BAD</literal>. An OK status will + remain so until <xref linkend="libpq-PQcancelFinish"/> or + <xref linkend="libpq-PQcancelReset"/> is called. + </para> + + <para> + See the entry for <xref linkend="libpq-PQcancelStart"/> with regards + to other status codes that might be returned. + </para> + + <para> + 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. + </para> + + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelSocket"> + <term><function>PQcancelSocket</function><indexterm><primary>PQcancelSocket</primary></indexterm></term> + + <listitem> + <para> + Obtains the file descriptor number of the cancel connection socket to + the server. +<synopsis> +int PQcancelSocket(const PGcancelConn *cancelConn); +</synopsis> + </para> + + <para> + 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 <structname>PQcancelConn</structname> + (except for <xref linkend="libpq-PQcancelErrorMessage"/> and + <function>PQcancelSocket</function> itself). + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelErrorMessage"> + <term> + <function>PQcancelErrorMessage</function><indexterm><primary>PQcancelErrorMessage</primary></indexterm> + <indexterm><primary>error message</primary><secondary>in <structname>PGcancelConn</structname></secondary></indexterm> + </term> + + <listitem> + <para> + Returns the error message most recently generated by an + operation on the cancel connection. +<synopsis> +char *PQcancelErrorMessage(const PGcancelConn *cancelconn); +</synopsis> + </para> + + <para> + Nearly all <application>libpq</application> functions that take a + <structname>PGcancelConn</structname> will set a message for + <xref linkend="libpq-PQcancelErrorMessage"/> if they fail. + Note that by <application>libpq</application> convention, + a nonempty <xref linkend="libpq-PQcancelErrorMessage"/> 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 + <structname>PGcancelConn</structname> handle is passed to + <xref linkend="libpq-PQcancelFinish"/>. The result string should not be + expected to remain the same across operations on the + <literal>PGcancelConn</literal> structure. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelFinish"> + <term><function>PQcancelFinish</function><indexterm><primary>PQcancelFinish</primary></indexterm></term> + <listitem> + <para> + Closes the cancel connection (if it did not finish sending the + cancel request yet). Also frees memory used by the + <structname>PGcancelConn</structname> object. +<synopsis> +void PQcancelFinish(PGcancelConn *cancelConn); +</synopsis> + </para> + + <para> + Note that even if the cancel attempt fails (as + indicated by <xref linkend="libpq-PQcancelStatus"/>), the + application should call <xref linkend="libpq-PQcancelFinish"/> + to free the memory used by the <structname>PGcancelConn</structname> + object. + The <structname>PGcancelConn</structname> pointer must not be used + again after <xref linkend="libpq-PQcancelFinish"/> has been called. + </para> + </listitem> + </varlistentry> + + <varlistentry id="libpq-PQcancelReset"> + <term><function>PQcancelReset</function><indexterm><primary>PQcancelReset</primary></indexterm></term> + <listitem> + <para> + Resets the <symbol>PGcancelConn</symbol> so it can be reused for a new + cancel connection. +<synopsis> +void PQcancelReset(PGcancelConn *cancelConn); +</synopsis> + </para> + + <para> + If the <symbol>PGcancelConn</symbol> is currently used to send a cancel + request, then this connection is closed. It will then prepare the + <symbol>PGcancelConn</symbol> object such that it can be used to send a + new cancel request. + </para> + + <para> + This can be used to create one <structname>PGcancelConn</structname> + for a <structname>PGconn</structname> and reuse it multiple times + throughout the lifetime of the original <structname>PGconn</structname>. + </para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="libpq-cancel-deprecated"> + <title>Obsolete Functions for Sending Cancel Requests</title> + + <para> + 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 <literal>sslmode</literal> or <literal>gssencmode</literal> 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. + </para> <variablelist> <varlistentry id="libpq-PQgetCancel"> @@ -6046,7 +6463,7 @@ int PQsetSingleRowMode(PGconn *conn); <listitem> <para> Creates a data structure containing the information needed to cancel - a command issued through a particular database connection. + a command using <xref linkend="libpq-PQcancel"/>. <synopsis> PGcancel *PQgetCancel(PGconn *conn); </synopsis> @@ -6054,10 +6471,11 @@ PGcancel *PQgetCancel(PGconn *conn); <para> <xref linkend="libpq-PQgetCancel"/> creates a - <structname>PGcancel</structname><indexterm><primary>PGcancel</primary></indexterm> object - given a <structname>PGconn</structname> connection object. It will return - <symbol>NULL</symbol> if the given <parameter>conn</parameter> is <symbol>NULL</symbol> or an invalid - connection. The <structname>PGcancel</structname> object is an opaque + <structname>PGcancel</structname><indexterm><primary>PGcancel</primary></indexterm> + object given a <structname>PGconn</structname> connection object. + It will return <symbol>NULL</symbol> if the given <parameter>conn</parameter> + is <symbol>NULL</symbol> or an invalid connection. + The <structname>PGcancel</structname> object is an opaque structure that is not meant to be accessed directly by the application; it can only be passed to <xref linkend="libpq-PQcancel"/> or <xref linkend="libpq-PQfreeCancel"/>. @@ -6088,36 +6506,38 @@ void PQfreeCancel(PGcancel *cancel); <listitem> <para> - Requests that the server abandon processing of the current command. + <xref linkend="libpq-PQcancel"/> is a deprecated and insecure + variant of <xref linkend="libpq-PQcancelBlocking"/>, but one that can be + used safely from within a signal handler. <synopsis> int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize); </synopsis> </para> <para> - The return value is 1 if the cancel request was successfully - dispatched and 0 if not. If not, <parameter>errbuf</parameter> is filled - with an explanatory error message. <parameter>errbuf</parameter> - must be a char array of size <parameter>errbufsize</parameter> (the - recommended size is 256 bytes). + <xref linkend="libpq-PQcancel"/> only exists because of backwards + compatibility reasons. <xref linkend="libpq-PQcancelBlocking"/> should be + used instead. The only benefit that <xref linkend="libpq-PQcancel"/> has + is that it can be safely invoked from a signal handler, if the + <parameter>errbuf</parameter> 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. </para> <para> - 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 <structname>PGcancel</structname> object is read-only as far as + <xref linkend="libpq-PQcancel"/> is concerned, so it can also be invoked + from a thread that is separate from the one manipulating the + <structname>PGconn</structname> object. </para> <para> - <xref linkend="libpq-PQcancel"/> can safely be invoked from a signal - handler, if the <parameter>errbuf</parameter> is a local variable in the - signal handler. The <structname>PGcancel</structname> object is read-only - as far as <xref linkend="libpq-PQcancel"/> is concerned, so it can - also be invoked from a thread that is separate from the one - manipulating the <structname>PGconn</structname> object. + The return value of <xref linkend="libpq-PQcancel"/> is 1 if the + cancel request was successfully dispatched and 0 if not. + If not, <parameter>errbuf</parameter> is filled with an explanatory + error message. + <parameter>errbuf</parameter> must be a char array of size + <parameter>errbufsize</parameter> (the recommended size is 256 bytes). </para> </listitem> </varlistentry> @@ -6129,14 +6549,22 @@ int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize); <listitem> <para> - <xref linkend="libpq-PQrequestCancel"/> is a deprecated variant of - <xref linkend="libpq-PQcancel"/>. + <xref linkend="libpq-PQrequestCancel"/> is a deprecated and insecure + variant of <xref linkend="libpq-PQcancelBlocking"/>. <synopsis> int PQrequestCancel(PGconn *conn); </synopsis> </para> <para> + <xref linkend="libpq-PQrequestCancel"/> only exists because of backwards + compatibility reasons. <xref linkend="libpq-PQcancelBlocking"/> should be + used instead. There is no benefit to using + <xref linkend="libpq-PQrequestCancel"/> over + <xref linkend="libpq-PQcancelBlocking"/>. + </para> + + <para> Requests that the server abandon processing of the current command. It operates directly on the <structname>PGconn</structname> object, and in case of failure stores the @@ -6150,8 +6578,7 @@ int PQrequestCancel(PGconn *conn); </listitem> </varlistentry> </variablelist> - </para> - + </sect2> </sect1> <sect1 id="libpq-fastpath"> @@ -9362,7 +9789,7 @@ int PQisthreadsafe(); The deprecated functions <xref linkend="libpq-PQrequestCancel"/> and <xref linkend="libpq-PQoidStatus"/> are not thread-safe and should not be used in multithread programs. <xref linkend="libpq-PQrequestCancel"/> - can be replaced by <xref linkend="libpq-PQcancel"/>. + can be replaced by <xref linkend="libpq-PQcancelBlocking"/>. <xref linkend="libpq-PQoidStatus"/> can be replaced by <xref linkend="libpq-PQoidValue"/>. </para> diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 088592deb16..9fbd3d34074 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 d69b8f9f9f4..6bbd126bafe 100644 --- a/src/interfaces/libpq/fe-cancel.c +++ b/src/interfaces/libpq/fe-cancel.c @@ -23,6 +23,17 @@ /* + * 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. */ @@ -42,6 +53,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. * * A copy is needed to be able to cancel a running query from a different @@ -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 d4e10a0c4f3..01e49c6975e 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; + } } @@ -924,6 +933,45 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions) } /* + * 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 * * Internal subroutine to set up connection parameters given an already- @@ -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; @@ -3227,6 +3290,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. */ startpacket = pqBuildStartupPacket3(conn, &packetlen, @@ -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); @@ -4496,6 +4589,13 @@ 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 2c06044a75e..09b485bd2bc 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 3abcd180d6d..9c05f11a6e9 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 c6c7b1c3a17..1fe15ee8899 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 a3052a181d1..aa7a25b8f8c 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 |