mirror of https://github.com/postgres/postgres
Originally, this code was duplicated in src/bin/psql/ and src/bin/scripts/, but it can be useful for other frontend applications, like pgbench. This refactoring offers the possibility to setup a custom callback which would get called in the signal handler for SIGINT or when the interruption console events happen on Windows. Author: Fabien Coelho, with contributions from Michael Paquier Reviewed-by: Álvaro Herrera, Ibrar Ahmed Discussion: https://postgr.es/m/alpine.DEB.2.21.1910311939430.27369@lancrepull/44/head
parent
c01ac6dcba
commit
a4fd3aa719
@ -0,0 +1,225 @@ |
||||
/*------------------------------------------------------------------------
|
||||
* |
||||
* 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 */ |
@ -0,0 +1,30 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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 */ |
Loading…
Reference in new issue