| /*------------------------------------------------------------------------ |
| * |
| * Query cancellation support for frontend code |
| * |
| * Assorted utility functions to control query cancellation with signal |
| * handler for SIGINT. |
| * |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * src/fe-utils/cancel.c |
| * |
| *------------------------------------------------------------------------ |
| */ |
| |
| #include "postgres_fe.h" |
| |
| #include <unistd.h> |
| |
| #include "common/connect.h" |
| #include "fe_utils/cancel.h" |
| #include "fe_utils/string_utils.h" |
| |
| |
| /* |
| * Write a simple string to stderr --- must be safe in a signal handler. |
| * We ignore the write() result since there's not much we could do about it. |
| * Certain compilers make that harder than it ought to be. |
| */ |
| #define write_stderr(str) \ |
| do { \ |
| const char *str_ = (str); \ |
| int rc_; \ |
| rc_ = write(fileno(stderr), str_, strlen(str_)); \ |
| (void) rc_; \ |
| } while (0) |
| |
| /* |
| * Contains all the information needed to cancel a query issued from |
| * a database connection to the backend. |
| */ |
| static PGcancel *volatile cancelConn = NULL; |
| |
| /* |
| * Predetermined localized error strings --- needed to avoid trying |
| * to call gettext() from a signal handler. |
| */ |
| static const char *cancel_sent_msg = NULL; |
| static const char *cancel_not_sent_msg = NULL; |
| |
| /* |
| * CancelRequested is set when we receive SIGINT (or local equivalent). |
| * There is no provision in this module for resetting it; but applications |
| * might choose to clear it after successfully recovering from a cancel. |
| * Note that there is no guarantee that we successfully sent a Cancel request, |
| * or that the request will have any effect if we did send it. |
| */ |
| volatile sig_atomic_t CancelRequested = false; |
| |
| #ifdef WIN32 |
| static CRITICAL_SECTION cancelConnLock; |
| #endif |
| |
| /* |
| * Additional callback for cancellations. |
| */ |
| static void (*cancel_callback) (void) = NULL; |
| |
| |
| /* |
| * SetCancelConn |
| * |
| * Set cancelConn to point to the current database connection. |
| */ |
| void |
| SetCancelConn(PGconn *conn) |
| { |
| PGcancel *oldCancelConn; |
| |
| #ifdef WIN32 |
| EnterCriticalSection(&cancelConnLock); |
| #endif |
| |
| /* Free the old one if we have one */ |
| oldCancelConn = cancelConn; |
| |
| /* be sure handle_sigint doesn't use pointer while freeing */ |
| cancelConn = NULL; |
| |
| if (oldCancelConn != NULL) |
| PQfreeCancel(oldCancelConn); |
| |
| cancelConn = PQgetCancel(conn); |
| |
| #ifdef WIN32 |
| LeaveCriticalSection(&cancelConnLock); |
| #endif |
| } |
| |
| /* |
| * ResetCancelConn |
| * |
| * Free the current cancel connection, if any, and set to NULL. |
| */ |
| void |
| ResetCancelConn(void) |
| { |
| PGcancel *oldCancelConn; |
| |
| #ifdef WIN32 |
| EnterCriticalSection(&cancelConnLock); |
| #endif |
| |
| oldCancelConn = cancelConn; |
| |
| /* be sure handle_sigint doesn't use pointer while freeing */ |
| cancelConn = NULL; |
| |
| if (oldCancelConn != NULL) |
| PQfreeCancel(oldCancelConn); |
| |
| #ifdef WIN32 |
| LeaveCriticalSection(&cancelConnLock); |
| #endif |
| } |
| |
| |
| /* |
| * Code to support query cancellation |
| * |
| * Note that sending the cancel directly from the signal handler is safe |
| * because PQcancel() is written to make it so. We use write() to report |
| * to stderr because it's better to use simple facilities in a signal |
| * handler. |
| * |
| * On Windows, the signal canceling happens on a separate thread, because |
| * that's how SetConsoleCtrlHandler works. The PQcancel function is safe |
| * for this (unlike PQrequestCancel). However, a CRITICAL_SECTION is required |
| * to protect the PGcancel structure against being changed while the signal |
| * thread is using it. |
| */ |
| |
| #ifndef WIN32 |
| |
| /* |
| * handle_sigint |
| * |
| * Handle interrupt signals by canceling the current command, if cancelConn |
| * is set. |
| */ |
| static void |
| handle_sigint(SIGNAL_ARGS) |
| { |
| int save_errno = errno; |
| char errbuf[256]; |
| |
| CancelRequested = true; |
| |
| if (cancel_callback != NULL) |
| cancel_callback(); |
| |
| /* Send QueryCancel if we are processing a database query */ |
| if (cancelConn != NULL) |
| { |
| if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) |
| { |
| write_stderr(cancel_sent_msg); |
| } |
| else |
| { |
| write_stderr(cancel_not_sent_msg); |
| write_stderr(errbuf); |
| } |
| } |
| |
| errno = save_errno; /* just in case the write changed it */ |
| } |
| |
| /* |
| * setup_cancel_handler |
| * |
| * Register query cancellation callback for SIGINT. |
| */ |
| void |
| setup_cancel_handler(void (*callback) (void)) |
| { |
| cancel_callback = callback; |
| cancel_sent_msg = _("Cancel request sent\n"); |
| cancel_not_sent_msg = _("Could not send cancel request: "); |
| |
| pqsignal(SIGINT, handle_sigint); |
| } |
| |
| #else /* WIN32 */ |
| |
| static BOOL WINAPI |
| consoleHandler(DWORD dwCtrlType) |
| { |
| char errbuf[256]; |
| |
| if (dwCtrlType == CTRL_C_EVENT || |
| dwCtrlType == CTRL_BREAK_EVENT) |
| { |
| CancelRequested = true; |
| |
| if (cancel_callback != NULL) |
| cancel_callback(); |
| |
| /* Send QueryCancel if we are processing a database query */ |
| EnterCriticalSection(&cancelConnLock); |
| if (cancelConn != NULL) |
| { |
| if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) |
| { |
| write_stderr(cancel_sent_msg); |
| } |
| else |
| { |
| write_stderr(cancel_not_sent_msg); |
| write_stderr(errbuf); |
| } |
| } |
| |
| LeaveCriticalSection(&cancelConnLock); |
| |
| return TRUE; |
| } |
| else |
| /* Return FALSE for any signals not being handled */ |
| return FALSE; |
| } |
| |
| void |
| setup_cancel_handler(void (*callback) (void)) |
| { |
| cancel_callback = callback; |
| cancel_sent_msg = _("Cancel request sent\n"); |
| cancel_not_sent_msg = _("Could not send cancel request: "); |
| |
| InitializeCriticalSection(&cancelConnLock); |
| |
| SetConsoleCtrlHandler(consoleHandler, TRUE); |
| } |
| |
| #endif /* WIN32 */ |