#include "copy.h"
#include "crosstabview.h"
#include "describe.h"
+#include "fe_utils/cancel.h"
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "help.h"
#include "common/logging.h"
#include "copy.h"
#include "crosstabview.h"
+#include "fe_utils/cancel.h"
#include "fe_utils/mbprint.h"
#include "fe_utils/string_utils.h"
#include "portability/instr_time.h"
* Code to support query cancellation
*
* Before we start a query, we enable the SIGINT signal catcher to send a
- * cancel request to the backend. 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 win32, 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.
+ * cancel request to the backend.
*
* SIGINT is supposed to abort all long-running psql operations, not only
* database queries. In most places, this is accomplished by checking
- * cancel_pressed during long-running loops. However, that won't work when
+ * CancelRequested during long-running loops. However, that won't work when
* blocked on user input (in readline() or fgets()). In those places, we
* set sigint_interrupt_enabled true while blocked, instructing the signal
* catcher to longjmp through sigint_interrupt_jmp. We assume readline and
- * fgets are coded to handle possible interruption. (XXX currently this does
- * not work on win32, so control-C is less useful there)
+ * fgets are coded to handle possible interruption.
+ *
+ * On Windows, currently this does not work, so control-C is less useful
+ * there, and the callback is just a no-op.
*/
volatile bool sigint_interrupt_enabled = false;
sigjmp_buf sigint_interrupt_jmp;
-static PGcancel *volatile cancelConn = NULL;
-
-#ifdef WIN32
-static CRITICAL_SECTION cancelConnLock;
-#endif
-
-/*
- * 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)
-
-
#ifndef WIN32
static void
-handle_sigint(SIGNAL_ARGS)
+psql_cancel_callback(void)
{
- int save_errno = errno;
- char errbuf[256];
-
/* if we are waiting for input, longjmp out of it */
if (sigint_interrupt_enabled)
{
}
/* else, set cancel flag to stop any long-running loops */
- cancel_pressed = true;
-
- /* and send QueryCancel if we are processing a database query */
- if (cancelConn != NULL)
- {
- if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
- write_stderr("Cancel request sent\n");
- else
- {
- write_stderr("Could not send cancel request: ");
- write_stderr(errbuf);
- }
- }
-
- errno = save_errno; /* just in case the write changed it */
+ CancelRequested = true;
}
-void
-setup_cancel_handler(void)
-{
- pqsignal(SIGINT, handle_sigint);
-}
-#else /* WIN32 */
+#else
-static BOOL WINAPI
-consoleHandler(DWORD dwCtrlType)
+static void
+psql_cancel_callback(void)
{
- char errbuf[256];
-
- if (dwCtrlType == CTRL_C_EVENT ||
- dwCtrlType == CTRL_BREAK_EVENT)
- {
- /*
- * Can't longjmp here, because we are in wrong thread :-(
- */
-
- /* set cancel flag to stop any long-running loops */
- cancel_pressed = true;
-
- /* and send QueryCancel if we are processing a database query */
- EnterCriticalSection(&cancelConnLock);
- if (cancelConn != NULL)
- {
- if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
- write_stderr("Cancel request sent\n");
- else
- {
- write_stderr("Could not send cancel request: ");
- write_stderr(errbuf);
- }
- }
- LeaveCriticalSection(&cancelConnLock);
-
- return TRUE;
- }
- else
- /* Return FALSE for any signals not being handled */
- return FALSE;
+ /* nothing to do here */
}
+#endif /* !WIN32 */
+
void
-setup_cancel_handler(void)
+psql_setup_cancel_handler(void)
{
- InitializeCriticalSection(&cancelConnLock);
-
- SetConsoleCtrlHandler(consoleHandler, TRUE);
+ setup_cancel_handler(psql_cancel_callback);
}
-#endif /* WIN32 */
/* ConnectionUp
-/*
- * SetCancelConn
- *
- * Set cancelConn to point to the current database connection.
- */
-void
-SetCancelConn(void)
-{
- 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(pset.db);
-
-#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
-}
-
/*
* AcceptResult
return NULL;
}
- SetCancelConn();
+ SetCancelConn(pset.db);
res = PQexec(pset.db, query);
return 0;
}
- SetCancelConn();
+ SetCancelConn(pset.db);
if (pset.timing)
INSTR_TIME_SET_CURRENT(before);
* consumed. The user's intention, though, is to cancel the entire watch
* process, so detect a sent cancellation request and exit in this case.
*/
- if (cancel_pressed)
+ if (CancelRequested)
{
PQclear(res);
return 0;
{
const char *query = PQgetvalue(result, r, c);
- /* Abandon execution if cancel_pressed */
- if (cancel_pressed)
+ /* Abandon execution if CancelRequested */
+ if (CancelRequested)
goto loop_exit;
/*
FILE *copystream;
PGresult *copy_result;
- SetCancelConn();
+ SetCancelConn(pset.db);
if (result_status == PGRES_COPY_OUT)
{
bool need_close = false;
if (fgets(buf, sizeof(buf), stdin) != NULL)
if (buf[0] == 'x')
goto sendquery_cleanup;
- if (cancel_pressed)
+ if (CancelRequested)
goto sendquery_cleanup;
}
else if (pset.echo == PSQL_ECHO_QUERIES)
fflush(pset.logfile);
}
- SetCancelConn();
+ SetCancelConn(pset.db);
transaction_status = PQtransactionStatus(pset.db);
* writing things to the stream, we presume $PAGER has disappeared and
* stop bothering to pull down more data.
*/
- if (ntuples < fetch_count || cancel_pressed || flush_error ||
+ if (ntuples < fetch_count || CancelRequested || flush_error ||
ferror(fout))
break;
}
extern sigjmp_buf sigint_interrupt_jmp;
-extern void setup_cancel_handler(void);
-
-extern void SetCancelConn(void);
-extern void ResetCancelConn(void);
+extern void psql_setup_cancel_handler(void);
extern PGresult *PSQLexec(const char *query);
extern int PSQLexecWatch(const char *query, const printQueryOpt *opt);
#include "common.h"
#include "common/logging.h"
+#include "fe_utils/cancel.h"
#include "large_obj.h"
#include "settings.h"
if (!start_lo_xact("\\lo_export", &own_transaction))
return false;
- SetCancelConn();
+ SetCancelConn(NULL);
status = lo_export(pset.db, atooid(loid_arg), filename_arg);
ResetCancelConn();
if (!start_lo_xact("\\lo_import", &own_transaction))
return false;
- SetCancelConn();
+ SetCancelConn(NULL);
loid = lo_import(pset.db, filename_arg);
ResetCancelConn();
if (!start_lo_xact("\\lo_unlink", &own_transaction))
return false;
- SetCancelConn();
+ SetCancelConn(NULL);
status = lo_unlink(pset.db, loid);
ResetCancelConn();
exit(EXIT_BADCONN);
}
- setup_cancel_handler();
+ psql_setup_cancel_handler();
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
exit(1);
}
- setup_cancel_handler();
+ setup_cancel_handler(NULL);
if (alldb)
{
#define ERRCODE_UNDEFINED_TABLE "42P01"
-
-static PGcancel *volatile cancelConn = NULL;
-bool CancelRequested = false;
-
-#ifdef WIN32
-static CRITICAL_SECTION cancelConnLock;
-#endif
-
/*
* Provide strictly harmonized handling of --help and --version
* options.
_(PG_YESLETTER), _(PG_NOLETTER));
}
}
-
-/*
- * 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
-}
-
-#ifndef WIN32
-/*
- * Handle interrupt signals by canceling the current command, if a cancelConn
- * is set.
- */
-static void
-handle_sigint(SIGNAL_ARGS)
-{
- int save_errno = errno;
- char errbuf[256];
-
- /* Send QueryCancel if we are processing a database query */
- if (cancelConn != NULL)
- {
- if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
- {
- CancelRequested = true;
- fprintf(stderr, _("Cancel request sent\n"));
- }
- else
- fprintf(stderr, _("Could not send cancel request: %s"), errbuf);
- }
- else
- CancelRequested = true;
-
- errno = save_errno; /* just in case the write changed it */
-}
-
-void
-setup_cancel_handler(void)
-{
- pqsignal(SIGINT, handle_sigint);
-}
-#else /* WIN32 */
-
-/*
- * Console control handler for Win32. Note that the control handler will
- * execute on a *different thread* than the main one, so we need to do
- * proper locking around those structures.
- */
-static BOOL WINAPI
-consoleHandler(DWORD dwCtrlType)
-{
- char errbuf[256];
-
- if (dwCtrlType == CTRL_C_EVENT ||
- dwCtrlType == CTRL_BREAK_EVENT)
- {
- /* Send QueryCancel if we are processing a database query */
- EnterCriticalSection(&cancelConnLock);
- if (cancelConn != NULL)
- {
- if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
- {
- fprintf(stderr, _("Cancel request sent\n"));
- CancelRequested = true;
- }
- else
- fprintf(stderr, _("Could not send cancel request: %s"), errbuf);
- }
- else
- CancelRequested = true;
-
- LeaveCriticalSection(&cancelConnLock);
-
- return TRUE;
- }
- else
- /* Return FALSE for any signals not being handled */
- return FALSE;
-}
-
-void
-setup_cancel_handler(void)
-{
- InitializeCriticalSection(&cancelConnLock);
-
- SetConsoleCtrlHandler(consoleHandler, TRUE);
-}
-
-#endif /* WIN32 */
#define COMMON_H
#include "common/username.h"
+#include "fe_utils/cancel.h"
#include "getopt_long.h" /* pgrminclude ignore */
#include "libpq-fe.h"
#include "pqexpbuffer.h" /* pgrminclude ignore */
extern bool yesno_prompt(const char *question);
-extern void setup_cancel_handler(void);
-
-extern void SetCancelConn(PGconn *conn);
-extern void ResetCancelConn(void);
-
-
#endif /* COMMON_H */
exit(1);
}
- setup_cancel_handler();
+ setup_cancel_handler(NULL);
if (alldb)
{
/* allow 'and_analyze' with 'analyze_only' */
}
- setup_cancel_handler();
+ setup_cancel_handler(NULL);
/* Avoid opening extra connections. */
if (tbl_count && (concurrentCons > tbl_count))
override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
OBJS = \
+ cancel.o \
conditional.o \
mbprint.o \
print.o \
--- /dev/null
+/*------------------------------------------------------------------------
+ *
+ * Query cancellation support for frontend code
+ *
+ * Assorted utility functions to control query cancellation with signal
+ * handler for SIGINT.
+ *
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/fe-utils/cancel.c
+ *
+ *------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <unistd.h>
+
+#include "fe_utils/cancel.h"
+#include "fe_utils/connect.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)
+
+static PGcancel *volatile cancelConn = NULL;
+bool 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];
+
+ if (cancel_callback != NULL)
+ cancel_callback();
+
+ /* Send QueryCancel if we are processing a database query */
+ if (cancelConn != NULL)
+ {
+ if (PQcancel(cancelConn, errbuf, sizeof(errbuf)))
+ {
+ CancelRequested = true;
+ write_stderr(_("Cancel request sent\n"));
+ }
+ else
+ {
+ write_stderr(_("Could not send cancel request: "));
+ write_stderr(errbuf);
+ }
+ }
+ else
+ CancelRequested = true;
+
+ 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;
+ pqsignal(SIGINT, handle_sigint);
+}
+
+#else /* WIN32 */
+
+static BOOL WINAPI
+consoleHandler(DWORD dwCtrlType)
+{
+ char errbuf[256];
+
+ if (dwCtrlType == CTRL_C_EVENT ||
+ dwCtrlType == CTRL_BREAK_EVENT)
+ {
+ 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 request sent\n"));
+ CancelRequested = true;
+ }
+ else
+ {
+ write_stderr(_("Could not send cancel request: %s"));
+ write_stderr(errbuf);
+ }
+ }
+ else
+ CancelRequested = true;
+
+ 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;
+
+ InitializeCriticalSection(&cancelConnLock);
+
+ SetConsoleCtrlHandler(consoleHandler, TRUE);
+}
+
+#endif /* WIN32 */
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * Query cancellation support for frontend code
+ *
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/fe_utils/cancel.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef CANCEL_H
+#define CANCEL_H
+
+#include "libpq-fe.h"
+
+extern bool CancelRequested;
+
+extern void SetCancelConn(PGconn *conn);
+extern void ResetCancelConn(void);
+
+/*
+ * A callback can be optionally set up to be called at cancellation
+ * time.
+ */
+extern void setup_cancel_handler(void (*cancel_callback) (void));
+
+#endif /* CANCEL_H */
our @pgcommonbkndfiles = @pgcommonallfiles;
our @pgfeutilsfiles = qw(
- conditional.c mbprint.c print.c psqlscan.l psqlscan.c
+ cancel.c conditional.c mbprint.c print.c psqlscan.l psqlscan.c
simple_list.c string_utils.c recovery_gen.c);
$libpgport = $solution->AddProject('libpgport', 'lib', 'misc');