v1-0014-pg_dump-Refactor-connection-cancel-management.patch
text/x-patch
Filename: v1-0014-pg_dump-Refactor-connection-cancel-management.patch
Type: text/x-patch
Part: 13
Message:
pg_threads.h take II
Patch
Format: format-patch
Series: patch v1-0014
Subject: pg_dump: Refactor connection/cancel management.
| File | + | − |
|---|---|---|
| src/bin/pg_dump/parallel.c | 45 | 372 |
| src/bin/pg_dump/parallel.h | 3 | 1 |
| src/bin/pg_dump/pg_backup_db.c | 14 | 17 |
| src/bin/pg_dump/pg_backup_utils.c | 93 | 0 |
| src/bin/pg_dump/pg_backup_utils.h | 7 | 0 |
| src/bin/pg_dump/pg_dump.c | 2 | 0 |
| src/bin/pg_dump/pg_restore.c | 1 | 0 |
From ade047bbc6e252cbb4e959c73395c54b79e1758e Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Mon, 6 Jul 2026 19:23:29 +1200
Subject: [PATCH v1 14/14] pg_dump: Refactor connection/cancel management.
1. Now that we use threads on all OSes, move connect/disconnect out of
parallel workers. It's easier to think about if the leader sets their
resources up first and tears them down afterwards.
2. By using pg_signal_processor we can escape the constraints of
asynchronous signal handlers and run arbitrary code when you hit ^C.
Add a quit_handler that uses a cancel_set to track and cancel
connections. (Thanks to Jelte Fennema-Nio for this suggestion.)
XXX Experimental!
Discussion:
Reviewed-by:
---
src/bin/pg_dump/parallel.c | 417 ++++--------------------------
src/bin/pg_dump/parallel.h | 4 +-
src/bin/pg_dump/pg_backup_db.c | 31 +--
src/bin/pg_dump/pg_backup_utils.c | 93 +++++++
src/bin/pg_dump/pg_backup_utils.h | 7 +
src/bin/pg_dump/pg_dump.c | 2 +
src/bin/pg_dump/pg_restore.c | 1 +
7 files changed, 165 insertions(+), 390 deletions(-)
diff --git a/src/bin/pg_dump/parallel.c b/src/bin/pg_dump/parallel.c
index 4a095dd08e6..91f4b7f6f7b 100644
--- a/src/bin/pg_dump/parallel.c
+++ b/src/bin/pg_dump/parallel.c
@@ -107,14 +107,6 @@ struct ParallelSlot
int result;
};
-
-/* Structure to hold info passed to new threads. */
-typedef struct
-{
- ArchiveHandle *AH; /* leader database connection */
- ParallelSlot *slot; /* this worker's parallel slot */
-} WorkerInfo;
-
#ifdef WIN32
/* Windows implementation of pipe access */
@@ -142,26 +134,6 @@ typedef struct ShutdownInformation
static ShutdownInformation shutdown_info;
-/*
- * State info for signal handling.
- * We assume signal_info initializes to zeroes.
- *
- * On Unix, myAH is the leader DB connection in the leader process, and the
- * worker's own connection in worker processes. On Windows, we have only one
- * instance of signal_info, so myAH is the leader connection and the worker
- * connections must be dug out of pstate->parallelSlot[].
- */
-typedef struct DumpSignalInformation
-{
- ArchiveHandle *myAH; /* database connection to issue cancel for */
- ParallelState *pstate; /* parallel state, if any */
- bool handler_set; /* signal handler set up in this process? */
-} DumpSignalInformation;
-
-static volatile DumpSignalInformation signal_info;
-
-static pg_mtx_t signal_info_lock = PG_MTX_INIT;
-
/*
* 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.
@@ -178,17 +150,11 @@ static pg_mtx_t signal_info_lock = PG_MTX_INIT;
/* Pointer to each worker thread's ParallelSlot. NULL in main thread. */
static thread_local ParallelSlot *parallel_slot_thread_local;
-static bool parallel_init_done = false;
-
-
/* Local function prototypes */
static void archive_close_connection(int code, void *arg);
static void ShutdownWorkersHard(ParallelState *pstate);
static void WaitForTerminatingWorkers(ParallelState *pstate);
-static void set_cancel_handler(void);
-static void set_cancel_pstate(ParallelState *pstate);
-static void set_cancel_slot_archive(ParallelSlot *slot, ArchiveHandle *AH);
-static void RunWorker(ArchiveHandle *AH, ParallelSlot *slot);
+static void RunWorker(ParallelSlot *slot);
static int GetIdleWorker(ParallelState *pstate);
static void lockTableForWorker(ArchiveHandle *AH, TocEntry *te);
static void WaitForCommands(ArchiveHandle *AH, int pipefd[2]);
@@ -215,6 +181,8 @@ static char *readMessageFromPipe(int fd);
void
init_parallel_dump_utils(void)
{
+ static bool parallel_init_done = false;
+
if (!parallel_init_done)
{
#ifdef WIN32
@@ -226,7 +194,6 @@ init_parallel_dump_utils(void)
if (err != 0)
pg_fatal("%s() failed: error code %d", "WSAStartup", err);
#endif
-
parallel_init_done = true;
}
}
@@ -237,7 +204,7 @@ init_parallel_dump_utils(void)
bool
am_parallel_worker_thread(void)
{
- return parallel_init_done && parallel_slot_thread_local != NULL;
+ return parallel_slot_thread_local != NULL;
}
/*
@@ -291,20 +258,21 @@ archive_close_connection(int code, void *arg)
if (si->AHX)
DisconnectDatabase(si->AHX);
+
+ /* Close worker database connections. */
+ for (int i = 0; i < si->pstate->numWorkers; ++i)
+ if (si->pstate->parallelSlot[i].AH)
+ DisconnectDatabase(&si->pstate->parallelSlot[i].AH->public);
}
else
{
/*
- * We're a worker. Shut down our own DB connection if any. Also
- * close our communication sockets.
+ * We're a worker. Close our communication sockets.
*
* (Without this, if this is a premature exit, the leader would
* fail to detect it because there would be no EOF condition on
* the other end of the pipe.)
*/
- if (slot->AH)
- DisconnectDatabase(&(slot->AH->public));
-
closesocket(slot->pipeRevRead);
closesocket(slot->pipeRevWrite);
}
@@ -330,6 +298,8 @@ ShutdownWorkersHard(ParallelState *pstate)
{
int i;
+ Assert(!am_parallel_worker_thread());
+
/*
* Close our write end of the sockets so that any workers waiting for
* commands know they can exit. (Note: some of the pipeWrite fields might
@@ -339,21 +309,8 @@ ShutdownWorkersHard(ParallelState *pstate)
for (i = 0; i < pstate->numWorkers; i++)
closesocket(pstate->parallelSlot[i].pipeWrite);
- /*
- * Force early termination of any commands currently in progress. Send
- * query cancels directly to the workers' backends. Use a critical
- * section to ensure worker threads don't change state.
- */
- pg_mtx_lock(&signal_info_lock);
- for (i = 0; i < pstate->numWorkers; i++)
- {
- ArchiveHandle *AH = pstate->parallelSlot[i].AH;
- char errbuf[1];
-
- if (AH != NULL && AH->connCancel != NULL)
- (void) PQcancel(AH->connCancel, errbuf, sizeof(errbuf));
- }
- pg_mtx_unlock(&signal_info_lock);
+ /* Force early termination of any commands currently in progress. */
+ quit_handler_cancel_and_remove_all_connections();
/* Now wait for them to terminate. */
WaitForTerminatingWorkers(pstate);
@@ -365,6 +322,8 @@ ShutdownWorkersHard(ParallelState *pstate)
static void
WaitForTerminatingWorkers(ParallelState *pstate)
{
+ Assert(!am_parallel_worker_thread());
+
for (int i = 0; i < pstate->numWorkers; ++i)
{
ParallelSlot *slot = &pstate->parallelSlot[i];
@@ -379,270 +338,20 @@ WaitForTerminatingWorkers(ParallelState *pstate)
}
}
-
-/*
- * Code for responding to cancel interrupts (SIGINT, control-C, etc)
- *
- * This doesn't quite belong in this module, but it needs access to the
- * ParallelState data, so there's not really a better place either.
- *
- * When we get a cancel interrupt, we could just die, but in pg_restore that
- * could leave a SQL command (e.g., CREATE INDEX on a large table) running
- * for a long time. Instead, we try to send a cancel request and then die.
- * pg_dump probably doesn't really need this, but we might as well use it
- * there too. Note that sending the cancel directly from the signal handler
- * is safe because PQcancel() is written to make it so.
- */
-
-/*
- * Unix: runs in a signal handler (async-signal-safe operations only).
- *
- * Windows: runs in a system-provided thread and signal_info_lock has been
- * acquired by the caller.
- *
- * The caller will exit after this function returns.
- */
-static void
-handle_async_cancellation(void)
-{
- char errbuf[1];
- int null_device;
-
- /*
- * Report we're quitting, using nothing more complicated than write(2).
- */
- if (progname)
- {
- write_stderr(progname);
- write_stderr(": ");
- }
- write_stderr("terminated by user\n");
-
- /*
- * Atomically replace STDERR_FILENO with the null device, to swallow
- * future write() and fwrite() output to that descriptor. This prevents
- * worker threads from reporting cancellation-related errors, which might
- * clutter the user's screen if they manage arrive before the whole
- * process exits and terminates them. Ignore errors.
- */
- if ((null_device = open(DEVNULL, O_WRONLY, 0)) >= 0)
- dup2(null_device, STDERR_FILENO);
-
- /*
- * If in parallel mode, send QueryCancel to worker threads' connected
- * backends. Do this before canceling the main transaction, else we might
- * get invalid-snapshot errors reported before we can stop the workers.
- * Ignore errors, there's not much we can do about them anyway.
- */
- if (signal_info.pstate != NULL)
- {
- for (int i = 0; i < signal_info.pstate->numWorkers; i++)
- {
- ParallelSlot *slot = &(signal_info.pstate->parallelSlot[i]);
- ArchiveHandle *AH = slot->AH;
-
- if (AH != NULL && AH->connCancel != NULL)
- PQcancel(AH->connCancel, errbuf, sizeof(errbuf));
- }
- }
-
- /*
- * Send QueryCancel to leader connection, if enabled. Ignore errors,
- * there's not much we can do about them anyway.
- */
- if (signal_info.myAH != NULL && signal_info.myAH->connCancel != NULL)
- PQcancel(signal_info.myAH->connCancel, errbuf, sizeof(errbuf));
-}
-
-#ifndef WIN32
-
-/*
- * Signal handler (Unix only)
- */
-static void
-sigTermHandler(int signo)
-{
- handle_async_cancellation();
-
- /*
- * And die, using _exit() not exit() because the latter will invoke atexit
- * handlers that can fail if we interrupted related code.
- */
- _exit(1);
-}
-
-/*
- * Enable cancel interrupt handler, if not already done.
- */
-static void
-set_cancel_handler(void)
-{
- /*
- * When forking, signal_info.handler_set will propagate into the new
- * process, but that's fine because the signal handler state does too.
- */
- if (!signal_info.handler_set)
- {
- struct sigaction sa = {0};
-
- signal_info.handler_set = true;
-
- /*
- * While any of these signals is handled, block all three to avoid
- * possible repeat cancellation.
- */
- sigemptyset(&sa.sa_mask);
- sigaddset(&sa.sa_mask, SIGINT);
- sigaddset(&sa.sa_mask, SIGTERM);
- sigaddset(&sa.sa_mask, SIGQUIT);
-
- sa.sa_handler = sigTermHandler;
-
- sigaction(SIGINT, &sa, NULL);
- sigaction(SIGTERM, &sa, NULL);
- sigaction(SIGQUIT, &sa, NULL);
- }
-}
-
-#else /* WIN32 */
-
-/*
- * Console interrupt handler --- runs in a newly-started thread.
- *
- * After stopping other threads and sending cancel requests on all open
- * connections, we return FALSE which will allow the default ExitProcess()
- * action to be taken.
- */
-static BOOL WINAPI
-consoleHandler(DWORD dwCtrlType)
-{
- if (dwCtrlType == CTRL_C_EVENT ||
- dwCtrlType == CTRL_BREAK_EVENT)
- {
- pg_mtx_lock(&signal_info_lock);
- handle_async_cancellation();
- pg_mtx_unlock(&signal_info_lock);
- }
-
- /* Always return FALSE to allow signal handling to continue */
- return FALSE;
-}
-
-/*
- * Enable cancel interrupt handler, if not already done.
- */
-static void
-set_cancel_handler(void)
-{
- if (!signal_info.handler_set)
- {
- signal_info.handler_set = true;
-
- SetConsoleCtrlHandler(consoleHandler, TRUE);
- }
-}
-
-#endif /* WIN32 */
-
-
-/*
- * set_archive_cancel_info
- *
- * Fill AH->connCancel with cancellation info for the specified database
- * connection; or clear it if conn is NULL.
- */
-void
-set_archive_cancel_info(ArchiveHandle *AH, PGconn *conn)
-{
- PGcancel *oldConnCancel;
-
- /*
- * Activate the interrupt handler if we didn't yet in this process. On
- * Windows, this also initializes signal_info_lock; therefore it's
- * important that this happen at least once before we fork off any
- * threads.
- */
- set_cancel_handler();
-
- /*
- * On Unix, we assume that storing a pointer value is atomic with respect
- * to any possible signal interrupt. On Windows, the signal acquires this
- * lock.
- */
- pg_mtx_lock(&signal_info_lock);
-
- /* Free the old one if we have one */
- oldConnCancel = AH->connCancel;
- /* be sure interrupt handler doesn't use pointer while freeing */
- AH->connCancel = NULL;
-
- if (oldConnCancel != NULL)
- PQfreeCancel(oldConnCancel);
-
- /* Set the new one if specified */
- if (conn)
- AH->connCancel = PQgetCancel(conn);
-
- /*
- * Set the main thread's myAH, unless we're in a worker. Workers set
- * their per-slot AH in RunWorker().
- */
- if (parallel_slot_thread_local == NULL)
- signal_info.myAH = AH;
-
- pg_mtx_unlock(&signal_info_lock);
-}
-
-/*
- * set_cancel_pstate
- *
- * Set signal_info.pstate to point to the specified ParallelState, if any.
- * We need this mainly to have an interlock against Windows signal thread.
- */
-static void
-set_cancel_pstate(ParallelState *pstate)
-{
- pg_mtx_lock(&signal_info_lock);
- signal_info.pstate = pstate;
- pg_mtx_unlock(&signal_info_lock);
-}
-
-/*
- * set_cancel_slot_archive
- *
- * Set ParallelSlot's AH field to point to the specified archive, if any.
- * We need this mainly to have an interlock against Windows signal thread.
- */
-static void
-set_cancel_slot_archive(ParallelSlot *slot, ArchiveHandle *AH)
-{
- pg_mtx_lock(&signal_info_lock);
- slot->AH = AH;
- pg_mtx_unlock(&signal_info_lock);
-}
-
/*
* This function is called to set up and run a worker process. Caller should
* exit the thread upon return.
*/
static void
-RunWorker(ArchiveHandle *AH, ParallelSlot *slot)
+RunWorker(ParallelSlot *slot)
{
+ ArchiveHandle *AH = slot->AH;
int pipefd[2];
/* fetch child ends of pipes */
pipefd[PIPE_READ] = slot->pipeRevRead;
pipefd[PIPE_WRITE] = slot->pipeRevWrite;
- /*
- * Clone the archive so that we have our own state to work with, and in
- * particular our own database connection.
- */
- AH = CloneArchive(AH);
-
- /* Remember cloned archive where signal handler can find it */
- set_cancel_slot_archive(slot, AH);
-
/*
* Call the setup worker function that's defined in the ArchiveHandle.
*/
@@ -652,30 +361,18 @@ RunWorker(ArchiveHandle *AH, ParallelSlot *slot)
* Execute commands until done.
*/
WaitForCommands(AH, pipefd);
-
- /*
- * Disconnect from database and clean up.
- */
- set_cancel_slot_archive(slot, NULL);
- DisconnectDatabase(&(AH->public));
- DeCloneArchive(AH);
}
static int
worker_thread_main(void *argument)
{
- WorkerInfo *wi = argument;
- ArchiveHandle *AH = wi->AH;
- ParallelSlot *slot = wi->slot;
-
- /* Don't need WorkerInfo anymore */
- free(wi);
+ ParallelSlot *slot = argument;
/* For getThreadLocalPQExpBuffer() and am_parallel_worker_thread(). */
parallel_slot_thread_local = slot;
/* Run the worker ... */
- RunWorker(AH, slot);
+ RunWorker(slot);
/* Exit the thread */
return 0;
@@ -707,38 +404,34 @@ ParallelBackupStart(ArchiveHandle *AH)
pstate->parallelSlot =
pg_malloc0_array(ParallelSlot, pstate->numWorkers);
- /* Make per-thread PQExpBuffer object for getThreadLocalPQExpBuffer(). */
for (int i = 0; i < pstate->numWorkers; ++i)
+ {
+ ArchiveHandle *worker_ah;
+
+ /* Make per-thread PQExpBuffer. */
if (!(pstate->parallelSlot[i].buffer = createPQExpBuffer()))
pg_fatal("could not allocate PQExpBuffer");
+ /* Make per-thread archive clone with its own connection. */
+ worker_ah = CloneArchive(AH);
+ worker_ah->connCancel = PQgetCancel(worker_ah->connection);
+ if (!worker_ah->connCancel)
+ pg_fatal("could not create cancel connection");
+ pstate->parallelSlot[i].AH = worker_ah;
+ }
+
/* Make fmtId() and fmtQualifiedId() use per-thread buffer. */
getLocalPQExpBuffer = getThreadLocalPQExpBuffer;
/*
* Set the pstate in shutdown_info, to tell the exit handler that it must
- * clean up workers as well as the main database connection. But we don't
- * set this in signal_info yet, because we don't want child processes to
- * inherit non-NULL signal_info.pstate.
+ * clean up workers as well as the main database connection.
*/
shutdown_info.pstate = pstate;
- /*
- * Temporarily disable query cancellation on the leader connection. This
- * ensures that child processes won't inherit valid AH->connCancel
- * settings and thus won't try to issue cancels against the leader's
- * connection. No harm is done if we fail while it's disabled, because
- * the leader connection is idle at this point anyway.
- */
- set_archive_cancel_info(AH, NULL);
-
- /* Ensure stdio state is quiesced before forking */
- fflush(NULL);
-
/* Create desired number of workers */
for (int i = 0; i < pstate->numWorkers; i++)
{
- WorkerInfo *wi;
ParallelSlot *slot = &pstate->parallelSlot[i];
int pipeMW[2],
pipeWM[2];
@@ -754,41 +447,14 @@ ParallelBackupStart(ArchiveHandle *AH)
slot->pipeRevRead = pipeMW[PIPE_READ];
slot->pipeRevWrite = pipeWM[PIPE_WRITE];
- /* Create transient structure to pass args to worker function */
- wi = pg_malloc_object(WorkerInfo);
-
- wi->AH = AH;
- wi->slot = slot;
-
if (pg_thrd_create(&slot->thread,
worker_thread_main,
- wi) != pg_thrd_success)
+ slot) != pg_thrd_success)
pg_fatal("could not start worker thread");
slot->workerStatus = WRKR_IDLE;
}
- /*
- * Having started the workers, disable SIGPIPE so that the process isn't
- * kill if the leader tries to send a command to a dead worker.
- */
-#ifndef WIN32
- pqsignal(SIGPIPE, PG_SIG_IGN);
-#endif
-
- /*
- * Re-establish query cancellation on the leader connection.
- */
- set_archive_cancel_info(AH, AH->connection);
-
- /*
- * Tell the cancel signal handler to forward signals to worker processes,
- * too. (As with query cancel, we did not need this earlier because the
- * workers have not yet been given anything to do; if we die before this
- * point, any already-started workers will see EOF and quit promptly.)
- */
- set_cancel_pstate(pstate);
-
return pstate;
}
@@ -820,12 +486,19 @@ ParallelBackupEnd(ArchiveHandle *AH, ParallelState *pstate)
* use it; and likewise unlink from signal_info.
*/
shutdown_info.pstate = NULL;
- set_cancel_pstate(NULL);
- /* Release state (mere neatnik-ism, since we're about to terminate) */
+ /* Release per-thread resources. */
for (int i = 0; i < pstate->numWorkers; ++i)
- if (pstate->parallelSlot[i].buffer)
- destroyPQExpBuffer(pstate->parallelSlot[i].buffer);
+ {
+ ParallelSlot *slot = &pstate->parallelSlot[i];
+
+ destroyPQExpBuffer(slot->buffer);
+ PQfreeCancel(slot->AH->connCancel);
+ DisconnectDatabase(&slot->AH->public);
+ DeCloneArchive(slot->AH);
+ }
+
+ /* Release state (mere neatnik-ism, since we're about to terminate) */
free(pstate->te);
free(pstate->parallelSlot);
free(pstate);
diff --git a/src/bin/pg_dump/parallel.h b/src/bin/pg_dump/parallel.h
index 06764eefcac..d3b167d53cd 100644
--- a/src/bin/pg_dump/parallel.h
+++ b/src/bin/pg_dump/parallel.h
@@ -77,6 +77,8 @@ extern void DispatchJobForTocEntry(ArchiveHandle *AH,
void *callback_data);
extern void ParallelBackupEnd(ArchiveHandle *AH, ParallelState *pstate);
-extern void set_archive_cancel_info(ArchiveHandle *AH, PGconn *conn);
+extern void quit_cancel_set_add(PGconn *connection);
+extern void quit_cancel_set_remove(PGconn *connection);
+extern void quit_cancel_set_cancel_and_remove(PGconn *connection);
#endif /* PG_DUMP_PARALLEL_H */
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
index ec0ddf1d718..928adcde03a 100644
--- a/src/bin/pg_dump/pg_backup_db.c
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -114,6 +114,8 @@ ConnectDatabaseAhx(Archive *AHX,
trivalue prompt_password;
char *password;
+ Assert(!am_parallel_worker_thread());
+
if (AH->connection)
pg_fatal("already connected to a database");
@@ -153,7 +155,7 @@ ConnectDatabaseAhx(Archive *AHX,
PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
/* arrange for SIGINT to issue a query cancel on this connection */
- set_archive_cancel_info(AH, AH->connection);
+ quit_handler_add_connection(AH->connection);
}
/*
@@ -164,26 +166,21 @@ void
DisconnectDatabase(Archive *AHX)
{
ArchiveHandle *AH = (ArchiveHandle *) AHX;
- char errbuf[1];
+
+ Assert(!am_parallel_worker_thread());
if (!AH->connection)
return;
- if (AH->connCancel)
- {
- /*
- * If we have an active query, send a cancel before closing, ignoring
- * any errors. This is of no use for a normal exit, but might be
- * helpful during pg_fatal().
- */
- if (PQtransactionStatus(AH->connection) == PQTRANS_ACTIVE)
- (void) PQcancel(AH->connCancel, errbuf, sizeof(errbuf));
-
- /*
- * Prevent signal handler from sending a cancel after this.
- */
- set_archive_cancel_info(AH, NULL);
- }
+ /*
+ * If we have an active query, send a cancel before closing, ignoring any
+ * errors. This is of no use for a normal exit, but might be helpful
+ * during pg_fatal().
+ */
+ if (PQtransactionStatus(AH->connection) == PQTRANS_ACTIVE)
+ quit_handler_cancel_and_remove_connection(AH->connection);
+ else
+ quit_handler_remove_connection(AH->connection);
PQfinish(AH->connection);
AH->connection = NULL;
diff --git a/src/bin/pg_dump/pg_backup_utils.c b/src/bin/pg_dump/pg_backup_utils.c
index b545a4a5439..ab3551f0302 100644
--- a/src/bin/pg_dump/pg_backup_utils.c
+++ b/src/bin/pg_dump/pg_backup_utils.c
@@ -13,9 +13,15 @@
*/
#include "postgres_fe.h"
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
#include "parallel.h"
#include "pg_backup_utils.h"
#include "port/pg_threads.h"
+#include "port/pg_signal_processor.h"
+#include "fe_utils/cancel_set.h"
/* Globals exported by this file */
const char *progname = NULL;
@@ -30,6 +36,8 @@ static struct
static int on_exit_nicely_index;
+static cancel_set *quit_handler_connections;
+
/*
* Parse a --section=foo command line argument.
*
@@ -58,6 +66,91 @@ set_dump_section(const char *arg, int *dumpSections)
}
}
+/*
+ * Code for responding to cancel interrupts (SIGINT, control-C, etc)
+ *
+ * When we get a cancel interrupt, we could just die, but in pg_restore that
+ * could leave a SQL command (e.g., CREATE INDEX on a large table) running
+ * for a long time. Instead, we try to send a cancel request and then die.
+ * pg_dump probably doesn't really need this, but we might as well use it
+ * there too.
+ *
+ * This runs synchronously and serially on a thread, so it is not subject to
+ * the usual async-signal-safety requirements. The cancel_set API is
+ * thread-safe.
+ */
+static void
+quit_handler(int signo)
+{
+ int null_device;
+
+ fprintf(stderr, "%s: terminated by user\n", progname);
+
+ /*
+ * Atomically replace STDERR_FILENO with the null device, to swallow
+ * future write() and fwrite() output to that descriptor. This prevents
+ * worker threads from reporting cancellation-related errors, which might
+ * clutter the user's screen if they manage arrive before the whole
+ * process exits and terminates them. Ignore errors.
+ *
+ * XXX TODO:
+ * https://www.postgresql.org/message-id/flat/CA%2BhUKGJgO%3Do-vLFahGdR2WesuX3h1-0j%3Da8z72fChc-MG1Hveg%40mail.gmail.com
+ */
+ if ((null_device = open(DEVNULL, O_WRONLY, 0)) >= 0)
+ dup2(null_device, STDERR_FILENO);
+
+ /* Send cancel on all connections. */
+ cancel_set_cancel_all(quit_handler_connections);
+
+ _exit(1);
+}
+
+/*
+ * Initialize SIGINT, SIGQUIT/SIGBREAK, SIGTERM handlers.
+ */
+void
+quit_handler_setup(void)
+{
+ if (!(quit_handler_connections = cancel_set_alloc()))
+ pg_fatal("out of memory");
+
+ /* Handle ^C etc in serial signal processing thread. */
+ pg_signal_processor_set_serial_handler(SIGINT, quit_handler);
+ pg_signal_processor_set_serial_handler(SIGTERM, quit_handler);
+#ifndef WIN32
+ pg_signal_processor_set_serial_handler(SIGQUIT, quit_handler);
+#else
+ pg_signal_processor_set_serial_handler(SIGBREAK, quit_handler);
+#endif
+
+ if (pg_signal_processor_start() != 0)
+ pg_fatal("%s() failed: %m", "pg_signal_processor_start");
+}
+
+void
+quit_handler_add_connection(PGconn *connection)
+{
+ if (!cancel_set_add(quit_handler_connections, connection))
+ pg_fatal("out of memory");
+}
+
+void
+quit_handler_remove_connection(PGconn *connection)
+{
+ cancel_set_remove(quit_handler_connections, connection);
+}
+
+void
+quit_handler_cancel_and_remove_connection(PGconn *connection)
+{
+ cancel_set_cancel_and_remove(quit_handler_connections, connection);
+}
+
+void
+quit_handler_cancel_and_remove_all_connections(void)
+{
+ cancel_set_cancel_and_remove_all(quit_handler_connections);
+}
/* Register a callback to be run when exit_nicely is invoked. */
void
diff --git a/src/bin/pg_dump/pg_backup_utils.h b/src/bin/pg_dump/pg_backup_utils.h
index 9e98ed11619..23c2dc650aa 100644
--- a/src/bin/pg_dump/pg_backup_utils.h
+++ b/src/bin/pg_dump/pg_backup_utils.h
@@ -16,6 +16,7 @@
#define PG_BACKUP_UTILS_H
#include "common/logging.h"
+#include "libpq-fe.h"
/* bits returned by set_dump_section */
#define DUMP_PRE_DATA 0x01
@@ -31,6 +32,12 @@ extern void set_dump_section(const char *arg, int *dumpSections);
extern void on_exit_nicely(on_exit_nicely_callback function, void *arg);
pg_noreturn extern void exit_nicely(int code);
+extern void quit_handler_setup(void);
+extern void quit_handler_add_connection(PGconn *connection);
+extern void quit_handler_remove_connection(PGconn *connection);
+extern void quit_handler_cancel_and_remove_connection(PGconn *connection);
+extern void quit_handler_cancel_and_remove_all_connections(void);
+
/* In pg_dump, we modify pg_fatal to call exit_nicely instead of exit */
#undef pg_fatal
#define pg_fatal(...) do { \
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f67daf85911..54c4b69e209 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -962,6 +962,8 @@ main(int argc, char **argv)
if (archiveFormat != archDirectory && numWorkers > 1)
pg_fatal("parallel backup only supported by the directory format");
+ quit_handler_setup();
+
/* Open the output file */
fout = CreateArchive(filename, archiveFormat, compression_spec,
dosync, archiveMode, setupDumpWorker, sync_method);
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 84b8d410c9e..00c6bfb9e02 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -150,6 +150,7 @@ main(int argc, char **argv)
pg_logging_set_level(PG_LOG_WARNING);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_dump"));
+ quit_handler_setup();
init_parallel_dump_utils();
opts = NewRestoreOptions();
--
2.47.3