summaryrefslogtreecommitdiffstats
path: root/src/fe_utils/cancel.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fe_utils/cancel.c')
-rw-r--r--src/fe_utils/cancel.c246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/fe_utils/cancel.c b/src/fe_utils/cancel.c
new file mode 100644
index 0000000..d430866
--- /dev/null
+++ b/src/fe_utils/cancel.c
@@ -0,0 +1,246 @@
+/*------------------------------------------------------------------------
+ *
+ * Query cancellation support for frontend code
+ *
+ * Assorted utility functions to control query cancellation with signal
+ * handler for SIGINT.
+ *
+ *
+ * Portions Copyright (c) 1996-2022, 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 */