22
22
#include "port/pg_bswap.h"
23
23
24
24
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
+
25
36
/*
26
37
* pg_cancel (backing struct for PGcancel) stores all data necessary to send a
27
38
* cancel request.
@@ -41,6 +52,289 @@ struct pg_cancel
41
52
};
42
53
43
54
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
+
44
338
/*
45
339
* PQgetCancel: get a PGcancel structure corresponding to a connection.
46
340
*
@@ -145,7 +439,7 @@ optional_setsockopt(int fd, int protoid, int optid, int value)
145
439
146
440
147
441
/*
148
- * PQcancel: request query cancel
442
+ * PQcancel: old, non-encrypted, but signal-safe way of requesting query cancel
149
443
*
150
444
* The return value is true if the cancel request was successfully
151
445
* dispatched, false if not (in which case an error message is available).
0 commit comments