2222#include "port/pg_bswap.h"
2323
2424
25+ /*
26+ * pg_cancel_conn (backing struct for PGcancelConn) is a wrapper around a
27+ * PGconn to send cancellations using PQcancelBlocking and PQcancelStart.
28+ * This isn't just a typedef because we want the compiler to complain when a
29+ * PGconn is passed to a function that expects a PGcancelConn, and vice versa.
30+ */
31+ struct pg_cancel_conn
32+ {
33+ PGconn conn ;
34+ };
35+
2536/*
2637 * pg_cancel (backing struct for PGcancel) stores all data necessary to send a
2738 * cancel request.
@@ -41,6 +52,289 @@ struct pg_cancel
4152};
4253
4354
55+ /*
56+ * PQcancelCreate
57+ *
58+ * Create and return a PGcancelConn, which can be used to securely cancel a
59+ * query on the given connection.
60+ *
61+ * This requires either following the non-blocking flow through
62+ * PQcancelStart() and PQcancelPoll(), or the blocking PQcancelBlocking().
63+ */
64+ PGcancelConn *
65+ PQcancelCreate (PGconn * conn )
66+ {
67+ PGconn * cancelConn = pqMakeEmptyPGconn ();
68+ pg_conn_host originalHost ;
69+
70+ if (cancelConn == NULL )
71+ return NULL ;
72+
73+ /* Check we have an open connection */
74+ if (!conn )
75+ {
76+ libpq_append_conn_error (cancelConn , "passed connection was NULL" );
77+ return (PGcancelConn * ) cancelConn ;
78+ }
79+
80+ if (conn -> sock == PGINVALID_SOCKET )
81+ {
82+ libpq_append_conn_error (cancelConn , "passed connection is not open" );
83+ return (PGcancelConn * ) cancelConn ;
84+ }
85+
86+ /*
87+ * Indicate that this connection is used to send a cancellation
88+ */
89+ cancelConn -> cancelRequest = true;
90+
91+ if (!pqCopyPGconn (conn , cancelConn ))
92+ return (PGcancelConn * ) cancelConn ;
93+
94+ /*
95+ * Compute derived options
96+ */
97+ if (!pqConnectOptions2 (cancelConn ))
98+ return (PGcancelConn * ) cancelConn ;
99+
100+ /*
101+ * Copy cancellation token data from the original connnection
102+ */
103+ cancelConn -> be_pid = conn -> be_pid ;
104+ cancelConn -> be_key = conn -> be_key ;
105+
106+ /*
107+ * Cancel requests should not iterate over all possible hosts. The request
108+ * needs to be sent to the exact host and address that the original
109+ * connection used. So we manually create the host and address arrays with
110+ * a single element after freeing the host array that we generated from
111+ * the connection options.
112+ */
113+ pqReleaseConnHosts (cancelConn );
114+ cancelConn -> nconnhost = 1 ;
115+ cancelConn -> naddr = 1 ;
116+
117+ cancelConn -> connhost = calloc (cancelConn -> nconnhost , sizeof (pg_conn_host ));
118+ if (!cancelConn -> connhost )
119+ goto oom_error ;
120+
121+ originalHost = conn -> connhost [conn -> whichhost ];
122+ if (originalHost .host )
123+ {
124+ cancelConn -> connhost [0 ].host = strdup (originalHost .host );
125+ if (!cancelConn -> connhost [0 ].host )
126+ goto oom_error ;
127+ }
128+ if (originalHost .hostaddr )
129+ {
130+ cancelConn -> connhost [0 ].hostaddr = strdup (originalHost .hostaddr );
131+ if (!cancelConn -> connhost [0 ].hostaddr )
132+ goto oom_error ;
133+ }
134+ if (originalHost .port )
135+ {
136+ cancelConn -> connhost [0 ].port = strdup (originalHost .port );
137+ if (!cancelConn -> connhost [0 ].port )
138+ goto oom_error ;
139+ }
140+ if (originalHost .password )
141+ {
142+ cancelConn -> connhost [0 ].password = strdup (originalHost .password );
143+ if (!cancelConn -> connhost [0 ].password )
144+ goto oom_error ;
145+ }
146+
147+ cancelConn -> addr = calloc (cancelConn -> naddr , sizeof (AddrInfo ));
148+ if (!cancelConn -> connhost )
149+ goto oom_error ;
150+
151+ cancelConn -> addr [0 ].addr = conn -> raddr ;
152+ cancelConn -> addr [0 ].family = conn -> raddr .addr .ss_family ;
153+
154+ cancelConn -> status = CONNECTION_ALLOCATED ;
155+ return (PGcancelConn * ) cancelConn ;
156+
157+ oom_error :
158+ conn -> status = CONNECTION_BAD ;
159+ libpq_append_conn_error (cancelConn , "out of memory" );
160+ return (PGcancelConn * ) cancelConn ;
161+ }
162+
163+
164+ /*
165+ * PQcancelBlocking
166+ *
167+ * Send a cancellation request in a blocking fashion.
168+ * Returns 1 if successful 0 if not.
169+ */
170+ int
171+ PQcancelBlocking (PGcancelConn * cancelConn )
172+ {
173+ if (!PQcancelStart (cancelConn ))
174+ return 0 ;
175+ return pqConnectDBComplete (& cancelConn -> conn );
176+ }
177+
178+ /*
179+ * PQcancelStart
180+ *
181+ * Starts sending a cancellation request in a non-blocking fashion. Returns
182+ * 1 if successful 0 if not.
183+ */
184+ int
185+ PQcancelStart (PGcancelConn * cancelConn )
186+ {
187+ if (!cancelConn || cancelConn -> conn .status == CONNECTION_BAD )
188+ return 0 ;
189+
190+ if (cancelConn -> conn .status != CONNECTION_ALLOCATED )
191+ {
192+ libpq_append_conn_error (& cancelConn -> conn ,
193+ "cancel request is already being sent on this connection" );
194+ cancelConn -> conn .status = CONNECTION_BAD ;
195+ return 0 ;
196+ }
197+
198+ return pqConnectDBStart (& cancelConn -> conn );
199+ }
200+
201+ /*
202+ * PQcancelPoll
203+ *
204+ * Poll a cancel connection. For usage details see PQconnectPoll.
205+ */
206+ PostgresPollingStatusType
207+ PQcancelPoll (PGcancelConn * cancelConn )
208+ {
209+ PGconn * conn = & cancelConn -> conn ;
210+ int n ;
211+
212+ /*
213+ * We leave most of the connection establishement to PQconnectPoll, since
214+ * it's very similar to normal connection establishment. But once we get
215+ * to the CONNECTION_AWAITING_RESPONSE we need to start doing our own
216+ * thing.
217+ */
218+ if (conn -> status != CONNECTION_AWAITING_RESPONSE )
219+ {
220+ return PQconnectPoll (conn );
221+ }
222+
223+ /*
224+ * At this point we are waiting on the server to close the connection,
225+ * which is its way of communicating that the cancel has been handled.
226+ */
227+
228+ n = pqReadData (conn );
229+
230+ if (n == 0 )
231+ return PGRES_POLLING_READING ;
232+
233+ #ifndef WIN32
234+
235+ /*
236+ * If we receive an error report it, but only if errno is non-zero.
237+ * Otherwise we assume it's an EOF, which is what we expect from the
238+ * server.
239+ *
240+ * We skip this for Windows, because Windows is a bit special in its EOF
241+ * behaviour for TCP. Sometimes it will error with an ECONNRESET when
242+ * there is a clean connection closure. See these threads for details:
243+ * https://www.postgresql.org/message-id/flat/90b34057-4176-7bb0-0dbb-9822a5f6425b%40greiz-reinsdorf.de
244+ *
245+ * https://www.postgresql.org/message-id/flat/CA%2BhUKG%2BOeoETZQ%3DQw5Ub5h3tmwQhBmDA%3DnuNO3KG%3DzWfUypFAw%40mail.gmail.com
246+ *
247+ * PQcancel ignores such errors and reports success for the cancellation
248+ * anyway, so even if this is not always correct we do the same here.
249+ */
250+ if (n < 0 && errno != 0 )
251+ {
252+ conn -> status = CONNECTION_BAD ;
253+ return PGRES_POLLING_FAILED ;
254+ }
255+ #endif
256+
257+ /*
258+ * We don't expect any data, only connection closure. So if we strangely
259+ * do receive some data we consider that an error.
260+ */
261+ if (n > 0 )
262+ {
263+ libpq_append_conn_error (conn , "received unexpected response from server" );
264+ conn -> status = CONNECTION_BAD ;
265+ return PGRES_POLLING_FAILED ;
266+ }
267+
268+ /*
269+ * Getting here means that we received an EOF, which is what we were
270+ * expecting -- the cancel request has completed.
271+ */
272+ cancelConn -> conn .status = CONNECTION_OK ;
273+ resetPQExpBuffer (& conn -> errorMessage );
274+ return PGRES_POLLING_OK ;
275+ }
276+
277+ /*
278+ * PQcancelStatus
279+ *
280+ * Get the status of a cancel connection.
281+ */
282+ ConnStatusType
283+ PQcancelStatus (const PGcancelConn * cancelConn )
284+ {
285+ return PQstatus (& cancelConn -> conn );
286+ }
287+
288+ /*
289+ * PQcancelSocket
290+ *
291+ * Get the socket of the cancel connection.
292+ */
293+ int
294+ PQcancelSocket (const PGcancelConn * cancelConn )
295+ {
296+ return PQsocket (& cancelConn -> conn );
297+ }
298+
299+ /*
300+ * PQcancelErrorMessage
301+ *
302+ * Get the socket of the cancel connection.
303+ */
304+ char *
305+ PQcancelErrorMessage (const PGcancelConn * cancelConn )
306+ {
307+ return PQerrorMessage (& cancelConn -> conn );
308+ }
309+
310+ /*
311+ * PQcancelReset
312+ *
313+ * Resets the cancel connection, so it can be reused to send a new cancel
314+ * request.
315+ */
316+ void
317+ PQcancelReset (PGcancelConn * cancelConn )
318+ {
319+ pqClosePGconn (& cancelConn -> conn );
320+ cancelConn -> conn .status = CONNECTION_ALLOCATED ;
321+ cancelConn -> conn .whichhost = 0 ;
322+ cancelConn -> conn .whichaddr = 0 ;
323+ cancelConn -> conn .try_next_host = false;
324+ cancelConn -> conn .try_next_addr = false;
325+ }
326+
327+ /*
328+ * PQcancelFinish
329+ *
330+ * Closes and frees the cancel connection.
331+ */
332+ void
333+ PQcancelFinish (PGcancelConn * cancelConn )
334+ {
335+ PQfinish (& cancelConn -> conn );
336+ }
337+
44338/*
45339 * PQgetCancel: get a PGcancel structure corresponding to a connection.
46340 *
@@ -145,7 +439,7 @@ optional_setsockopt(int fd, int protoid, int optid, int value)
145439
146440
147441/*
148- * PQcancel: request query cancel
442+ * PQcancel: old, non-encrypted, but signal-safe way of requesting query cancel
149443 *
150444 * The return value is true if the cancel request was successfully
151445 * dispatched, false if not (in which case an error message is available).
0 commit comments