|
|
|
|
/*------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* Query cancellation support for frontend code
|
|
|
|
|
*
|
|
|
|
|
* Assorted utility functions to control query cancellation with signal
|
|
|
|
|
* handler for SIGINT.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Portions Copyright (c) 1996-2020, 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 "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)
|
|
|
|
|
|
Fix query cancellation handling in psql
The refactoring done in a4fd3aa for query cancellation has messed up
with the logic in psql by mixing CancelRequested and cancel_pressed,
breaking for example \watch. The former would be switched to true if a
cancellation request has been attempted and that it actually succeeded,
and the latter tracks if a cancellation attempt has been done.
This commit brings back the code of psql to a state consistent to what
it was before a4fd3aa, without giving up on the refactoring pieces
introduced. It should be actually possible to merge more both flags as
their concepts are close enough, however note that psql's --single-step
mode relies on cancel_pressed to be always set, so this requires more
careful analysis left for later.
While on it, fix the declarations of CancelRequested (in cancel.c) and
cancel_pressed (in psql) to be volatile sig_atomic_t. Previously,
both were declared as booleans, which should be fine on modern
platforms, but the C standard recommends the use of sig_atomic_t for
variables used in signal handlers. Note that since its introduction in
a1792320, CancelRequested declaration was not volatile.
Reported-by: Jeff Janes
Author: Michael Paquier
Discussion: https://postgr.es/m/CAMkU=1zpoUDGKqWKuMWkj7t-bOCaJDx0r=5te_-d0B2HVLABXg@mail.gmail.com
6 years ago
|
|
|
/*
|
|
|
|
|
* Contains all the information needed to cancel a query issued from
|
|
|
|
|
* a database connection to the backend.
|
|
|
|
|
*/
|
|
|
|
|
static PGcancel *volatile cancelConn = NULL;
|
Fix query cancellation handling in psql
The refactoring done in a4fd3aa for query cancellation has messed up
with the logic in psql by mixing CancelRequested and cancel_pressed,
breaking for example \watch. The former would be switched to true if a
cancellation request has been attempted and that it actually succeeded,
and the latter tracks if a cancellation attempt has been done.
This commit brings back the code of psql to a state consistent to what
it was before a4fd3aa, without giving up on the refactoring pieces
introduced. It should be actually possible to merge more both flags as
their concepts are close enough, however note that psql's --single-step
mode relies on cancel_pressed to be always set, so this requires more
careful analysis left for later.
While on it, fix the declarations of CancelRequested (in cancel.c) and
cancel_pressed (in psql) to be volatile sig_atomic_t. Previously,
both were declared as booleans, which should be fine on modern
platforms, but the C standard recommends the use of sig_atomic_t for
variables used in signal handlers. Note that since its introduction in
a1792320, CancelRequested declaration was not volatile.
Reported-by: Jeff Janes
Author: Michael Paquier
Discussion: https://postgr.es/m/CAMkU=1zpoUDGKqWKuMWkj7t-bOCaJDx0r=5te_-d0B2HVLABXg@mail.gmail.com
6 years ago
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* CancelRequested tracks if a cancellation request has completed after
|
|
|
|
|
* a signal interruption. Note that if cancelConn is not set, in short
|
|
|
|
|
* if SetCancelConn() was never called or if ResetCancelConn() freed
|
|
|
|
|
* the cancellation object, then CancelRequested is switched to true after
|
|
|
|
|
* all cancellation attempts.
|
|
|
|
|
*/
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
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 */
|