Branch data Line data Source code
1 : : /*------------------------------------------------------------------------
2 : : *
3 : : * Query cancellation support for frontend code
4 : : *
5 : : * This module provides SIGINT/Ctrl-C handling for frontend tools that need to
6 : : * cancel queries or interrupt other operations. It provides three
7 : : * independent mechanisms, any combination of which can be used by an
8 : : * application:
9 : : *
10 : : * 1. Server cancel query request -- When a query is running and the main
11 : : * thread is waiting for the result of that query in a blocking manner, we
12 : : * want SIGINT/Ctrl-C to cancel that query. This can be achieved by
13 : : * calling SetCancelConn() to register the connection that is (or will be)
14 : : * running the query, prior to waiting for the result. When SIGINT/Ctrl-C
15 : : * is received, a cancel request for this connection will then be sent from
16 : : * the signal handler (on Windows, from a separate thread). That in turn
17 : : * will then (assuming a co-operating server) cause the server to cancel
18 : : * the query and send an error to the waiting client on the main thread.
19 : : * The cancel connection is a process-wide global, so only one connection
20 : : * can be the cancel target at a time. ResetCancelConn() should be called
21 : : * to disarm the mechanism again after the blocking wait has completed.
22 : : *
23 : : * 2. CancelRequested flag -- The CancelRequested flag is set to true whenever
24 : : * SIGINT is received, and can be checked by the application at appropriate
25 : : * times. The primary use case for this is when the application code is
26 : : * not blocked (indefinitely), but needs to take an action when Ctrl-C is
27 : : * pressed, such as break out of a long running loop.
28 : : *
29 : : * 3. Signal handler callback -- A callback function can be registered with
30 : : * setup_cancel_handler(), which will then be called directly from the
31 : : * signal handler whenever SIGINT is received. Because it is called from a
32 : : * signal handler, the callback function must be async-signal-safe. On
33 : : * Windows, it is called from a separate signal-handling thread. NOTE: The
34 : : * callback is called AFTER setting CancelRequested but BEFORE sending the
35 : : * cancel request to the server (if armed by SetCancelConn). This means
36 : : * that if the callback exits or longjmps, no cancel request will be sent
37 : : * to the server.
38 : : *
39 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
40 : : * Portions Copyright (c) 1994, Regents of the University of California
41 : : *
42 : : * src/fe_utils/cancel.c
43 : : *
44 : : *------------------------------------------------------------------------
45 : : */
46 : :
47 : : #include "postgres_fe.h"
48 : :
49 : : #include <unistd.h>
50 : :
51 : : #include "common/connect.h"
52 : : #include "fe_utils/cancel.h"
53 : : #include "fe_utils/string_utils.h"
54 : :
55 : :
56 : : /*
57 : : * Write a simple string to stderr --- must be safe in a signal handler.
58 : : * We ignore the write() result since there's not much we could do about it.
59 : : * Certain compilers make that harder than it ought to be.
60 : : */
61 : : #define write_stderr(str) \
62 : : do { \
63 : : const char *str_ = (str); \
64 : : ssize_t rc_; \
65 : : rc_ = write(fileno(stderr), str_, strlen(str_)); \
66 : : (void) rc_; \
67 : : } while (0)
68 : :
69 : : /*
70 : : * Contains all the information needed to cancel a query issued from
71 : : * a database connection to the backend.
72 : : */
73 : : static PGcancel *volatile cancelConn = NULL;
74 : :
75 : : /*
76 : : * Predetermined localized error strings --- needed to avoid trying
77 : : * to call gettext() from a signal handler.
78 : : */
79 : : static const char *cancel_sent_msg = NULL;
80 : : static const char *cancel_not_sent_msg = NULL;
81 : :
82 : : /*
83 : : * CancelRequested is set when we receive SIGINT (or local equivalent).
84 : : * There is no provision in this module for resetting it; but applications
85 : : * might choose to clear it after successfully recovering from a cancel.
86 : : * Note that there is no guarantee that we successfully sent a Cancel request,
87 : : * or that the request will have any effect if we did send it.
88 : : */
89 : : volatile sig_atomic_t CancelRequested = false;
90 : :
91 : : #ifdef WIN32
92 : : static CRITICAL_SECTION cancelConnLock;
93 : : #endif
94 : :
95 : : /*
96 : : * Additional callback for cancellations.
97 : : */
98 : : static void (*cancel_callback) (void) = NULL;
99 : :
100 : :
101 : : /*
102 : : * SetCancelConn
103 : : *
104 : : * Set cancelConn to point to the current database connection.
105 : : */
106 : : void
107 : 302654 : SetCancelConn(PGconn *conn)
108 : : {
109 : : PGcancel *oldCancelConn;
110 : :
111 : : #ifdef WIN32
112 : : EnterCriticalSection(&cancelConnLock);
113 : : #endif
114 : :
115 : : /* Free the old one if we have one */
116 : 302654 : oldCancelConn = cancelConn;
117 : :
118 : : /* be sure handle_sigint doesn't use pointer while freeing */
119 : 302654 : cancelConn = NULL;
120 : :
121 [ + + ]: 302654 : if (oldCancelConn != NULL)
122 : 1130 : PQfreeCancel(oldCancelConn);
123 : :
124 : 302654 : cancelConn = PQgetCancel(conn);
125 : :
126 : : #ifdef WIN32
127 : : LeaveCriticalSection(&cancelConnLock);
128 : : #endif
129 : 302654 : }
130 : :
131 : : /*
132 : : * ResetCancelConn
133 : : *
134 : : * Free the current cancel connection, if any, and set to NULL.
135 : : */
136 : : void
137 : 302638 : ResetCancelConn(void)
138 : : {
139 : : PGcancel *oldCancelConn;
140 : :
141 : : #ifdef WIN32
142 : : EnterCriticalSection(&cancelConnLock);
143 : : #endif
144 : :
145 : 302638 : oldCancelConn = cancelConn;
146 : :
147 : : /* be sure handle_sigint doesn't use pointer while freeing */
148 : 302638 : cancelConn = NULL;
149 : :
150 [ + + ]: 302638 : if (oldCancelConn != NULL)
151 : 301479 : PQfreeCancel(oldCancelConn);
152 : :
153 : : #ifdef WIN32
154 : : LeaveCriticalSection(&cancelConnLock);
155 : : #endif
156 : 302638 : }
157 : :
158 : :
159 : : /*
160 : : * Code to support query cancellation
161 : : *
162 : : * Note that sending the cancel directly from the signal handler is safe
163 : : * because PQcancel() is written to make it so. We use write() to report
164 : : * to stderr because it's better to use simple facilities in a signal
165 : : * handler.
166 : : *
167 : : * On Windows, the signal canceling happens on a separate thread, because
168 : : * that's how SetConsoleCtrlHandler works. The PQcancel function is safe
169 : : * for this (unlike PQrequestCancel). However, a CRITICAL_SECTION is required
170 : : * to protect the PGcancel structure against being changed while the signal
171 : : * thread is using it.
172 : : */
173 : :
174 : : #ifndef WIN32
175 : :
176 : : /*
177 : : * handle_sigint
178 : : *
179 : : * Handle interrupt signals by canceling the current command, if cancelConn
180 : : * is set.
181 : : */
182 : : static void
183 : 1 : handle_sigint(SIGNAL_ARGS)
184 : : {
185 : : char errbuf[256];
186 : :
187 : 1 : CancelRequested = true;
188 : :
189 [ + - ]: 1 : if (cancel_callback != NULL)
190 : 1 : cancel_callback();
191 : :
192 : : /* Send QueryCancel if we are processing a database query */
193 [ + - ]: 1 : if (cancelConn != NULL)
194 : : {
195 [ + - ]: 1 : if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
196 : : {
197 : 1 : write_stderr(cancel_sent_msg);
198 : : }
199 : : else
200 : : {
201 : 0 : write_stderr(cancel_not_sent_msg);
202 : 0 : write_stderr(errbuf);
203 : : }
204 : : }
205 : 1 : }
206 : :
207 : : /*
208 : : * setup_cancel_handler
209 : : *
210 : : * Register query cancellation callback for SIGINT.
211 : : */
212 : : void
213 : 10609 : setup_cancel_handler(void (*query_cancel_callback) (void))
214 : : {
215 : 10609 : cancel_callback = query_cancel_callback;
216 : 10609 : cancel_sent_msg = _("Cancel request sent\n");
217 : 10609 : cancel_not_sent_msg = _("Could not send cancel request: ");
218 : :
219 : 10609 : pqsignal(SIGINT, handle_sigint);
220 : 10609 : }
221 : :
222 : : #else /* WIN32 */
223 : :
224 : : static BOOL WINAPI
225 : : consoleHandler(DWORD dwCtrlType)
226 : : {
227 : : char errbuf[256];
228 : :
229 : : if (dwCtrlType == CTRL_C_EVENT ||
230 : : dwCtrlType == CTRL_BREAK_EVENT)
231 : : {
232 : : CancelRequested = true;
233 : :
234 : : if (cancel_callback != NULL)
235 : : cancel_callback();
236 : :
237 : : /* Send QueryCancel if we are processing a database query */
238 : : EnterCriticalSection(&cancelConnLock);
239 : : if (cancelConn != NULL)
240 : : {
241 : : if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
242 : : {
243 : : write_stderr(cancel_sent_msg);
244 : : }
245 : : else
246 : : {
247 : : write_stderr(cancel_not_sent_msg);
248 : : write_stderr(errbuf);
249 : : }
250 : : }
251 : :
252 : : LeaveCriticalSection(&cancelConnLock);
253 : :
254 : : return TRUE;
255 : : }
256 : : else
257 : : /* Return FALSE for any signals not being handled */
258 : : return FALSE;
259 : : }
260 : :
261 : : void
262 : : setup_cancel_handler(void (*callback) (void))
263 : : {
264 : : cancel_callback = callback;
265 : : cancel_sent_msg = _("Cancel request sent\n");
266 : : cancel_not_sent_msg = _("Could not send cancel request: ");
267 : :
268 : : InitializeCriticalSection(&cancelConnLock);
269 : :
270 : : SetConsoleCtrlHandler(consoleHandler, TRUE);
271 : : }
272 : :
273 : : #endif /* WIN32 */
|