global / super barriers (for checksums)
Hi,
Magnus cornered me at pgconf.eu and asked me whether I could prototype
the "barriers" I'd been talking about in the online checksumming thread.
The problem there was to make sure that all processes, backends and
auxiliary processes have seen the new state of checksums being enabled,
and aren't currently in the process of writing a new page out.
The current prototype solves that by requiring a restart, but that
strikes me as a far too large hammer.
The attached patch introduces "global barriers" (name was invented in a
overcrowded hotel lounge, so ...), which allow to wait for such a change
to be absorbed by all backends.
I've only tested the code with gdb, but that seems to work:
p WaitForGlobalBarrier(EmitGlobalBarrier(GLOBBAR_CHECKSUM))
waits until all backends (including bgwriter, checkpointers, walwriters,
bgworkers, ...) have accepted interrupts at least once. Multiple such
requests are coalesced.
I decided to wait until interrupts are actually process, rather than
just the signal received, because that means the system is in a well
defined state. E.g. there's no pages currently being written out.
For the checksum enablement patch you'd do something like;
EnableChecksumsInShmemWithLock();
WaitForGlobalBarrier(EmitGlobalBarrier(GLOBBAR_CHECKSUM));
and after that you should be able to set it to a perstistent mode.
I chose to use procsignals to send the signals, a global uint64
globalBarrierGen, and per-backend barrierGen, barrierFlags, with the
latter keeping track which barriers have been requested. There likely
seem to be other usecases.
The patch definitely is in a prototype stage. At the very least it needs
a high-level comment somewhere, and some of the lower-level code needs
to be cleaned up.
One thing I wasn't happy about is how checksum internals have to absorb
barrier requests - that seems unavoidable, but I'd hope for something
more global than just BufferSync().
Comments?
Greetings,
Andres Freund
Attachments:
0001-Use-procsignal_sigusr1_handler-for-all-shmem-connect.patchtext/x-diff; charset=us-asciiDownload
From a3481bfde49c531eaee865024826bc893b0130f4 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 29 Oct 2018 10:14:15 -0700
Subject: [PATCH 1/3] Use procsignal_sigusr1_handler for all shmem connected
bgworkers.
As all processes that have a PGPROC can use procsignal.h style
signals BGWORKER_SHMEM_ACCESS should use procsignal_sigusr1_handler
not just BGWORKER_BACKEND_DATABASE_CONNECTION ones.
Author: Andres Freund
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
src/backend/postmaster/bgworker.c | 33 ++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index d2b695e1462..718ce940095 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -734,23 +734,32 @@ StartBackgroundWorker(void)
/*
* Set up signal handlers.
*/
- if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
- {
- /*
- * SIGINT is used to signal canceling the current action
- */
- pqsignal(SIGINT, StatementCancelHandler);
- pqsignal(SIGUSR1, procsignal_sigusr1_handler);
- pqsignal(SIGFPE, FloatExceptionHandler);
- /* XXX Any other handlers needed here? */
- }
+
+ /*
+ * SIGINT is used to signal canceling the current action for processes
+ * able to run queries.
+ */
+ if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
+ pqsignal(SIGINT, StatementCancelHandler);
else
- {
pqsignal(SIGINT, SIG_IGN);
+
+ /*
+ * Everything with a PGPROC should be able to receive procsignal.h style
+ * signals.
+ */
+ if (worker->bgw_flags & (BGWORKER_BACKEND_DATABASE_CONNECTION |
+ BGWORKER_SHMEM_ACCESS))
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+ else
pqsignal(SIGUSR1, bgworker_sigusr1_handler);
+
+ if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
+ pqsignal(SIGFPE, FloatExceptionHandler);
+ else
pqsignal(SIGFPE, SIG_IGN);
- }
+
pqsignal(SIGTERM, bgworker_die);
pqsignal(SIGHUP, SIG_IGN);
--
2.18.0.rc2.dirty
0002-Use-procsignal_sigusr1_handler-in-all-auxiliary-proc.patchtext/x-diff; charset=us-asciiDownload
From 4909b2d2d2299b866e70b68b71d1e2a9058b7fac Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 29 Oct 2018 12:45:00 -0700
Subject: [PATCH 2/3] Use procsignal_sigusr1_handler in all auxiliary
processes.
That will be helpful in a later commit, and also reduces duplication
due to individual copies of sigusr1 handlers.
Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
src/backend/postmaster/bgwriter.c | 20 +++-----------------
src/backend/postmaster/checkpointer.c | 15 ++-------------
src/backend/postmaster/startup.c | 15 ++-------------
src/backend/postmaster/walwriter.c | 15 ++-------------
src/backend/replication/walreceiver.c | 16 ++--------------
5 files changed, 11 insertions(+), 70 deletions(-)
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index b1e9bb2c537..87157b543fa 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -51,6 +51,7 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
+#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "storage/spin.h"
@@ -97,7 +98,6 @@ static volatile sig_atomic_t shutdown_requested = false;
static void bg_quickdie(SIGNAL_ARGS);
static void BgSigHupHandler(SIGNAL_ARGS);
static void ReqShutdownHandler(SIGNAL_ARGS);
-static void bgwriter_sigusr1_handler(SIGNAL_ARGS);
/*
@@ -115,10 +115,7 @@ BackgroundWriterMain(void)
WritebackContext wb_context;
/*
- * Properly accept or ignore signals the postmaster might send us.
- *
- * bgwriter doesn't participate in ProcSignal signalling, but a SIGUSR1
- * handler is still needed for latch wakeups.
+ * Properly accept or ignore signals that might be sent to us.
*/
pqsignal(SIGHUP, BgSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
@@ -126,7 +123,7 @@ BackgroundWriterMain(void)
pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, bgwriter_sigusr1_handler);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN);
/*
@@ -439,14 +436,3 @@ ReqShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-
-/* SIGUSR1: used for latch wakeups */
-static void
-bgwriter_sigusr1_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- latch_sigusr1_handler();
-
- errno = save_errno;
-}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 1a033093c53..31c9644759a 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -54,6 +54,7 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
+#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "storage/spin.h"
@@ -179,7 +180,6 @@ static void UpdateSharedMemoryConfig(void);
static void chkpt_quickdie(SIGNAL_ARGS);
static void ChkptSigHupHandler(SIGNAL_ARGS);
static void ReqCheckpointHandler(SIGNAL_ARGS);
-static void chkpt_sigusr1_handler(SIGNAL_ARGS);
static void ReqShutdownHandler(SIGNAL_ARGS);
@@ -211,7 +211,7 @@ CheckpointerMain(void)
pqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, chkpt_sigusr1_handler);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */
/*
@@ -854,17 +854,6 @@ ReqCheckpointHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGUSR1: used for latch wakeups */
-static void
-chkpt_sigusr1_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- latch_sigusr1_handler();
-
- errno = save_errno;
-}
-
/* SIGUSR2: set flag to run a shutdown checkpoint and exit */
static void
ReqShutdownHandler(SIGNAL_ARGS)
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 2926211e35d..b8d5f5a2073 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -30,6 +30,7 @@
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
+#include "storage/procsignal.h"
#include "storage/standby.h"
#include "utils/guc.h"
#include "utils/timeout.h"
@@ -50,7 +51,6 @@ static volatile sig_atomic_t in_restore_command = false;
/* Signal handlers */
static void startupproc_quickdie(SIGNAL_ARGS);
-static void StartupProcSigUsr1Handler(SIGNAL_ARGS);
static void StartupProcTriggerHandler(SIGNAL_ARGS);
static void StartupProcSigHupHandler(SIGNAL_ARGS);
@@ -87,17 +87,6 @@ startupproc_quickdie(SIGNAL_ARGS)
}
-/* SIGUSR1: let latch facility handle the signal */
-static void
-StartupProcSigUsr1Handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- latch_sigusr1_handler();
-
- errno = save_errno;
-}
-
/* SIGUSR2: set flag to finish recovery */
static void
StartupProcTriggerHandler(SIGNAL_ARGS)
@@ -181,7 +170,7 @@ StartupProcessMain(void)
pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
InitializeTimeouts(); /* establishes SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, StartupProcSigUsr1Handler);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, StartupProcTriggerHandler);
/*
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index fb66bceeedf..62c239e4a2c 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -55,6 +55,7 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
+#include "storage/procsignal.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
@@ -86,7 +87,6 @@ static volatile sig_atomic_t shutdown_requested = false;
static void wal_quickdie(SIGNAL_ARGS);
static void WalSigHupHandler(SIGNAL_ARGS);
static void WalShutdownHandler(SIGNAL_ARGS);
-static void walwriter_sigusr1_handler(SIGNAL_ARGS);
/*
* Main entry point for walwriter process
@@ -114,7 +114,7 @@ WalWriterMain(void)
pqsignal(SIGQUIT, wal_quickdie); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, walwriter_sigusr1_handler);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN); /* not used */
/*
@@ -349,14 +349,3 @@ WalShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-
-/* SIGUSR1: used for latch wakeups */
-static void
-walwriter_sigusr1_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- latch_sigusr1_handler();
-
- errno = save_errno;
-}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 6f4b3538ac4..5ec471259f9 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -63,6 +63,7 @@
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procarray.h"
+#include "storage/procsignal.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/pg_lsn.h"
@@ -146,7 +147,6 @@ static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
/* Signal handlers */
static void WalRcvSigHupHandler(SIGNAL_ARGS);
-static void WalRcvSigUsr1Handler(SIGNAL_ARGS);
static void WalRcvShutdownHandler(SIGNAL_ARGS);
static void WalRcvQuickDieHandler(SIGNAL_ARGS);
@@ -274,7 +274,7 @@ WalReceiverMain(void)
pqsignal(SIGQUIT, WalRcvQuickDieHandler); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
- pqsignal(SIGUSR1, WalRcvSigUsr1Handler);
+ pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN);
/* Reset some signals that are accepted by postmaster but not here */
@@ -815,18 +815,6 @@ WalRcvSigHupHandler(SIGNAL_ARGS)
got_SIGHUP = true;
}
-
-/* SIGUSR1: used by latch mechanism */
-static void
-WalRcvSigUsr1Handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- latch_sigusr1_handler();
-
- errno = save_errno;
-}
-
/* SIGTERM: set flag for main loop, or shutdown immediately if safe */
static void
WalRcvShutdownHandler(SIGNAL_ARGS)
--
2.18.0.rc2.dirty
0003-WIP-global-barriers.patchtext/x-diff; charset=us-asciiDownload
From 370d4d740eb011f69cbd9a656dee53c7cdad3211 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 29 Oct 2018 22:16:02 -0700
Subject: [PATCH 3/3] WIP: global barriers.
Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
src/backend/postmaster/autovacuum.c | 3 +-
src/backend/postmaster/bgwriter.c | 4 +
src/backend/postmaster/checkpointer.c | 4 +
src/backend/postmaster/startup.c | 3 +
src/backend/postmaster/walwriter.c | 2 +
src/backend/replication/walreceiver.c | 5 +-
src/backend/storage/buffer/bufmgr.c | 4 +
src/backend/storage/ipc/procsignal.c | 138 ++++++++++++++++++++++++++
src/backend/storage/lmgr/proc.c | 20 ++++
src/backend/tcop/postgres.c | 7 ++
src/include/storage/proc.h | 9 ++
src/include/storage/procsignal.h | 23 ++++-
12 files changed, 217 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 978089575b8..e821adcb4f3 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -651,8 +651,9 @@ AutoVacLauncherMain(int argc, char *argv[])
ResetLatch(MyLatch);
- /* Process sinval catchup interrupts that happened while sleeping */
+ /* Process pending interrupts. */
ProcessCatchupInterrupt();
+ ProcessGlobalBarrierIntterupt();
/*
* Emergency bailout if postmaster has died. This is to avoid the
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 87157b543fa..cd82cce4938 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -262,6 +262,10 @@ BackgroundWriterMain(void)
proc_exit(0); /* done */
}
+ /* Process all pending interrupts. */
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
+
/*
* Do one cycle of dirty-buffer writing.
*/
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 31c9644759a..25e741d9869 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -349,6 +349,10 @@ CheckpointerMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
+ /* Process all pending interrupts. */
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
+
/*
* Process any requests or signals received recently.
*/
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index b8d5f5a2073..2e4b0d55056 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -151,6 +151,9 @@ HandleStartupProcInterrupts(void)
*/
if (IsUnderPostmaster && !PostmasterIsAlive())
exit(1);
+
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
}
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 62c239e4a2c..e36198ce0d4 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -260,6 +260,8 @@ WalWriterMain(void)
/* Normal exit from the walwriter is here */
proc_exit(0); /* done */
}
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
/*
* Do what we're here for; then, if XLogBackgroundFlush() found useful
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 5ec471259f9..4f6838b2a5f 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -155,9 +155,8 @@ static void
ProcessWalRcvInterrupts(void)
{
/*
- * Although walreceiver interrupt handling doesn't use the same scheme as
- * regular backends, call CHECK_FOR_INTERRUPTS() to make sure we receive
- * any incoming signals on Win32.
+ * The CHECK_FOR_INTERRUPTS() call ensures global barriers are handled,
+ * and incoming signals on Win32 are received.
*/
CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 01eabe57063..02e3b9ac396 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1877,6 +1877,10 @@ BufferSync(int flags)
cur_tsid = CkptBufferIds[i].tsId;
+ /* XXX: need a more principled approach here */
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
+
/*
* Grow array of per-tablespace status structs, every time a new
* tablespace is found.
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index b0dd7d1b377..c12a6e85cb0 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -18,6 +18,7 @@
#include <unistd.h>
#include "access/parallel.h"
+#include "access/twophase.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "replication/walsender.h"
@@ -62,9 +63,11 @@ typedef struct
static ProcSignalSlot *ProcSignalSlots = NULL;
static volatile ProcSignalSlot *MyProcSignalSlot = NULL;
+volatile sig_atomic_t GlobalBarrierInterruptPending = false;
static bool CheckProcSignal(ProcSignalReason reason);
static void CleanupProcSignalState(int status, Datum arg);
+static void HandleGlobalBarrierSignal(void);
/*
* ProcSignalShmemSize
@@ -262,6 +265,8 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
{
int save_errno = errno;
+ pg_read_barrier();
+
if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
HandleCatchupInterrupt();
@@ -292,9 +297,142 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ if (CheckProcSignal(PROCSIG_GLOBAL_BARRIER))
+ HandleGlobalBarrierSignal();
+
SetLatch(MyLatch);
latch_sigusr1_handler();
errno = save_errno;
}
+
+/*
+ *
+ */
+uint64
+EmitGlobalBarrier(GlobalBarrierKind kind)
+{
+ uint64 generation;
+
+ /*
+ * Broadcast flag, without incrementing generation. This ensures that all
+ * backends could know about this.
+ *
+ * It's OK if the to-be-signalled backend enters after our check here. A
+ * new backend should have current settings.
+ */
+ for (int i = 0; i < (MaxBackends + max_prepared_xacts); i++)
+ {
+ PGPROC *proc = &ProcGlobal->allProcs[i];
+
+ if (proc->pid == 0)
+ continue;
+
+ pg_atomic_fetch_or_u32(&proc->barrierFlags, (uint32) kind);
+
+ elog(LOG, "setting flags for %u", proc->pid);
+ }
+
+ /*
+ * Broadcast flag generation. If any backend joins after this, it's either
+ * going to be signalled below, or has read a new enough generation that
+ * WaitForGlobalBarrier() will not wait for it.
+ */
+ generation = pg_atomic_add_fetch_u64(&ProcGlobal->globalBarrierGen, 1);
+
+ /* Wake up each backend (including ours) */
+ for (int i = 0; i < NumProcSignalSlots; i++)
+ {
+ ProcSignalSlot *slot = &ProcSignalSlots[i];
+
+ if (slot->pss_pid == 0)
+ continue;
+
+ /* Atomically set the proper flag */
+ slot->pss_signalFlags[PROCSIG_GLOBAL_BARRIER] = true;
+
+ pg_write_barrier();
+
+ /* Send signal */
+ kill(slot->pss_pid, SIGUSR1);
+ }
+
+ return generation;
+}
+
+/*
+ * Wait for all barriers to be absorbed. This guarantees that all changes
+ * requested by a specific EmitGlobalBarrier() have taken effect.
+ */
+void
+WaitForGlobalBarrier(uint64 generation)
+{
+ for (int i = 0; i < (MaxBackends + max_prepared_xacts); i++)
+ {
+ PGPROC *proc = &ProcGlobal->allProcs[i];
+ uint64 oldval;
+
+ pg_memory_barrier();
+ oldval = pg_atomic_read_u64(&proc->barrierGen);
+
+ /*
+ * Unused proc slots get their barrierGen set to UINT64_MAX, so we
+ * need not care about that.
+ */
+ while (oldval < generation)
+ {
+ CHECK_FOR_INTERRUPTS();
+ pg_usleep(10000);
+
+ pg_memory_barrier();
+ oldval = pg_atomic_read_u64(&proc->barrierGen);
+ }
+ }
+}
+
+/*
+ * Absorb the global barrier procsignal.
+ */
+static void
+HandleGlobalBarrierSignal(void)
+{
+ InterruptPending = true;
+ GlobalBarrierInterruptPending = true;
+ SetLatch(MyLatch);
+}
+
+/*
+ * Perform global barrier related interrupt checking. If CHECK_FOR_INTERRUPTS
+ * is used, it'll be called by that, if a backend type doesn't do so, it has
+ * to be called explicitly.
+ */
+void
+ProcessGlobalBarrierIntterupt(void)
+{
+ if (GlobalBarrierInterruptPending)
+ {
+ uint64 generation;
+ uint32 flags;
+
+ GlobalBarrierInterruptPending = false;
+
+ generation = pg_atomic_read_u64(&ProcGlobal->globalBarrierGen);
+ pg_memory_barrier();
+ flags = pg_atomic_exchange_u32(&MyProc->barrierFlags, 0);
+ pg_memory_barrier();
+
+ if (flags & GLOBBAR_CHECKSUM)
+ {
+ /*
+ * By virtue of getting here (i.e. interrupts being processed), we
+ * know that this backend won't have any in-progress writes (which
+ * might have missed the checksum change).
+ */
+ }
+
+ pg_atomic_write_u64(&MyProc->barrierGen, generation);
+
+ elog(LOG, "processed interrupts for %u", MyProcPid);
+ }
+}
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 6f9aaa52faf..354574ef4a5 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -187,6 +187,7 @@ InitProcGlobal(void)
ProcGlobal->checkpointerLatch = NULL;
pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);
+ pg_atomic_init_u64(&ProcGlobal->globalBarrierGen, 1);
/*
* Create and initialize all the PGPROC structures we'll need. There are
@@ -267,6 +268,9 @@ InitProcGlobal(void)
/* Initialize lockGroupMembers list. */
dlist_init(&procs[i].lockGroupMembers);
+
+ pg_atomic_init_u32(&procs[i].barrierFlags, 0);
+ pg_atomic_init_u64(&procs[i].barrierGen, PG_UINT64_MAX);
}
/*
@@ -418,6 +422,12 @@ InitProcess(void)
MyProc->clogGroupMemberLsn = InvalidXLogRecPtr;
pg_atomic_init_u32(&MyProc->clogGroupNext, INVALID_PGPROCNO);
+ /* pairs with globalBarrierGen increase */
+ pg_memory_barrier();
+ pg_atomic_write_u32(&MyProc->barrierFlags, 0);
+ pg_atomic_write_u64(&MyProc->barrierGen,
+ pg_atomic_read_u64(&ProcGlobal->globalBarrierGen));
+
/*
* Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
* on it. That allows us to repoint the process latch, which so far
@@ -561,6 +571,13 @@ InitAuxiliaryProcess(void)
MyProc->lwWaitMode = 0;
MyProc->waitLock = NULL;
MyProc->waitProcLock = NULL;
+
+ /* pairs with globalBarrierGen increase */
+ pg_memory_barrier();
+ pg_atomic_write_u32(&MyProc->barrierFlags, 0);
+ pg_atomic_write_u64(&MyProc->barrierGen,
+ pg_atomic_read_u64(&ProcGlobal->globalBarrierGen));
+
#ifdef USE_ASSERT_CHECKING
{
int i;
@@ -859,6 +876,9 @@ ProcKill(int code, Datum arg)
LWLockRelease(leader_lwlock);
}
+ pg_atomic_write_u32(&MyProc->barrierFlags, 0);
+ pg_atomic_write_u64(&MyProc->barrierGen, PG_UINT64_MAX);
+
/*
* Reset MyLatch to the process local one. This is so that signal
* handlers et al can continue using the latch after the shared latch
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 6e13d14fcd0..806c66bc320 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -589,6 +589,10 @@ ProcessClientWriteInterrupt(bool blocked)
CHECK_FOR_INTERRUPTS();
}
+ /* safe to handle during client communication */
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
+
errno = save_errno;
}
@@ -3123,6 +3127,9 @@ ProcessInterrupts(void)
if (ParallelMessagePending)
HandleParallelMessages();
+
+ if (GlobalBarrierInterruptPending)
+ ProcessGlobalBarrierIntterupt();
}
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index cb613c8076e..1280c4f41f1 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -203,6 +203,13 @@ struct PGPROC
PGPROC *lockGroupLeader; /* lock group leader, if I'm a member */
dlist_head lockGroupMembers; /* list of members, if I'm a leader */
dlist_node lockGroupLink; /* my member link, if I'm a member */
+
+ /*
+ * Support for "super barriers". These can be used to e.g. make sure that
+ * all backends have acknowledged a configuration change.
+ */
+ pg_atomic_uint64 barrierGen;
+ pg_atomic_uint32 barrierFlags;
};
/* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -270,6 +277,8 @@ typedef struct PROC_HDR
int startupProcPid;
/* Buffer id of the buffer that Startup process waits for pin on, or -1 */
int startupBufferPinWaitBufId;
+
+ pg_atomic_uint64 globalBarrierGen;
} PROC_HDR;
extern PGDLLIMPORT PROC_HDR *ProcGlobal;
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 6db0d69b71f..388eba83807 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,8 +14,9 @@
#ifndef PROCSIGNAL_H
#define PROCSIGNAL_H
-#include "storage/backendid.h"
+#include <signal.h>
+#include "storage/backendid.h"
/*
* Reasons for signalling a Postgres child process (a backend or an auxiliary
@@ -42,6 +43,8 @@ typedef enum
PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
+ PROCSIG_GLOBAL_BARRIER,
+
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
@@ -57,4 +60,22 @@ extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
+/*
+ * These collapse. The flag values better be distinct bits.
+ */
+typedef enum GlobalBarrierKind
+{
+ /*
+ * Guarantee that all processes have the correct view of whether checksums
+ * enabled/disabled, and no writes are in-progress with previous value(s).
+ */
+ GLOBBAR_CHECKSUM = 1 << 0
+} GlobalBarrierKind;
+
+extern uint64 EmitGlobalBarrier(GlobalBarrierKind kind);
+extern void WaitForGlobalBarrier(uint64 generation);
+extern void ProcessGlobalBarrierIntterupt(void);
+
+extern PGDLLIMPORT volatile sig_atomic_t GlobalBarrierInterruptPending;
+
#endif /* PROCSIGNAL_H */
--
2.18.0.rc2.dirty
On Tue, Oct 30, 2018 at 6:16 AM Andres Freund <andres@anarazel.de> wrote:
Hi,
Magnus cornered me at pgconf.eu and asked me whether I could prototype
the "barriers" I'd been talking about in the online checksumming thread.The problem there was to make sure that all processes, backends and
auxiliary processes have seen the new state of checksums being enabled,
and aren't currently in the process of writing a new page out.The current prototype solves that by requiring a restart, but that
strikes me as a far too large hammer.The attached patch introduces "global barriers" (name was invented in a
overcrowded hotel lounge, so ...), which allow to wait for such a change
to be absorbed by all backends.I've only tested the code with gdb, but that seems to work:
p WaitForGlobalBarrier(EmitGlobalBarrier(GLOBBAR_CHECKSUM))
waits until all backends (including bgwriter, checkpointers, walwriters,
bgworkers, ...) have accepted interrupts at least once. Multiple such
requests are coalesced.I decided to wait until interrupts are actually process, rather than
just the signal received, because that means the system is in a well
defined state. E.g. there's no pages currently being written out.For the checksum enablement patch you'd do something like;
EnableChecksumsInShmemWithLock();
WaitForGlobalBarrier(EmitGlobalBarrier(GLOBBAR_CHECKSUM));and after that you should be able to set it to a perstistent mode.
I chose to use procsignals to send the signals, a global uint64
globalBarrierGen, and per-backend barrierGen, barrierFlags, with the
latter keeping track which barriers have been requested. There likely
seem to be other usecases.The patch definitely is in a prototype stage. At the very least it needs
a high-level comment somewhere, and some of the lower-level code needs
to be cleaned up.One thing I wasn't happy about is how checksum internals have to absorb
barrier requests - that seems unavoidable, but I'd hope for something
more global than just BufferSync().Comments?
Finally getting around to playing with this one and it unfortunately
doesn't apply anymore (0003).
I think it's just a matter of adding those two rows though, right? That is,
it's not an actual conflict it's just something else added in the same
place?
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/>
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
Hi,
On 2018-12-27 13:54:34 +0100, Magnus Hagander wrote:
Finally getting around to playing with this one and it unfortunately
doesn't apply anymore (0003).I think it's just a matter of adding those two rows though, right? That is,
it's not an actual conflict it's just something else added in the same
place?
What do you mean with "rows" here? I see a bunch of trivial conflicts
due to changes in atomics initialization but nothing else? And yes, I'd
not expect any meaningful conflicts.
Greetings,
Andres Freund
On Thu, Dec 27, 2018 at 2:22 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2018-12-27 13:54:34 +0100, Magnus Hagander wrote:
Finally getting around to playing with this one and it unfortunately
doesn't apply anymore (0003).I think it's just a matter of adding those two rows though, right? That
is,
it's not an actual conflict it's just something else added in the same
place?What do you mean with "rows" here? I see a bunch of trivial conflicts
due to changes in atomics initialization but nothing else? And yes, I'd
not expect any meaningful conflicts.
Sorry, lack of caffeine. The conflict I saw was:
/* Initialize lockGroupMembers list. */
dlist_init(&procs[i].lockGroupMembers);
+
+ pg_atomic_init_u32(&procs[i].barrierFlags, 0);
+ pg_atomic_init_u64(&procs[i].barrierGen, PG_UINT64_MAX);
}
So yes, I'm pretty sure we're talking about the same thing.
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/>
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
On Tue, Oct 30, 2018 at 6:16 AM Andres Freund <andres@anarazel.de> wrote:
Hi,
Magnus cornered me at pgconf.eu and asked me whether I could prototype
the "barriers" I'd been talking about in the online checksumming thread.The problem there was to make sure that all processes, backends and
auxiliary processes have seen the new state of checksums being enabled,
and aren't currently in the process of writing a new page out.The current prototype solves that by requiring a restart, but that
strikes me as a far too large hammer.The attached patch introduces "global barriers" (name was invented in a
overcrowded hotel lounge, so ...), which allow to wait for such a change
to be absorbed by all backends.I've only tested the code with gdb, but that seems to work:
p WaitForGlobalBarrier(EmitGlobalBarrier(GLOBBAR_CHECKSUM))
waits until all backends (including bgwriter, checkpointers, walwriters,
bgworkers, ...) have accepted interrupts at least once. Multiple such
requests are coalesced.I decided to wait until interrupts are actually process, rather than
just the signal received, because that means the system is in a well
defined state. E.g. there's no pages currently being written out.For the checksum enablement patch you'd do something like;
EnableChecksumsInShmemWithLock();
WaitForGlobalBarrier(EmitGlobalBarrier(GLOBBAR_CHECKSUM));and after that you should be able to set it to a perstistent mode.
I chose to use procsignals to send the signals, a global uint64
globalBarrierGen, and per-backend barrierGen, barrierFlags, with the
latter keeping track which barriers have been requested. There likely
seem to be other usecases.The patch definitely is in a prototype stage. At the very least it needs
a high-level comment somewhere, and some of the lower-level code needs
to be cleaned up.One thing I wasn't happy about is how checksum internals have to absorb
barrier requests - that seems unavoidable, but I'd hope for something
more global than just BufferSync().Comments?
Finally getting back to this one.
In re-reading this, I notice there are a lot of references to Intterrupt
(with two t). I'm guessing this is just a spelling error, and not something
that actually conveys some meaning?
Can you elaborate on what you mean with:
+ /* XXX: need a more principled approach here */
Is that the thing you refer to above about "checksum internals"?
Also in checking we figured it'd be nice to have a wait event for this,
since a process can potentially get stuck in an infinite loop waiting for
some other process if it's misbehaving. Kind of like the attached?
//Magnus
Attachments:
barrier_wait_events.patchtext/x-patch; charset=US-ASCII; name=barrier_wait_events.patchDownload
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index a84042a4ea..58b360d225 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3765,6 +3765,9 @@ pgstat_get_wait_ipc(WaitEventIPC w)
case WAIT_EVENT_EXECUTE_GATHER:
event_name = "ExecuteGather";
break;
+ case WAIT_EVENT_GLOBAL_BARRIER:
+ event_name = "GlobalBarrier";
+ break;
case WAIT_EVENT_HASH_BATCH_ALLOCATING:
event_name = "Hash/Batch/Allocating";
break;
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 4e3e4a4893..9aed52df4a 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -21,6 +21,7 @@
#include "access/twophase.h"
#include "commands/async.h"
#include "miscadmin.h"
+#include "pgstat.h"
#include "replication/walsender.h"
#include "storage/latch.h"
#include "storage/ipc.h"
@@ -368,6 +369,7 @@ EmitGlobalBarrier(GlobalBarrierKind kind)
void
WaitForGlobalBarrier(uint64 generation)
{
+ pgstat_report_wait_start(WAIT_EVENT_GLOBAL_BARRIER);
for (int i = 0; i < (MaxBackends + max_prepared_xacts); i++)
{
PGPROC *proc = &ProcGlobal->allProcs[i];
@@ -389,6 +391,7 @@ WaitForGlobalBarrier(uint64 generation)
oldval = pg_atomic_read_u64(&proc->barrierGen);
}
}
+ pgstat_report_wait_end();
}
/*
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 2b656a8168..0d35b19420 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -827,6 +827,7 @@ typedef enum
WAIT_EVENT_CHECKPOINT_DONE,
WAIT_EVENT_CHECKPOINT_START,
WAIT_EVENT_EXECUTE_GATHER,
+ WAIT_EVENT_GLOBAL_BARRIER,
WAIT_EVENT_HASH_BATCH_ALLOCATING,
WAIT_EVENT_HASH_BATCH_ELECTING,
WAIT_EVENT_HASH_BATCH_LOADING,
Hi,
On 2019-07-10 15:31:11 +0200, Magnus Hagander wrote:
In re-reading this, I notice there are a lot of references to Intterrupt
(with two t). I'm guessing this is just a spelling error, and not something
that actually conveys some meaning?
Just a spelling error. I think I wrote the patch in a night after
pgconf.eu, to allow you to quickly make progress :P
Can you elaborate on what you mean with:
+ /* XXX: need a more principled approach here */
Is that the thing you refer to above about "checksum internals"?
I think I didn't actually mean "checksum" but instead "checkpoint". It
does bother me that we have an operation as long-running as BufferSync()
commonly is, without a proper way to accept event. There's a hack for
doing something similar-ish in CheckpointWriteDelay(), for absorbing
fsync requests, but it doesn't trigger for checkpoints not done in
checkpointer, nor is it really extensible.
Also in checking we figured it'd be nice to have a wait event for this,
since a process can potentially get stuck in an infinite loop waiting for
some other process if it's misbehaving. Kind of like the attached?
Yea, that makes sense.
Greetings,
Andres Freund
On Tue, Oct 30, 2018 at 1:17 AM Andres Freund <andres@anarazel.de> wrote:
The patch definitely is in a prototype stage. At the very least it needs
a high-level comment somewhere, and some of the lower-level code needs
to be cleaned up.One thing I wasn't happy about is how checksum internals have to absorb
barrier requests - that seems unavoidable, but I'd hope for something
more global than just BufferSync().
Hi,
TL;DR: I'm not sure that we need 0001; I propose to commit 0002; and I
have some concerns about 0003 and am interested in working further on
it.
0001 changes the StartBackgroundWorker so that the SIGINT handler is
contingent on BGWORKER_SHMEM_ACCESS rather than
BGWORKER_BACKEND_DATABASE_CONNECTION. It seems to me that the goal
here should be to use procsignal_sigusr1_handler(), or something that
calls it, in any process where ProcSignalInit() is called, but a
backend that only requests BGWORKER_SHMEM_ACCESS probably won't,
because the normal way for a background worker to call
ProcSignalInit() would be to indirectly call InitPostgres() by means
of BackgroundWorkerInitializeConnection() or
BackgroundWorkerInitializeConnectionByOid(), and a worker that didn't
ask for a database connection presumably shouldn't be doing that. So
I'm not sure that I understand why we need this. (Is it legal for a
worker to call one of these functions as long as it passes InvalidOid
for the database OID, or something? Do we have examples of workers
that do that?)
On the other hand, 0002 seems like it's pretty clearly a good idea. It
makes a whole bunch of auxiliary processes use
procsignal_sigusr1_handler() and those things all get called from
AuxiliaryProcessMain(), which does ProcSignalInit(), and it seems like
clearly the right idea that processes which register to participate in
the procsignal mechanism should also register to get notified if they
receive a procsignal. I think that the reason we haven't bothered with
this up until now is because I think that it's presently impossible
for any of the kind of procsignals that we have to get sent to any of
those processes. But, global barriers would require us to do so, so it
seems like it's time to tighten that up, and it doesn't really cost
anything. So I propose to commit this part soon, unless somebody
objects.
Regarding 0003:
- The proc->pid == 0 check in EmitGlobalBarrier() doesn't really do
what it appears to do, because regular backends don't clear proc->pid
when they exit; only auxiliary processes do. (We could, and perhaps
should, change that.)
- It seems to me that it would be generally better to insert
CHECK_FOR_INTERRUPTS() in places where the patch just inserts an
ad-hoc if (GlobalBarrierInterruptPending)
ProcessGlobalBarrierIntterupt(). There might be someplace where we
have to do it the latter way because we unavoidably hold an LWLock or
something, but I think we should avoid that whenever possible. If it's
a good place to check for one kind of interrupt, it's probably a good
place to check for all of them.
- I don't think BufferSync() is doing this in the right place. Unless
I am confused, it's doing it inside a loop that just allocates stuff
and copies data around, which probably does not need this kind of
decoration. It wouldn't hurt to check here, and it might be a good
idea for safety, but I think the place that really needs this
treatment is the following loop where we are actually doing I/O and
sleeping. Possibly we should even be doctoring CheckpointWriteDelay to
use a latch-wait loop that can CHECK_FOR_INTERRUPTS() before
re-sleeping, although for 100ms (curiously described as a Big Sleep)
it might be overkill.
- I think it would be nice to make this system more extensible. IIUC,
the idea is that ProcessGlobalBarrierIntterupt() will call bespoke
code for each GLOBBAR_* flag that set, and that code will then update
process-local state from global state before returning. But, instead
of hard-coding constants, how about just having a list of callbacks,
and we call them all here, and each one is responsible for figuring
out whether anything's been changed that it cares about, and if so
updating the appropriate local state? Then GlobalBarrierKind goes away
altogether. Granted, this could be a bit expensive if we were using
global barriers for lots of different things and at least some of
those things occurred frequently, but I don't think we're very near
the point where we need that kind of optimization. As long as the
callbacks have tests that exit quickly if there's nothing to do, I
think it should be fine. (As an intermediate position, we could
consider keeping barrierFlags but assign the bits dynamically; but
unless and until we get more users of the facility, I think it might
be simplest to just forget about barrierFlags altogether. Then even
extensions can use this, if they want.)
- The patch needs some general tidying up, like comments and naming
consistency and stuff like that.
Andres, Magnus, if neither of you are planning to work on this soon, I
might like to jump in and run with this. Please let me know your
thoughts.
Thanks,
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Hi,
On 2019-11-13 12:26:34 -0500, Robert Haas wrote:
TL;DR: I'm not sure that we need 0001; I propose to commit 0002; and I
have some concerns about 0003 and am interested in working further on
it.
Thanks for looking at the patch!
0001 changes the StartBackgroundWorker so that the SIGINT handler is
contingent on BGWORKER_SHMEM_ACCESS rather than
BGWORKER_BACKEND_DATABASE_CONNECTION. It seems to me that the goal
here should be to use procsignal_sigusr1_handler(), or something that
calls it, in any process where ProcSignalInit() is called, but a
backend that only requests BGWORKER_SHMEM_ACCESS probably won't,
because the normal way for a background worker to call
ProcSignalInit() would be to indirectly call InitPostgres() by means
of BackgroundWorkerInitializeConnection() or
BackgroundWorkerInitializeConnectionByOid(), and a worker that didn't
ask for a database connection presumably shouldn't be doing that. So
I'm not sure that I understand why we need this. (Is it legal for a
worker to call one of these functions as long as it passes InvalidOid
for the database OID, or something? Do we have examples of workers
that do that?)
Hm. Well, it seems useful for non-database connected processes to be
able to partake in global barriers. Without that, if there's any chance
they could e.g. generate WAL, it'd e.g. break the checksum enablement
patch. Note that auxiliary processes already do call ProcSignalInit().
You're right that we ought to make it easier (or automatic) to call
ProcSignalInit() for such processes. Perhaps we ought to do so in
ProcessInit()?
But perhaps we don't strictly need this - I'm not sure how many examples
of BGWORKER_SHMEM_ACCESS bgworkers that don't also use
BGWORKER_BACKEND_DATABASE_CONNECTION there are.
Regarding 0003:
- The proc->pid == 0 check in EmitGlobalBarrier() doesn't really do
what it appears to do, because regular backends don't clear proc->pid
when they exit; only auxiliary processes do. (We could, and perhaps
should, change that.)
Ick.
- It seems to me that it would be generally better to insert
CHECK_FOR_INTERRUPTS() in places where the patch just inserts an
ad-hoc if (GlobalBarrierInterruptPending)
ProcessGlobalBarrierIntterupt(). There might be someplace where we
have to do it the latter way because we unavoidably hold an LWLock or
something, but I think we should avoid that whenever possible. If it's
a good place to check for one kind of interrupt, it's probably a good
place to check for all of them.
I might be missing something. Aren't all of the places where those
checks are places where we currently can't do a CHECK_FOR_INTERRUPTS()?
I've swapped this thoroughly out of my mind, but going through them:
1) AutoVacLauncherMain() - doesn't do CFI()
2) BackgroundWriterMain() - dito
3) CheckpointerMain() - dito
4) HandleStartupProcInterrupts() - dito
5) WalWriterMain() - dito
6) BufferSync() - dito, called from CheckpointerMain(), and startup process
7) ProcessClientWriteInterrupt() - can't do generic CFI, don't want to
process all interrupts while writing out data, to avoid corrupting
the output stream or loosing messages
Which one do you think we should convert to CFI()? As far as I can tell
we can't make the non-backend cases use the postgres.c
ProcessInterrupts(), and the ProcessClientWriteInterrupt() one can't do
so either.
- I don't think BufferSync() is doing this in the right place. Unless
I am confused, it's doing it inside a loop that just allocates stuff
and copies data around, which probably does not need this kind of
decoration. It wouldn't hurt to check here, and it might be a good
idea for safety, but I think the place that really needs this
treatment is the following loop where we are actually doing I/O and
sleeping. Possibly we should even be doctoring CheckpointWriteDelay to
use a latch-wait loop that can CHECK_FOR_INTERRUPTS() before
re-sleeping, although for 100ms (curiously described as a Big Sleep)
it might be overkill.
Yea. Not sure what happened there. I think it's good to have such a
check in all the buffer loops in BufferSync(), but clearly it's most
important to have one in the write case.
- I think it would be nice to make this system more extensible. IIUC,
the idea is that ProcessGlobalBarrierIntterupt() will call bespoke
code for each GLOBBAR_* flag that set, and that code will then update
process-local state from global state before returning. But, instead
of hard-coding constants, how about just having a list of callbacks,
and we call them all here, and each one is responsible for figuring
out whether anything's been changed that it cares about, and if so
updating the appropriate local state?
I don't think that's a good idea. This stuff happens at an extremely low
level, in different types of processes, with various locks held
etc. Running arbitrary code in those circumstances strikes me as a
seriously bad idea.
Then GlobalBarrierKind goes away altogether. Granted, this could be a
bit expensive if we were using global barriers for lots of different
things and at least some of those things occurred frequently, but I
don't think we're very near the point where we need that kind of
optimization. As long as the callbacks have tests that exit quickly if
there's nothing to do, I think it should be fine. (As an intermediate
position, we could consider keeping barrierFlags but assign the bits
dynamically; but unless and until we get more users of the facility, I
think it might be simplest to just forget about barrierFlags
altogether. Then even extensions can use this, if they want.)
I think we need the option to *not* "accept" the barrier (as in, not
update MyProc->barrierGen), because we cannot guarantee the option has
taken effect yet. Without something identifying which types of events
being waited for, that seems hard.
- The patch needs some general tidying up, like comments and naming
consistency and stuff like that.
Yea. It really was a prototype to allow Magnus to continue...
Andres, Magnus, if neither of you are planning to work on this soon, I
might like to jump in and run with this. Please let me know your
thoughts.
I'm not planning to do so in the near term - so I'd more than welcome
you to do so.
Greetings,
Andres Freund
On Wed, Nov 13, 2019 at 2:45 PM Andres Freund <andres@anarazel.de> wrote:
I might be missing something. Aren't all of the places where those
checks are places where we currently can't do a CHECK_FOR_INTERRUPTS()?
I've swapped this thoroughly out of my mind, but going through them:1) AutoVacLauncherMain() - doesn't do CFI()
2) BackgroundWriterMain() - dito
3) CheckpointerMain() - dito
4) HandleStartupProcInterrupts() - dito
5) WalWriterMain() - dito
6) BufferSync() - dito, called from CheckpointerMain(), and startup process
7) ProcessClientWriteInterrupt() - can't do generic CFI, don't want to
process all interrupts while writing out data, to avoid corrupting
the output stream or loosing messagesWhich one do you think we should convert to CFI()? As far as I can tell
we can't make the non-backend cases use the postgres.c
ProcessInterrupts(), and the ProcessClientWriteInterrupt() one can't do
so either.
I haven't looked through all of these, but in AutoVacLauncherMain, a
trivial conversion to CFI doesn't seem to break anything horribly (see
attached). It does change the error message when we exit, making it
chattier, but I think we could find our way around that problem. Note
that AutoVacLauncherMain, and some of the others, do a
HOLD_INTERRUPTS() and a RESUME_INTERRUPTS() in the error-recovery
block, so somebody evidently thought some of that code might call
CHECK_FOR_INTERRUPTS(), and I can't prove off-hand that none of the
other logic which isn't so protected doesn't have some way to reach
CHECK_FOR_INTERRUPTS(). It seems to me that there are so many places
where PostgreSQL calls CHECK_FOR_INTERRUPTS() that it's somewhat
unwise to assume that just "can't happen."
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
trivial-av-conversion-cfi.patchapplication/octet-stream; name=trivial-av-conversion-cfi.patchDownload
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1dd8168ca..d6b8738d28 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -140,7 +140,6 @@ static bool am_autovacuum_worker = false;
/* Flags set by signal handlers */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_SIGTERM = false;
/* Comparison points for determining whether freeze_max_age is exceeded */
static TransactionId recentXid;
@@ -344,7 +343,6 @@ static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
const char *nspname, const char *relname);
static void av_sighup_handler(SIGNAL_ARGS);
static void avl_sigusr2_handler(SIGNAL_ARGS);
-static void avl_sigterm_handler(SIGNAL_ARGS);
static void autovac_refresh_stats(void);
@@ -452,7 +450,7 @@ AutoVacLauncherMain(int argc, char *argv[])
*/
pqsignal(SIGHUP, av_sighup_handler);
pqsignal(SIGINT, StatementCancelHandler);
- pqsignal(SIGTERM, avl_sigterm_handler);
+ pqsignal(SIGTERM, die);
pqsignal(SIGQUIT, quickdie);
InitializeTimeouts(); /* establishes SIGALRM handler */
@@ -552,9 +550,8 @@ AutoVacLauncherMain(int argc, char *argv[])
/* Now we can allow interrupts again */
RESUME_INTERRUPTS();
- /* if in shutdown mode, no need for anything further; just go away */
- if (got_SIGTERM)
- goto shutdown;
+ /* And check for them, too */
+ CHECK_FOR_INTERRUPTS();
/*
* Sleep at least 1 second after any error. We don't want to be
@@ -605,8 +602,8 @@ AutoVacLauncherMain(int argc, char *argv[])
*/
if (!AutoVacuumingActive())
{
- if (!got_SIGTERM)
- do_start_worker();
+ CHECK_FOR_INTERRUPTS();
+ do_start_worker();
proc_exit(0); /* done */
}
@@ -622,12 +619,14 @@ AutoVacLauncherMain(int argc, char *argv[])
rebuild_database_list(InvalidOid);
/* loop until shutdown request */
- while (!got_SIGTERM)
+ while (1)
{
struct timeval nap;
TimestampTz current_time = 0;
bool can_launch;
+ CHECK_FOR_INTERRUPTS();
+
/*
* This loop is a bit different from the normal use of WaitLatch,
* because we'd like to sleep before the first launch of a child
@@ -652,10 +651,6 @@ AutoVacLauncherMain(int argc, char *argv[])
/* Process sinval catchup interrupts that happened while sleeping */
ProcessCatchupInterrupt();
- /* the normal shutdown case */
- if (got_SIGTERM)
- break;
-
if (got_SIGHUP)
{
got_SIGHUP = false;
@@ -814,7 +809,6 @@ AutoVacLauncherMain(int argc, char *argv[])
}
/* Normal exit from the autovac launcher is here */
-shutdown:
ereport(DEBUG1,
(errmsg("autovacuum launcher shutting down")));
AutoVacuumShmem->av_launcherpid = 0;
@@ -1411,18 +1405,6 @@ avl_sigusr2_handler(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGTERM: time to die */
-static void
-avl_sigterm_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGTERM = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/********************************************************************
* AUTOVACUUM WORKER CODE
On Wed, Nov 13, 2019 at 8:45 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2019-11-13 12:26:34 -0500, Robert Haas wrote:
TL;DR: I'm not sure that we need 0001; I propose to commit 0002; and I
have some concerns about 0003 and am interested in working further on
it.Thanks for looking at the patch!
+1 (well, +<more than one>, but there is a quota)
- The patch needs some general tidying up, like comments and naming
consistency and stuff like that.
Yea. It really was a prototype to allow Magnus to continue...
And a very useful one! :) So thanks for that one as well.
Andres, Magnus, if neither of you are planning to work on this soon, I
might like to jump in and run with this. Please let me know your
thoughts.I'm not planning to do so in the near term - so I'd more than welcome
you to do so.
I'm definitely happy to work with it, but I did not and do not feel I have
the skills for doing the "proper review" needed for it. So I am also very
happy for you to pick it up and run with it.
--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/>
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>
On Wed, Nov 13, 2019 at 12:26 PM Robert Haas <robertmhaas@gmail.com> wrote:
On the other hand, 0002 seems like it's pretty clearly a good idea. It
makes a whole bunch of auxiliary processes use
procsignal_sigusr1_handler() and those things all get called from
AuxiliaryProcessMain(), which does ProcSignalInit(), and it seems like
clearly the right idea that processes which register to participate in
the procsignal mechanism should also register to get notified if they
receive a procsignal. I think that the reason we haven't bothered with
this up until now is because I think that it's presently impossible
for any of the kind of procsignals that we have to get sent to any of
those processes. But, global barriers would require us to do so, so it
seems like it's time to tighten that up, and it doesn't really cost
anything. So I propose to commit this part soon, unless somebody
objects.
Done.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Sun, Nov 17, 2019 at 8:38 AM Magnus Hagander <magnus@hagander.net> wrote:
I'm definitely happy to work with it, but I did not and do not feel I have the skills for doing the "proper review" needed for it. So I am also very happy for you to pick it up and run with it.
OK, here's what I came up with.
0001 is just a code movement patch. It puts the interrupt handling for
each type of background process into a subroutine, instead of having
it all inline in the main loop. I feel this makes things more clear.
Hopefully it's uncontroversial.
0002 and 0003 are optional and slightly off-topic as far as the matter
at hand, but they are not entirely unrelated and I believe that they
are good cleanups. 0002 changes assorted background processes to use
PostgresSigHupHandler and ConfigReloadPending rather than having a
bunch of separate global variables and a bunch of separate signal
handlers that basically do the same stuff. 0003 takes this a step
further by trying to unify a bunch more error-handling across
different background process types. Together, 0002 and 0003 save >300
lines of code and as far as I can see there's basically no downside. I
think in general it would be good to strive towards a future where
everybody used the same signal handling and interrupt handling, but
these patches have the much less ambitious goal of just removing
overtly duplicated code. I think they might act as an inducement to
future patch authors to try to make things look more alike, though.
0004 is the main patch. It is substantially revised from Andres's
version, but the basic principle is along similar lines. Changes:
- I felt that it was imprudent to put some of the machinery into
PGPROC and the rest into the ProcSignal mechanism, because there is
nothing that says that everything with a PGPROC must also be a
ProcSignal participant. So I moved everything over into the ProcSignal
mechanism. This seems a lot safer to me.
- I accordingly renamed this thing from GlobalBarrier to
ProcSignalBarrier. This is a lot more arguable, but as of now I favor
it.
- There details of the synchronization are different: there's no
ProcSignalKind for this new kind of barrier any more, and there's now
only one pg_memory_barrier() and it's in a different place than Andres
had it. It is quite possible that I have screwed things up here, but I
couldn't make heads or tails of the way Andres had it. I don't know
whether that's because it was a POC or because I'm dumb. Also, moving
everything over into ProcSignal seemed to me to suggest some
rejiggering here that might've made less sense in the old scheme.
- I changed the code that waits for a barrier to use a latch wait,
because I think that's our usual convention. So wait-for-barrier gets
woken up early if the latch is set, and also itself manipulates the
latch. I also gave it a variable-length timeout, because when testing
it irked me that the same test randomly took either 0s or 1s. Now it
takes either 0s or 125ms, which is a lot less noticeable.
- I changed the kind of barrier provided by the patch from "checksum"
to "sample".
- Otherwise hacked on comments and naming somewhat.
This patch probably needs some more changes before commit. One likely
candidate: the use of a sample barrier type here probably needs to be
replaced with something else. But the way this is set up doesn't
really lend itself to starting with 0 types of barrier and then adding
them later, so I am not sure exactly what to do to make this patch
independent of what follows. The callback proposal I made before
could've accommodated that (as well as, perhaps, some automated
testing by dynamically loading up a barrier type) but Andres didn't
like that. Perhaps he'll relent, or have a counter-proposal, or
someone else will feel differently.
0005 is test code that sends and waits for a sample barrier.
Comments?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
v2-0005-Not-for-commit-test-code.patchapplication/octet-stream; name=v2-0005-Not-for-commit-test-code.patchDownload
From 19feb5a34b9d79d4052b4ad5b499e3d19bc328b3 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 2 Dec 2019 11:53:25 -0500
Subject: [PATCH v2 5/5] Not for commit: test code.
---
src/backend/storage/ipc/signalfuncs.c | 11 +++++++++++
src/include/catalog/pg_proc.dat | 5 +++++
2 files changed, 16 insertions(+)
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index ade8d713aa..f6deebcb5a 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -215,3 +215,14 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
PG_RETURN_BOOL(true);
}
+
+Datum
+sample_barrier(PG_FUNCTION_ARGS)
+{
+ uint64 bgen;
+
+ bgen = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_SAMPLE);
+ elog(NOTICE, "emitted barrier");
+ WaitForProcSignalBarrier(bgen);
+ PG_RETURN_VOID();
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ac8f64b219..380bdfea45 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10729,4 +10729,9 @@
proname => 'pg_partition_root', prorettype => 'regclass',
proargtypes => 'regclass', prosrc => 'pg_partition_root' },
+# function to get the top-most partition root parent
+{ oid => '9758', descr => 'send sample barrier',
+ proname => 'sample_barrier', prorettype => 'void',
+ proargtypes => '', prosrc => 'sample_barrier' },
+
]
--
2.17.2 (Apple Git-113)
v2-0001-Move-interrupting-handling-code-into-subroutines.patchapplication/octet-stream; name=v2-0001-Move-interrupting-handling-code-into-subroutines.patchDownload
From 033e790f544d107a914aab432bca7b9c82ac8f41 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 26 Nov 2019 13:14:13 -0500
Subject: [PATCH v2 1/5] Move interrupting-handling code into subroutines.
Some auxiliary processes, as well as the autovacuum launcher,
have interrupt handling code directly in their main loops.
Try to abstract things a little better by moving it into
separate functions.
This doesn't make any functional difference, and leaves
in place relatively large differences among processes in how
interrupts are handled, but hopefully it at least makes it
easier to see the commonalities and differences across
process types.
---
src/backend/postmaster/autovacuum.c | 72 +++++++++++++++++----------
src/backend/postmaster/bgwriter.c | 42 ++++++++++------
src/backend/postmaster/checkpointer.c | 71 ++++++++++++++------------
src/backend/postmaster/walwriter.c | 34 ++++++++-----
4 files changed, 133 insertions(+), 86 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1dd8168ca..5766203aaf 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -311,6 +311,8 @@ NON_EXEC_STATIC void AutoVacWorkerMain(int argc, char *argv[]) pg_attribute_nore
NON_EXEC_STATIC void AutoVacLauncherMain(int argc, char *argv[]) pg_attribute_noreturn();
static Oid do_start_worker(void);
+static void HandleAutoVacLauncherInterrupts(void);
+static void AutoVacLauncherShutdown() pg_attribute_noreturn();
static void launcher_determine_sleep(bool canlaunch, bool recursing,
struct timeval *nap);
static void launch_worker(TimestampTz now);
@@ -554,7 +556,7 @@ AutoVacLauncherMain(int argc, char *argv[])
/* if in shutdown mode, no need for anything further; just go away */
if (got_SIGTERM)
- goto shutdown;
+ AutoVacLauncherShutdown();
/*
* Sleep at least 1 second after any error. We don't want to be
@@ -649,30 +651,7 @@ AutoVacLauncherMain(int argc, char *argv[])
ResetLatch(MyLatch);
- /* Process sinval catchup interrupts that happened while sleeping */
- ProcessCatchupInterrupt();
-
- /* the normal shutdown case */
- if (got_SIGTERM)
- break;
-
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
-
- /* shutdown requested in config file? */
- if (!AutoVacuumingActive())
- break;
-
- /* rebalance in case the default cost parameters changed */
- LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
- autovac_balance_cost();
- LWLockRelease(AutovacuumLock);
-
- /* rebuild the list in case the naptime changed */
- rebuild_database_list(InvalidOid);
- }
+ HandleAutoVacLauncherInterrupts();
/*
* a worker finished, or postmaster signalled failure to start a
@@ -813,8 +792,47 @@ AutoVacLauncherMain(int argc, char *argv[])
}
}
- /* Normal exit from the autovac launcher is here */
-shutdown:
+ AutoVacLauncherShutdown();
+}
+
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleAutoVacLauncherInterrupts(void)
+{
+ /* the normal shutdown case */
+ if (got_SIGTERM)
+ AutoVacLauncherShutdown();
+
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ /* shutdown requested in config file? */
+ if (!AutoVacuumingActive())
+ AutoVacLauncherShutdown();
+
+ /* rebalance in case the default cost parameters changed */
+ LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
+ autovac_balance_cost();
+ LWLockRelease(AutovacuumLock);
+
+ /* rebuild the list in case the naptime changed */
+ rebuild_database_list(InvalidOid);
+ }
+
+ /* Process sinval catchup interrupts that happened while sleeping */
+ ProcessCatchupInterrupt();
+}
+
+/*
+ * Perform a normal exit from the autovac launcher.
+ */
+static void
+AutoVacLauncherShutdown()
+{
ereport(DEBUG1,
(errmsg("autovacuum launcher shutting down")));
AutoVacuumShmem->av_launcherpid = 0;
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 2fa631ea7a..c7500f1d71 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -92,6 +92,8 @@ static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
+static void HandleBackgroundWriterInterrupts(void);
+
/* Signal handlers */
static void bg_quickdie(SIGNAL_ARGS);
@@ -241,21 +243,7 @@ BackgroundWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
- if (shutdown_requested)
- {
- /*
- * From here on, elog(ERROR) should end with exit(1), not send
- * control back to the sigsetjmp block above
- */
- ExitOnAnyError = true;
- /* Normal exit from the bgwriter is here */
- proc_exit(0); /* done */
- }
+ HandleBackgroundWriterInterrupts();
/*
* Do one cycle of dirty-buffer writing.
@@ -369,6 +357,30 @@ BackgroundWriterMain(void)
}
}
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleBackgroundWriterInterrupts(void)
+{
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+
+ if (shutdown_requested)
+ {
+ /*
+ * From here on, elog(ERROR) should end with exit(1), not send
+ * control back to the sigsetjmp block above
+ */
+ ExitOnAnyError = true;
+ /* Normal exit from the bgwriter is here */
+ proc_exit(0); /* done */
+ }
+}
+
/* --------------------------------
* signal handler routines
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index d93c941871..d087ee9c74 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -169,6 +169,7 @@ static pg_time_t last_xlog_switch_time;
/* Prototypes for private functions */
+static void HandleCheckpointerInterrupts();
static void CheckArchiveTimeout(void);
static bool IsCheckpointOnSchedule(double progress);
static bool ImmediateCheckpointRequested(void);
@@ -350,37 +351,7 @@ CheckpointerMain(void)
* Process any requests or signals received recently.
*/
AbsorbSyncRequests();
-
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
-
- /*
- * Checkpointer is the last process to shut down, so we ask it to
- * hold the keys for a range of other tasks required most of which
- * have nothing to do with checkpointing at all.
- *
- * For various reasons, some config values can change dynamically
- * so the primary copy of them is held in shared memory to make
- * sure all backends see the same value. We make Checkpointer
- * responsible for updating the shared memory copy if the
- * parameter setting changes because of SIGHUP.
- */
- UpdateSharedMemoryConfig();
- }
- if (shutdown_requested)
- {
- /*
- * From here on, elog(ERROR) should end with exit(1), not send
- * control back to the sigsetjmp block above
- */
- ExitOnAnyError = true;
- /* Close down the database */
- ShutdownXLOG(0, 0);
- /* Normal exit from the checkpointer is here */
- proc_exit(0); /* done */
- }
+ HandleCheckpointerInterrupts();
/*
* Detect a pending checkpoint request by checking whether the flags
@@ -558,6 +529,44 @@ CheckpointerMain(void)
}
}
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleCheckpointerInterrupts(void)
+{
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ /*
+ * Checkpointer is the last process to shut down, so we ask it to
+ * hold the keys for a range of other tasks required most of which
+ * have nothing to do with checkpointing at all.
+ *
+ * For various reasons, some config values can change dynamically
+ * so the primary copy of them is held in shared memory to make
+ * sure all backends see the same value. We make Checkpointer
+ * responsible for updating the shared memory copy if the
+ * parameter setting changes because of SIGHUP.
+ */
+ UpdateSharedMemoryConfig();
+ }
+ if (shutdown_requested)
+ {
+ /*
+ * From here on, elog(ERROR) should end with exit(1), not send
+ * control back to the sigsetjmp block above
+ */
+ ExitOnAnyError = true;
+ /* Close down the database */
+ ShutdownXLOG(0, 0);
+ /* Normal exit from the checkpointer is here */
+ proc_exit(0); /* done */
+ }
+}
+
/*
* CheckArchiveTimeout -- check for archive_timeout and switch xlog files
*
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index cce9713408..5a3573503c 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -83,6 +83,8 @@ int WalWriterFlushAfter = 128;
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
+static void HandleWalWriterInterrupts(void);
+
/* Signal handlers */
static void wal_quickdie(SIGNAL_ARGS);
static void WalSigHupHandler(SIGNAL_ARGS);
@@ -242,19 +244,7 @@ WalWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- /*
- * Process any requests or signals received recently.
- */
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
- if (shutdown_requested)
- {
- /* Normal exit from the walwriter is here */
- proc_exit(0); /* done */
- }
+ HandleWalWriterInterrupts();
/*
* Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -282,6 +272,24 @@ WalWriterMain(void)
}
}
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleWalWriterInterrupts(void)
+{
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+ if (shutdown_requested)
+ {
+ /* Normal exit from the walwriter is here */
+ proc_exit(0); /* done */
+ }
+}
+
/* --------------------------------
* signal handler routines
--
2.17.2 (Apple Git-113)
v2-0002-Use-PostgresSigHupHandler-in-more-places.patchapplication/octet-stream; name=v2-0002-Use-PostgresSigHupHandler-in-more-places.patchDownload
From 8b6122932d9458015d3e468e4f30c2bffb7b9696 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 09:01:16 -0500
Subject: [PATCH v2 2/5] Use PostgresSigHupHandler in more places.
There seems to be no reason for every background process to have
its own flag indicating that a config-file reload is needed.
Instead, let's just use ConfigFilePending for that purpose
everywhere.
---
src/backend/postmaster/autovacuum.c | 30 ++++++----------------
src/backend/postmaster/bgwriter.c | 20 +++------------
src/backend/postmaster/checkpointer.c | 24 ++++-------------
src/backend/postmaster/pgarch.c | 25 ++++--------------
src/backend/postmaster/pgstat.c | 28 +++++---------------
src/backend/postmaster/walwriter.c | 20 +++------------
src/backend/replication/logical/launcher.c | 23 +++--------------
src/backend/replication/logical/worker.c | 23 +++--------------
8 files changed, 37 insertions(+), 156 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 5766203aaf..b1c2b0a01c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -138,7 +138,6 @@ static bool am_autovacuum_launcher = false;
static bool am_autovacuum_worker = false;
/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t got_SIGUSR2 = false;
static volatile sig_atomic_t got_SIGTERM = false;
@@ -344,7 +343,6 @@ static void perform_work_item(AutoVacuumWorkItem *workitem);
static void autovac_report_activity(autovac_table *tab);
static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
const char *nspname, const char *relname);
-static void av_sighup_handler(SIGNAL_ARGS);
static void avl_sigusr2_handler(SIGNAL_ARGS);
static void avl_sigterm_handler(SIGNAL_ARGS);
static void autovac_refresh_stats(void);
@@ -452,7 +450,7 @@ AutoVacLauncherMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, av_sighup_handler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGINT, StatementCancelHandler);
pqsignal(SIGTERM, avl_sigterm_handler);
@@ -805,9 +803,9 @@ HandleAutoVacLauncherInterrupts(void)
if (got_SIGTERM)
AutoVacLauncherShutdown();
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/* shutdown requested in config file? */
@@ -1405,18 +1403,6 @@ AutoVacWorkerFailed(void)
AutoVacuumShmem->av_signal[AutoVacForkFailed] = true;
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-av_sighup_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGUSR2: a worker is up and running, or just finished, or failed to fork */
static void
avl_sigusr2_handler(SIGNAL_ARGS)
@@ -1539,7 +1525,7 @@ AutoVacWorkerMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, av_sighup_handler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
/*
* SIGINT is used to signal canceling the current table's vacuum; SIGTERM
@@ -2332,9 +2318,9 @@ do_autovacuum(void)
/*
* Check for config changes before processing each collected table.
*/
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/*
@@ -2580,9 +2566,9 @@ deleted:
* Check for config changes before acquiring lock for further jobs.
*/
CHECK_FOR_INTERRUPTS();
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index c7500f1d71..bca46de6d5 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -89,7 +89,6 @@ static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
static void HandleBackgroundWriterInterrupts(void);
@@ -97,7 +96,6 @@ static void HandleBackgroundWriterInterrupts(void);
/* Signal handlers */
static void bg_quickdie(SIGNAL_ARGS);
-static void BgSigHupHandler(SIGNAL_ARGS);
static void ReqShutdownHandler(SIGNAL_ARGS);
@@ -118,7 +116,7 @@ BackgroundWriterMain(void)
/*
* Properly accept or ignore signals that might be sent to us.
*/
- pqsignal(SIGHUP, BgSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, ReqShutdownHandler); /* shutdown */
pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
@@ -363,9 +361,9 @@ BackgroundWriterMain(void)
static void
HandleBackgroundWriterInterrupts(void)
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -413,18 +411,6 @@ bg_quickdie(SIGNAL_ARGS)
_exit(2);
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-BgSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGTERM: set flag to shutdown and exit */
static void
ReqShutdownHandler(SIGNAL_ARGS)
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index d087ee9c74..9cf91b0d35 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -151,7 +151,6 @@ double CheckPointCompletionTarget = 0.5;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
/*
@@ -179,7 +178,6 @@ static void UpdateSharedMemoryConfig(void);
/* Signal handlers */
static void chkpt_quickdie(SIGNAL_ARGS);
-static void ChkptSigHupHandler(SIGNAL_ARGS);
static void ReqCheckpointHandler(SIGNAL_ARGS);
static void ReqShutdownHandler(SIGNAL_ARGS);
@@ -206,7 +204,7 @@ CheckpointerMain(void)
* want to wait for the backends to exit, whereupon the postmaster will
* tell us it's okay to shut down (via SIGUSR2).
*/
- pqsignal(SIGHUP, ChkptSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */
pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
pqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */
@@ -535,9 +533,9 @@ CheckpointerMain(void)
static void
HandleCheckpointerInterrupts(void)
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/*
@@ -685,9 +683,9 @@ CheckpointWriteDelay(int flags, double progress)
!ImmediateCheckpointRequested() &&
IsCheckpointOnSchedule(progress))
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/* update shmem copies of config variables */
UpdateSharedMemoryConfig();
@@ -835,18 +833,6 @@ chkpt_quickdie(SIGNAL_ARGS)
_exit(2);
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-ChkptSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGINT: set flag to run a normal checkpoint right away */
static void
ReqCheckpointHandler(SIGNAL_ARGS)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index f84f882c4c..09a9c66b4b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -83,7 +83,6 @@ static time_t last_sigterm_time = 0;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t got_SIGTERM = false;
static volatile sig_atomic_t wakened = false;
static volatile sig_atomic_t ready_to_stop = false;
@@ -98,7 +97,6 @@ static pid_t pgarch_forkexec(void);
NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn();
static void pgarch_exit(SIGNAL_ARGS);
-static void ArchSigHupHandler(SIGNAL_ARGS);
static void ArchSigTermHandler(SIGNAL_ARGS);
static void pgarch_waken(SIGNAL_ARGS);
static void pgarch_waken_stop(SIGNAL_ARGS);
@@ -229,7 +227,7 @@ PgArchiverMain(int argc, char *argv[])
* Ignore all signals usually bound to some action in the postmaster,
* except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
*/
- pqsignal(SIGHUP, ArchSigHupHandler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, ArchSigTermHandler);
pqsignal(SIGQUIT, pgarch_exit);
@@ -259,19 +257,6 @@ pgarch_exit(SIGNAL_ARGS)
exit(1);
}
-/* SIGHUP signal handler for archiver process */
-static void
-ArchSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- /* set flag to re-read config file at next convenient time */
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGTERM signal handler for archiver process */
static void
ArchSigTermHandler(SIGNAL_ARGS)
@@ -348,9 +333,9 @@ pgarch_MainLoop(void)
time_to_stop = ready_to_stop;
/* Check for config update */
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -457,9 +442,9 @@ pgarch_ArchiverCopyLoop(void)
* setting for archive_command as soon as possible, even if there
* is a backlog of files to be archived.
*/
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index fabcf31de8..96a9e09ced 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -264,7 +264,6 @@ static List *pending_write_requests = NIL;
/* Signal handler flags */
static volatile bool need_exit = false;
-static volatile bool got_SIGHUP = false;
/*
* Total time charged to functions so far in the current backend.
@@ -285,7 +284,6 @@ static pid_t pgstat_forkexec(void);
NON_EXEC_STATIC void PgstatCollectorMain(int argc, char *argv[]) pg_attribute_noreturn();
static void pgstat_exit(SIGNAL_ARGS);
static void pgstat_beshutdown_hook(int code, Datum arg);
-static void pgstat_sighup_handler(SIGNAL_ARGS);
static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create);
static PgStat_StatTabEntry *pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry,
@@ -4434,7 +4432,7 @@ PgstatCollectorMain(int argc, char *argv[])
* except SIGHUP and SIGQUIT. Note we don't need a SIGUSR1 handler to
* support latch operations, because we only use a local latch.
*/
- pqsignal(SIGHUP, pgstat_sighup_handler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, SIG_IGN);
pqsignal(SIGQUIT, pgstat_exit);
@@ -4466,10 +4464,10 @@ PgstatCollectorMain(int argc, char *argv[])
* message. (This effectively means that if backends are sending us stuff
* like mad, we won't notice postmaster death until things slack off a
* bit; which seems fine.) To do that, we have an inner loop that
- * iterates as long as recv() succeeds. We do recognize got_SIGHUP inside
- * the inner loop, which means that such interrupts will get serviced but
- * the latch won't get cleared until next time there is a break in the
- * action.
+ * iterates as long as recv() succeeds. We do check ConfigReloadPending
+ * inside the inner loop, which means that such interrupts will get
+ * serviced but the latch won't get cleared until next time there is a
+ * break in the action.
*/
for (;;)
{
@@ -4491,9 +4489,9 @@ PgstatCollectorMain(int argc, char *argv[])
/*
* Reload configuration if we got SIGHUP from the postmaster.
*/
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -4691,18 +4689,6 @@ pgstat_exit(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGHUP handler for collector process */
-static void
-pgstat_sighup_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* Subroutine to clear stats in a database entry
*
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 5a3573503c..599478530f 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -80,14 +80,12 @@ int WalWriterFlushAfter = 128;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
static void HandleWalWriterInterrupts(void);
/* Signal handlers */
static void wal_quickdie(SIGNAL_ARGS);
-static void WalSigHupHandler(SIGNAL_ARGS);
static void WalShutdownHandler(SIGNAL_ARGS);
/*
@@ -110,7 +108,7 @@ WalWriterMain(void)
* We have no particular use for SIGINT at the moment, but seems
* reasonable to treat like SIGTERM.
*/
- pqsignal(SIGHUP, WalSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, WalShutdownHandler); /* request shutdown */
pqsignal(SIGTERM, WalShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, wal_quickdie); /* hard crash time */
@@ -278,9 +276,9 @@ WalWriterMain(void)
static void
HandleWalWriterInterrupts(void)
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
if (shutdown_requested)
@@ -322,18 +320,6 @@ wal_quickdie(SIGNAL_ARGS)
_exit(2);
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-WalSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGTERM: set flag to exit normally */
static void
WalShutdownHandler(SIGNAL_ARGS)
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 4643af95fe..edca70c58c 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -92,9 +92,6 @@ static void logicalrep_worker_onexit(int code, Datum arg);
static void logicalrep_worker_detach(void);
static void logicalrep_worker_cleanup(LogicalRepWorker *worker);
-/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGHUP = false;
-
static bool on_commit_launcher_wakeup = false;
Datum pg_stat_get_subscription(PG_FUNCTION_ARGS);
@@ -714,20 +711,6 @@ logicalrep_worker_onexit(int code, Datum arg)
ApplyLauncherWakeup();
}
-/* SIGHUP: set flag to reload configuration at next convenient time */
-static void
-logicalrep_launcher_sighup(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
-
- /* Waken anything waiting on the process latch */
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* Count the number of registered (not necessarily running) sync workers
* for a subscription.
@@ -972,7 +955,7 @@ ApplyLauncherMain(Datum main_arg)
LogicalRepCtx->launcher_pid = MyProcPid;
/* Establish signal handlers. */
- pqsignal(SIGHUP, logicalrep_launcher_sighup);
+ pqsignal(SIGTERM, PostgresSigHupHandler);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
@@ -1061,9 +1044,9 @@ ApplyLauncherMain(Datum main_arg)
CHECK_FOR_INTERRUPTS();
}
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index ced0d191c2..3b12ad6400 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -111,9 +111,6 @@ static void store_flush_position(XLogRecPtr remote_lsn);
static void maybe_reread_subscription(void);
-/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGHUP = false;
-
/*
* Should this worker apply changes for given relation.
*
@@ -1270,9 +1267,9 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
CHECK_FOR_INTERRUPTS();
}
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -1563,20 +1560,6 @@ subscription_change_cb(Datum arg, int cacheid, uint32 hashvalue)
MySubscriptionValid = false;
}
-/* SIGHUP: set flag to reload configuration at next convenient time */
-static void
-logicalrep_worker_sighup(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
-
- /* Waken anything waiting on the process latch */
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* Logical Replication Apply worker entry point */
void
ApplyWorkerMain(Datum main_arg)
@@ -1592,7 +1575,7 @@ ApplyWorkerMain(Datum main_arg)
logicalrep_worker_attach(worker_slot);
/* Setup signal handling */
- pqsignal(SIGHUP, logicalrep_worker_sighup);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
--
2.17.2 (Apple Git-113)
v2-0003-Partially-deduplicate-interrupt-handling-for-back.patchapplication/octet-stream; name=v2-0003-Partially-deduplicate-interrupt-handling-for-back.patchDownload
From dc57da67787fcbd736d98b97faacd2903121bccd Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 11:19:37 -0500
Subject: [PATCH v2 3/5] Partially deduplicate interrupt handling for
background processes.
Where possible, share signal handler code and main loop interrupt
checking. This saves quite a bit of code and should simplify
maintenance, too.
This commit intends not to change the way anything works, even
though that might allow more code to be unified. It does unify
a bunch of individual variables into a ShutdownRequestPending
flag that has is now used by a bunch of different process types,
though.
---
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/autovacuum.c | 29 ++----
src/backend/postmaster/bgworker.c | 25 +----
src/backend/postmaster/bgwriter.c | 93 +-----------------
src/backend/postmaster/checkpointer.c | 60 ++----------
src/backend/postmaster/interrupt.c | 108 +++++++++++++++++++++
src/backend/postmaster/pgarch.c | 29 +-----
src/backend/postmaster/pgstat.c | 28 ++----
src/backend/postmaster/startup.c | 34 +------
src/backend/postmaster/walwriter.c | 84 ++--------------
src/backend/replication/logical/launcher.c | 3 +-
src/backend/replication/logical/worker.c | 3 +-
src/backend/replication/walreceiver.c | 31 +-----
src/backend/replication/walsender.c | 4 +-
src/backend/tcop/postgres.c | 22 +----
src/backend/utils/init/globals.c | 1 -
src/include/miscadmin.h | 3 -
src/include/postmaster/interrupt.h | 32 ++++++
18 files changed, 191 insertions(+), 399 deletions(-)
create mode 100644 src/backend/postmaster/interrupt.c
create mode 100644 src/include/postmaster/interrupt.h
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 03e3d3650a..bfdf6a833d 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -18,6 +18,7 @@ OBJS = \
bgwriter.o \
checkpointer.o \
fork_process.o \
+ interrupt.o \
pgarch.o \
pgstat.o \
postmaster.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index b1c2b0a01c..1792008ebe 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -84,6 +84,7 @@
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
@@ -139,7 +140,6 @@ static bool am_autovacuum_worker = false;
/* Flags set by signal handlers */
static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_SIGTERM = false;
/* Comparison points for determining whether freeze_max_age is exceeded */
static TransactionId recentXid;
@@ -344,7 +344,6 @@ static void autovac_report_activity(autovac_table *tab);
static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
const char *nspname, const char *relname);
static void avl_sigusr2_handler(SIGNAL_ARGS);
-static void avl_sigterm_handler(SIGNAL_ARGS);
static void autovac_refresh_stats(void);
@@ -450,9 +449,9 @@ AutoVacLauncherMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler);
- pqsignal(SIGTERM, avl_sigterm_handler);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
pqsignal(SIGQUIT, quickdie);
InitializeTimeouts(); /* establishes SIGALRM handler */
@@ -553,7 +552,7 @@ AutoVacLauncherMain(int argc, char *argv[])
RESUME_INTERRUPTS();
/* if in shutdown mode, no need for anything further; just go away */
- if (got_SIGTERM)
+ if (ShutdownRequestPending)
AutoVacLauncherShutdown();
/*
@@ -605,7 +604,7 @@ AutoVacLauncherMain(int argc, char *argv[])
*/
if (!AutoVacuumingActive())
{
- if (!got_SIGTERM)
+ if (!ShutdownRequestPending)
do_start_worker();
proc_exit(0); /* done */
}
@@ -622,7 +621,7 @@ AutoVacLauncherMain(int argc, char *argv[])
rebuild_database_list(InvalidOid);
/* loop until shutdown request */
- while (!got_SIGTERM)
+ while (!ShutdownRequestPending)
{
struct timeval nap;
TimestampTz current_time = 0;
@@ -800,7 +799,7 @@ static void
HandleAutoVacLauncherInterrupts(void)
{
/* the normal shutdown case */
- if (got_SIGTERM)
+ if (ShutdownRequestPending)
AutoVacLauncherShutdown();
if (ConfigReloadPending)
@@ -1415,18 +1414,6 @@ avl_sigusr2_handler(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGTERM: time to die */
-static void
-avl_sigterm_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGTERM = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/********************************************************************
* AUTOVACUUM WORKER CODE
@@ -1525,7 +1512,7 @@ AutoVacWorkerMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
/*
* SIGINT is used to signal canceling the current table's vacuum; SIGTERM
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 5f8a007e73..a4c347d524 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -12,14 +12,13 @@
#include "postgres.h"
-#include <unistd.h>
-
#include "access/parallel.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "postmaster/bgworker_internals.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
@@ -641,26 +640,6 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel)
return true;
}
-static void
-bgworker_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
/*
* Standard SIGTERM handler for background workers
*/
@@ -754,7 +733,7 @@ StartBackgroundWorker(void)
pqsignal(SIGTERM, bgworker_die);
pqsignal(SIGHUP, SIG_IGN);
- pqsignal(SIGQUIT, bgworker_quickdie);
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
InitializeTimeouts(); /* establishes SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index bca46de6d5..9cc343b74f 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -34,16 +34,13 @@
*/
#include "postgres.h"
-#include <signal.h>
-#include <sys/time.h>
-#include <unistd.h>
-
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
@@ -86,18 +83,6 @@ int BgWriterDelay = 200;
static TimestampTz last_snapshot_ts;
static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t shutdown_requested = false;
-
-static void HandleBackgroundWriterInterrupts(void);
-
-/* Signal handlers */
-
-static void bg_quickdie(SIGNAL_ARGS);
-static void ReqShutdownHandler(SIGNAL_ARGS);
-
/*
* Main entry point for bgwriter process
@@ -116,10 +101,10 @@ BackgroundWriterMain(void)
/*
* Properly accept or ignore signals that might be sent to us.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
- pqsignal(SIGTERM, ReqShutdownHandler); /* shutdown */
- pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
@@ -241,7 +226,7 @@ BackgroundWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- HandleBackgroundWriterInterrupts();
+ HandleMainLoopInterrupts();
/*
* Do one cycle of dirty-buffer writing.
@@ -354,71 +339,3 @@ BackgroundWriterMain(void)
prev_hibernate = can_hibernate;
}
}
-
-/*
- * Process any new interrupts.
- */
-static void
-HandleBackgroundWriterInterrupts(void)
-{
- if (ConfigReloadPending)
- {
- ConfigReloadPending = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
-
- if (shutdown_requested)
- {
- /*
- * From here on, elog(ERROR) should end with exit(1), not send
- * control back to the sigsetjmp block above
- */
- ExitOnAnyError = true;
- /* Normal exit from the bgwriter is here */
- proc_exit(0); /* done */
- }
-}
-
-
-/* --------------------------------
- * signal handler routines
- * --------------------------------
- */
-
-/*
- * bg_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-bg_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
-/* SIGTERM: set flag to shutdown and exit */
-static void
-ReqShutdownHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- shutdown_requested = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 9cf91b0d35..3f35b324c3 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -36,10 +36,7 @@
*/
#include "postgres.h"
-#include <signal.h>
#include <sys/time.h>
-#include <time.h>
-#include <unistd.h>
#include "access/xlog.h"
#include "access/xlog_internal.h"
@@ -47,6 +44,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
@@ -148,11 +146,6 @@ int CheckPointTimeout = 300;
int CheckPointWarning = 30;
double CheckPointCompletionTarget = 0.5;
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t shutdown_requested = false;
-
/*
* Private state
*/
@@ -176,10 +169,7 @@ static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
/* Signal handlers */
-
-static void chkpt_quickdie(SIGNAL_ARGS);
static void ReqCheckpointHandler(SIGNAL_ARGS);
-static void ReqShutdownHandler(SIGNAL_ARGS);
/*
@@ -204,14 +194,14 @@ CheckpointerMain(void)
* want to wait for the backends to exit, whereupon the postmaster will
* tell us it's okay to shut down (via SIGUSR2).
*/
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */
pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
- pqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
- pqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */
+ pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
/*
* Reset some signals that are accepted by postmaster but not here
@@ -551,7 +541,7 @@ HandleCheckpointerInterrupts(void)
*/
UpdateSharedMemoryConfig();
}
- if (shutdown_requested)
+ if (ShutdownRequestPending)
{
/*
* From here on, elog(ERROR) should end with exit(1), not send
@@ -679,7 +669,7 @@ CheckpointWriteDelay(int flags, double progress)
* in which case we just try to catch up as quickly as possible.
*/
if (!(flags & CHECKPOINT_IMMEDIATE) &&
- !shutdown_requested &&
+ !ShutdownRequestPending &&
!ImmediateCheckpointRequested() &&
IsCheckpointOnSchedule(progress))
{
@@ -807,32 +797,6 @@ IsCheckpointOnSchedule(double progress)
* --------------------------------
*/
-/*
- * chkpt_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-chkpt_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
/* SIGINT: set flag to run a normal checkpoint right away */
static void
ReqCheckpointHandler(SIGNAL_ARGS)
@@ -848,18 +812,6 @@ ReqCheckpointHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGUSR2: set flag to run a shutdown checkpoint and exit */
-static void
-ReqShutdownHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- shutdown_requested = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* --------------------------------
* communication with backends
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
new file mode 100644
index 0000000000..6900cd02f6
--- /dev/null
+++ b/src/backend/postmaster/interrupt.c
@@ -0,0 +1,108 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ * Interrupt handling routines.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "miscadmin.h"
+#include "postmaster/interrupt.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "utils/guc.h"
+
+volatile sig_atomic_t ConfigReloadPending = false;
+volatile sig_atomic_t ShutdownRequestPending = false;
+
+/*
+ * Simple interrupt handler for main loops of background processes.
+ */
+void
+HandleMainLoopInterrupts(void)
+{
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+
+ if (ShutdownRequestPending)
+ proc_exit(0);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the ConfigReloadPending flag at
+ * convenient places inside main loops, or else call HandleMainLoopInterrupts.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ ConfigReloadPending = true;
+ SetLatch(MyLatch);
+
+ errno = save_errno;
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+ /*
+ * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+ * because shared memory may be corrupted, so we don't want to try to
+ * clean up our transaction. Just nail the windows shut and get out of
+ * town. The callbacks wouldn't be safe to run from a signal handler,
+ * anyway.
+ *
+ * Note we do _exit(2) not _exit(0). This is to force the postmaster into
+ * a system reset cycle if someone sends a manual SIGQUIT to a random
+ * backend. This is necessary precisely because we don't clean up our
+ * shared memory state. (The "dead man switch" mechanism in pmsignal.c
+ * should ensure the postmaster sees this as a crash, too, but no harm in
+ * being doubly sure.)
+ */
+ _exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running background process to
+ * shut down and exit.
+ *
+ * Typically, this handler would be used for SIGTERM, but some procesess use
+ * other signals. In particular, the checkpointer exits on SIGUSR2, the
+ * stats collector on SIGQUIT, and the WAL writer exits on either SIGINT
+ * or SIGTERM.
+ *
+ * ShutdownRequestPending should be checked at a convenient place within the
+ * main loop, or else the main loop should call HandleMainLoopInterrupts.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ ShutdownRequestPending = true;
+ SetLatch(MyLatch);
+
+ errno = save_errno;
+}
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 09a9c66b4b..7824180ac7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -39,6 +39,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "postmaster/postmaster.h"
#include "storage/dsm.h"
@@ -83,7 +84,6 @@ static time_t last_sigterm_time = 0;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGTERM = false;
static volatile sig_atomic_t wakened = false;
static volatile sig_atomic_t ready_to_stop = false;
@@ -97,7 +97,6 @@ static pid_t pgarch_forkexec(void);
NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn();
static void pgarch_exit(SIGNAL_ARGS);
-static void ArchSigTermHandler(SIGNAL_ARGS);
static void pgarch_waken(SIGNAL_ARGS);
static void pgarch_waken_stop(SIGNAL_ARGS);
static void pgarch_MainLoop(void);
@@ -227,9 +226,9 @@ PgArchiverMain(int argc, char *argv[])
* Ignore all signals usually bound to some action in the postmaster,
* except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
- pqsignal(SIGTERM, ArchSigTermHandler);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
pqsignal(SIGQUIT, pgarch_exit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
@@ -257,24 +256,6 @@ pgarch_exit(SIGNAL_ARGS)
exit(1);
}
-/* SIGTERM signal handler for archiver process */
-static void
-ArchSigTermHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- /*
- * The postmaster never sends us SIGTERM, so we assume that this means
- * that init is trying to shut down the whole system. If we hang around
- * too long we'll get SIGKILL'd. Set flag to prevent starting any more
- * archive commands.
- */
- got_SIGTERM = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGUSR1 signal handler for archiver process */
static void
pgarch_waken(SIGNAL_ARGS)
@@ -346,7 +327,7 @@ pgarch_MainLoop(void)
* idea. If more than 60 seconds pass since SIGTERM, exit anyway, so
* that the postmaster can start a new archiver if needed.
*/
- if (got_SIGTERM)
+ if (ShutdownRequestPending)
{
time_t curtime = time(NULL);
@@ -434,7 +415,7 @@ pgarch_ArchiverCopyLoop(void)
* command, and the second is to avoid conflicts with another
* archiver spawned by a newer postmaster.
*/
- if (got_SIGTERM || !PostmasterIsAlive())
+ if (ShutdownRequestPending || !PostmasterIsAlive())
return;
/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 96a9e09ced..e931512203 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -49,6 +49,7 @@
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/walsender.h"
#include "storage/backendid.h"
@@ -262,9 +263,6 @@ static PgStat_GlobalStats globalStats;
*/
static List *pending_write_requests = NIL;
-/* Signal handler flags */
-static volatile bool need_exit = false;
-
/*
* Total time charged to functions so far in the current backend.
* We use this to help separate "self" and "other" time charges.
@@ -282,7 +280,6 @@ static pid_t pgstat_forkexec(void);
#endif
NON_EXEC_STATIC void PgstatCollectorMain(int argc, char *argv[]) pg_attribute_noreturn();
-static void pgstat_exit(SIGNAL_ARGS);
static void pgstat_beshutdown_hook(int code, Datum arg);
static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create);
@@ -4432,10 +4429,10 @@ PgstatCollectorMain(int argc, char *argv[])
* except SIGHUP and SIGQUIT. Note we don't need a SIGUSR1 handler to
* support latch operations, because we only use a local latch.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, SIG_IGN);
- pqsignal(SIGQUIT, pgstat_exit);
+ pqsignal(SIGQUIT, SignalHandlerForShutdownRequest);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, SIG_IGN);
@@ -4477,14 +4474,14 @@ PgstatCollectorMain(int argc, char *argv[])
/*
* Quit if we get SIGQUIT from the postmaster.
*/
- if (need_exit)
+ if (ShutdownRequestPending)
break;
/*
* Inner loop iterates as long as we keep getting messages, or until
- * need_exit becomes set.
+ * ShutdownRequestPending becomes set.
*/
- while (!need_exit)
+ while (!ShutdownRequestPending)
{
/*
* Reload configuration if we got SIGHUP from the postmaster.
@@ -4676,19 +4673,6 @@ PgstatCollectorMain(int argc, char *argv[])
exit(0);
}
-
-/* SIGQUIT signal handler for collector process */
-static void
-pgstat_exit(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- need_exit = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* Subroutine to clear stats in a database entry
*
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index f43e57dadb..4f59c71f73 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -19,13 +19,11 @@
*/
#include "postgres.h"
-#include <signal.h>
-#include <unistd.h>
-
#include "access/xlog.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -50,7 +48,6 @@ static volatile sig_atomic_t promote_triggered = false;
static volatile sig_atomic_t in_restore_command = false;
/* Signal handlers */
-static void startupproc_quickdie(SIGNAL_ARGS);
static void StartupProcTriggerHandler(SIGNAL_ARGS);
static void StartupProcSigHupHandler(SIGNAL_ARGS);
@@ -60,33 +57,6 @@ static void StartupProcSigHupHandler(SIGNAL_ARGS);
* --------------------------------
*/
-/*
- * startupproc_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-startupproc_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
-
/* SIGUSR2: set flag to finish recovery */
static void
StartupProcTriggerHandler(SIGNAL_ARGS)
@@ -167,7 +137,7 @@ StartupProcessMain(void)
pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
pqsignal(SIGINT, SIG_IGN); /* ignore query cancel */
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
- pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
InitializeTimeouts(); /* establishes SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 599478530f..38e9eb1304 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -48,6 +48,7 @@
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "postmaster/walwriter.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
@@ -77,17 +78,6 @@ int WalWriterFlushAfter = 128;
#define LOOPS_UNTIL_HIBERNATE 50
#define HIBERNATE_FACTOR 25
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t shutdown_requested = false;
-
-static void HandleWalWriterInterrupts(void);
-
-/* Signal handlers */
-static void wal_quickdie(SIGNAL_ARGS);
-static void WalShutdownHandler(SIGNAL_ARGS);
-
/*
* Main entry point for walwriter process
*
@@ -108,10 +98,10 @@ WalWriterMain(void)
* We have no particular use for SIGINT at the moment, but seems
* reasonable to treat like SIGTERM.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
- pqsignal(SIGINT, WalShutdownHandler); /* request shutdown */
- pqsignal(SIGTERM, WalShutdownHandler); /* request shutdown */
- pqsignal(SIGQUIT, wal_quickdie); /* hard crash time */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
@@ -242,7 +232,7 @@ WalWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- HandleWalWriterInterrupts();
+ HandleMainLoopInterrupts();
/*
* Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -269,65 +259,3 @@ WalWriterMain(void)
WAIT_EVENT_WAL_WRITER_MAIN);
}
}
-
-/*
- * Process any new interrupts.
- */
-static void
-HandleWalWriterInterrupts(void)
-{
- if (ConfigReloadPending)
- {
- ConfigReloadPending = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
- if (shutdown_requested)
- {
- /* Normal exit from the walwriter is here */
- proc_exit(0); /* done */
- }
-}
-
-
-/* --------------------------------
- * signal handler routines
- * --------------------------------
- */
-
-/*
- * wal_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-wal_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
-/* SIGTERM: set flag to exit normally */
-static void
-WalShutdownHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- shutdown_requested = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index edca70c58c..99cfa5d8b4 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -30,6 +30,7 @@
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
@@ -955,7 +956,7 @@ ApplyLauncherMain(Datum main_arg)
LogicalRepCtx->launcher_pid = MyProcPid;
/* Establish signal handlers. */
- pqsignal(SIGTERM, PostgresSigHupHandler);
+ pqsignal(SIGTERM, SignalHandlerForConfigReload);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 3b12ad6400..4bf6f5e427 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -45,6 +45,7 @@
#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "postmaster/walwriter.h"
#include "replication/decode.h"
@@ -1575,7 +1576,7 @@ ApplyWorkerMain(Datum main_arg)
logicalrep_worker_attach(worker_slot);
/* Setup signal handling */
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 72acb10767..06214b213b 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -43,7 +43,6 @@
*/
#include "postgres.h"
-#include <signal.h>
#include <unistd.h>
#include "access/htup_details.h"
@@ -58,6 +57,7 @@
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
@@ -127,7 +127,6 @@ static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
/* Signal handlers */
static void WalRcvSigHupHandler(SIGNAL_ARGS);
static void WalRcvShutdownHandler(SIGNAL_ARGS);
-static void WalRcvQuickDieHandler(SIGNAL_ARGS);
/*
@@ -249,7 +248,7 @@ WalReceiverMain(void)
pqsignal(SIGHUP, WalRcvSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, WalRcvShutdownHandler); /* request shutdown */
- pqsignal(SIGQUIT, WalRcvQuickDieHandler); /* hard crash time */
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
@@ -780,32 +779,6 @@ WalRcvShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/*
- * WalRcvQuickDieHandler() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm, so we need to stop what we're doing and
- * exit.
- */
-static void
-WalRcvQuickDieHandler(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we use _exit(2) not _exit(0). This is to force the postmaster
- * into a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
/*
* Accept the message from XLOG stream, and process it.
*/
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 8bafa65e50..1361f34198 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -66,6 +66,7 @@
#include "miscadmin.h"
#include "nodes/replnodes.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "replication/basebackup.h"
#include "replication/decode.h"
#include "replication/logical.h"
@@ -2962,8 +2963,7 @@ void
WalSndSignals(void)
{
/* Set up signal handlers */
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config
- * file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler); /* query cancel */
pqsignal(SIGTERM, die); /* request shutdown */
pqsignal(SIGQUIT, quickdie); /* hard crash time */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 3b85e48333..d894c3ddf4 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -57,6 +57,7 @@
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
@@ -2843,24 +2844,6 @@ FloatExceptionHandler(SIGNAL_ARGS)
"invalid operation, such as division by zero.")));
}
-/*
- * SIGHUP: set flag to re-read config file at next convenient time.
- *
- * Sets the ConfigReloadPending flag, which should be checked at convenient
- * places inside main loops. (Better than doing the reading in the signal
- * handler, ey?)
- */
-void
-PostgresSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- ConfigReloadPending = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* RecoveryConflictInterrupt: out-of-line portion of recovery conflict
* handling following receipt of SIGUSR1. Designed to be similar to die()
@@ -3809,8 +3792,7 @@ PostgresMain(int argc, char *argv[],
WalSndSignals();
else
{
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config
- * file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
pqsignal(SIGTERM, die); /* cancel current query and exit */
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 3bf96de256..4473e18e53 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,7 +32,6 @@ volatile sig_atomic_t QueryCancelPending = false;
volatile sig_atomic_t ProcDiePending = false;
volatile sig_atomic_t ClientConnectionLost = false;
volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t ConfigReloadPending = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 QueryCancelHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index bc6e03fbc7..24f43ad686 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,7 +82,6 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
@@ -279,8 +278,6 @@ extern void restore_stack_base(pg_stack_base_t base);
extern void check_stack_depth(void);
extern bool stack_is_too_deep(void);
-extern void PostgresSigHupHandler(SIGNAL_ARGS);
-
/* in tcop/utility.c */
extern void PreventCommandIfReadOnly(const char *cmdname);
extern void PreventCommandIfParallelMode(const char *cmdname);
diff --git a/src/include/postmaster/interrupt.h b/src/include/postmaster/interrupt.h
new file mode 100644
index 0000000000..8f4e43be20
--- /dev/null
+++ b/src/include/postmaster/interrupt.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ * Interrupt handling routines.
+ *
+ * Responses to interrupts are fairly varied and many types of backends
+ * have their own implementations, but we provide a few generic things
+ * here to facilitate code reuse.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/postmaster/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INTERRUPT_H
+#define INTERRUPT_H
+
+#include <signal.h>
+
+extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
+extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
+
+extern void HandleMainLoopInterrupts(void);
+extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
+extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
+extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+
+#endif
--
2.17.2 (Apple Git-113)
v2-0004-Extend-the-ProcSignal-mechanism-to-support-barrie.patchapplication/octet-stream; name=v2-0004-Extend-the-ProcSignal-mechanism-to-support-barrie.patchDownload
From e9bfcbc3a1b1aa4a01a5a281bc71c6f019f5d46b Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 11:29:25 -0500
Subject: [PATCH v2 4/5] Extend the ProcSignal mechanism to support barriers.
A new function EmitProcSignalBarrier() can be used to emit a global
barrier which all backends that participate in the ProcSignal
mechanism must absorb, and a new function WaitForProcSignalBarrier()
can be used to wait until all relevant backends have in fact
absorbed the barrier.
This can be used to coordinate global state changes, such as turning
checksums on while the system is running.
Andres Freund and Robert Haas
---
doc/src/sgml/monitoring.sgml | 4 +
src/backend/postmaster/autovacuum.c | 4 +
src/backend/postmaster/checkpointer.c | 7 +
src/backend/postmaster/interrupt.c | 4 +
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/startup.c | 6 +-
src/backend/replication/walreceiver.c | 3 +-
src/backend/storage/buffer/bufmgr.c | 10 +
src/backend/storage/ipc/procsignal.c | 289 +++++++++++++++++++++++++-
src/backend/tcop/postgres.c | 3 +
src/backend/utils/init/globals.c | 1 +
src/include/miscadmin.h | 1 +
src/include/pgstat.h | 1 +
src/include/storage/procsignal.h | 9 +
14 files changed, 332 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index a3c5f86b7e..76957c0a43 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1593,6 +1593,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<entry><literal>LogicalRewriteWrite</literal></entry>
<entry>Waiting for a write of logical rewrite mappings.</entry>
</row>
+ <row>
+ <entry><literal>ProcSignalBarrier</literal></entry>
+ <entry>Waiting for a barrier event to be processed by all backends.</entry>
+ </row>
<row>
<entry><literal>RelationMapRead</literal></entry>
<entry>Waiting for a read of the relation map file.</entry>
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1792008ebe..52ad99f716 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -820,6 +820,10 @@ HandleAutoVacLauncherInterrupts(void)
rebuild_database_list(InvalidOid);
}
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
/* Process sinval catchup interrupts that happened while sleeping */
ProcessCatchupInterrupt();
}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3f35b324c3..d1185cc135 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -523,6 +523,9 @@ CheckpointerMain(void)
static void
HandleCheckpointerInterrupts(void)
{
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ConfigReloadPending)
{
ConfigReloadPending = false;
@@ -709,6 +712,10 @@ CheckpointWriteDelay(int flags, double progress)
AbsorbSyncRequests();
absorb_counter = WRITES_PER_ABSORB;
}
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
/*
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index 6900cd02f6..214ccaf34b 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -20,6 +20,7 @@
#include "postmaster/interrupt.h"
#include "storage/ipc.h"
#include "storage/latch.h"
+#include "storage/procsignal.h"
#include "utils/guc.h"
volatile sig_atomic_t ConfigReloadPending = false;
@@ -31,6 +32,9 @@ volatile sig_atomic_t ShutdownRequestPending = false;
void
HandleMainLoopInterrupts(void)
{
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ConfigReloadPending)
{
ConfigReloadPending = false;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index e931512203..7410b2ff5e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3988,6 +3988,9 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_LOGICAL_REWRITE_WRITE:
event_name = "LogicalRewriteWrite";
break;
+ case WAIT_EVENT_PROC_SIGNAL_BARRIER:
+ event_name = "ProcSignalBarrier";
+ break;
case WAIT_EVENT_RELATION_MAP_READ:
event_name = "RelationMapRead";
break;
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 4f59c71f73..11f7052e78 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -96,7 +96,7 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/* Handle SIGHUP and SIGTERM signals of startup process */
+/* Handle various signals that might be sent to the startup process */
void
HandleStartupProcInterrupts(void)
{
@@ -121,6 +121,10 @@ HandleStartupProcInterrupts(void)
*/
if (IsUnderPostmaster && !PostmasterIsAlive())
exit(1);
+
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 06214b213b..5c23e7db2d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -148,7 +148,8 @@ ProcessWalRcvInterrupts(void)
/*
* Although walreceiver interrupt handling doesn't use the same scheme as
* regular backends, call CHECK_FOR_INTERRUPTS() to make sure we receive
- * any incoming signals on Win32.
+ * any incoming signals on Win32, and also to make sure we process any
+ * barrier events.
*/
CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7ad10736d5..1f10a97dc7 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1852,6 +1852,10 @@ BufferSync(int flags)
}
UnlockBufHdr(bufHdr, buf_state);
+
+ /* Check for barrier events in case NBuffers is large. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
if (num_to_scan == 0)
@@ -1930,6 +1934,10 @@ BufferSync(int flags)
}
s->num_to_scan++;
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
Assert(num_spaces > 0);
@@ -2018,6 +2026,8 @@ BufferSync(int flags)
/*
* Sleep to throttle our I/O rate.
+ *
+ * (This will check for barrier events even if it doesn't sleep.)
*/
CheckpointWriteDelay(flags, (double) num_processed / num_to_scan);
}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index fde97a1036..4c249d58b1 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -20,6 +20,7 @@
#include "access/parallel.h"
#include "commands/async.h"
#include "miscadmin.h"
+#include "pgstat.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -45,13 +46,36 @@
* The flags are actually declared as "volatile sig_atomic_t" for maximum
* portability. This should ensure that loads and stores of the flag
* values are atomic, allowing us to dispense with any explicit locking.
+ *
+ * pss_signalFlags are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal,
+ * but sometimes we need confirmation, as when making a global state change
+ * that cannot be considered complete until all backends have taken notice
+ * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * increment the current "barrier generation"; when the new barrier generation
+ * (or greater) appears in the pss_barrierGeneration flag of every process,
+ * we know that the message has been received everywhere.
*/
typedef struct
{
pid_t pss_pid;
sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
+ pg_atomic_uint64 pss_barrierGeneration;
+ pg_atomic_uint32 pss_barrierCheckMask;
} ProcSignalSlot;
+/*
+ * Information that is global to the entire ProcSignal system can be stored
+ * here.
+ *
+ * psh_barrierGeneration is the highest barrier generation in existence.
+ */
+typedef struct
+{
+ pg_atomic_uint64 psh_barrierGeneration;
+ ProcSignalSlot psh_slot[FLEXIBLE_ARRAY_MEMBER];
+} ProcSignalHeader;
+
/*
* We reserve a slot for each possible BackendId, plus one for each
* possible auxiliary process type. (This scheme assumes there is not
@@ -59,11 +83,16 @@ typedef struct
*/
#define NumProcSignalSlots (MaxBackends + NUM_AUXPROCTYPES)
-static ProcSignalSlot *ProcSignalSlots = NULL;
+/* Check whether the relevant type bit is set in the flags. */
+#define BARRIER_SHOULD_CHECK(flags, type) \
+ (((flags) & (((uint32) 1) << (uint32) (type))) != 0)
+
+static ProcSignalHeader *ProcSignal = NULL;
static volatile ProcSignalSlot *MyProcSignalSlot = NULL;
static bool CheckProcSignal(ProcSignalReason reason);
static void CleanupProcSignalState(int status, Datum arg);
+static void ProcessBarrierSample(void);
/*
* ProcSignalShmemSize
@@ -72,7 +101,11 @@ static void CleanupProcSignalState(int status, Datum arg);
Size
ProcSignalShmemSize(void)
{
- return NumProcSignalSlots * sizeof(ProcSignalSlot);
+ Size size;
+
+ size = mul_size(NumProcSignalSlots, sizeof(ProcSignalSlot));
+ size = add_size(size, offsetof(ProcSignalHeader, psh_slot));
+ return size;
}
/*
@@ -85,12 +118,26 @@ ProcSignalShmemInit(void)
Size size = ProcSignalShmemSize();
bool found;
- ProcSignalSlots = (ProcSignalSlot *)
- ShmemInitStruct("ProcSignalSlots", size, &found);
+ ProcSignal = (ProcSignalHeader *)
+ ShmemInitStruct("ProcSignal", size, &found);
- /* If we're first, set everything to zeroes */
+ /* If we're first, initialize. */
if (!found)
- MemSet(ProcSignalSlots, 0, size);
+ {
+ int i;
+
+ pg_atomic_init_u64(&ProcSignal->psh_barrierGeneration, 0);
+
+ for (i = 0; i < NumProcSignalSlots; ++i)
+ {
+ ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+
+ slot->pss_pid = 0;
+ MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
+ pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+ pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
+ }
+ }
}
/*
@@ -104,10 +151,11 @@ void
ProcSignalInit(int pss_idx)
{
volatile ProcSignalSlot *slot;
+ uint64 barrier_generation;
Assert(pss_idx >= 1 && pss_idx <= NumProcSignalSlots);
- slot = &ProcSignalSlots[pss_idx - 1];
+ slot = &ProcSignal->psh_slot[pss_idx - 1];
/* sanity check */
if (slot->pss_pid != 0)
@@ -117,6 +165,23 @@ ProcSignalInit(int pss_idx)
/* Clear out any leftover signal reasons */
MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
+ /*
+ * Initialize barrier state. Since we're a brand-new process, there
+ * shouldn't be any leftover backend-private state that needs to be
+ * updated. Therefore, we can broadcast the latest barrier generation
+ * and disregard any previously-set check bits.
+ *
+ * NB: This only works if this initialization happens early enough in the
+ * startup sequence that we haven't yet cached any state that might need
+ * to be invalidated. That's also why we have a memory barrier here, to
+ * be sure that any later reads of memory happen strictly after this.
+ */
+ pg_atomic_write_u32(&slot->pss_barrierCheckMask, 0);
+ barrier_generation =
+ pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation);
+ pg_memory_barrier();
+
/* Mark slot with my PID */
slot->pss_pid = MyProcPid;
@@ -129,7 +194,7 @@ ProcSignalInit(int pss_idx)
/*
* CleanupProcSignalState
- * Remove current process from ProcSignalSlots
+ * Remove current process from ProcSignal mechanism
*
* This function is called via on_shmem_exit() during backend shutdown.
*/
@@ -139,7 +204,7 @@ CleanupProcSignalState(int status, Datum arg)
int pss_idx = DatumGetInt32(arg);
volatile ProcSignalSlot *slot;
- slot = &ProcSignalSlots[pss_idx - 1];
+ slot = &ProcSignal->psh_slot[pss_idx - 1];
Assert(slot == MyProcSignalSlot);
/*
@@ -161,6 +226,12 @@ CleanupProcSignalState(int status, Datum arg)
return; /* XXX better to zero the slot anyway? */
}
+ /*
+ * Make this slot look like it's absorbed all possible barriers, so that
+ * no barrier waits block on it.
+ */
+ pg_atomic_write_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+
slot->pss_pid = 0;
}
@@ -182,7 +253,7 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
if (backendId != InvalidBackendId)
{
- slot = &ProcSignalSlots[backendId - 1];
+ slot = &ProcSignal->psh_slot[backendId - 1];
/*
* Note: Since there's no locking, it's possible that the target
@@ -212,7 +283,7 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
for (i = NumProcSignalSlots - 1; i >= 0; i--)
{
- slot = &ProcSignalSlots[i];
+ slot = &ProcSignal->psh_slot[i];
if (slot->pss_pid == pid)
{
@@ -230,6 +301,175 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
return -1;
}
+/*
+ * EmitProcSignalBarrier
+ * Send a signal to every Postgres process
+ *
+ * The return value of this function is the barrier "generation" created
+ * by this operation. This value can be passed to WaitForProcSignalBarrier
+ * to wait until it is known that every participant in the ProcSignal
+ * mechanism has absorbed the signal (or started afterwards).
+ *
+ * Note that it would be a bad idea to use this for anything that happens
+ * frequently, as interrupting every backend could cause a noticeable
+ * performance hit.
+ *
+ * Callers are entitled to assume that this function will not throw ERROR
+ * or FATAL.
+ */
+uint64
+EmitProcSignalBarrier(ProcSignalBarrierType type)
+{
+ uint64 flagbit = UINT64CONST(1) << (uint64) type;
+ uint64 generation;
+
+ /*
+ * Set all the flags.
+ *
+ * Note that pg_atomic_fetch_or_u32 has full barrier semantics, so this
+ * is totally ordered with respect to anything the caller did before, and
+ * anything that we do afterwards. (This is also true of the later call
+ * to pg_atomic_add_fetch_u64.)
+ */
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+
+ pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit);
+ }
+
+ /*
+ * Increment the generation counter.
+ */
+ generation =
+ pg_atomic_add_fetch_u64(&ProcSignal->psh_barrierGeneration, 1);
+
+ /*
+ * Signal all the processes, so that they update their advertised barrier
+ * generation.
+ *
+ * Concurrency is not a problem here. Backends that have exited don't
+ * matter, and new backends that have joined since we entered this function
+ * must already have current state, since the caller is responsible for
+ * making sure that the relevant state is entirely visible before calling
+ * this function in the first place. We still have to wake them up -
+ * because we can't distinguish between such backends and older backends
+ * that need to update state - but they won't actually need to change
+ * any state.
+ */
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+ pid_t pid = slot->pss_pid;
+
+ if (pid != 0)
+ kill(pid, SIGUSR1);
+ }
+
+ return generation;
+}
+
+/*
+ * WaitForProcSignalBarrier - wait until it is guaranteed that all changes
+ * requested by a specific call to EmitProcSignalBarrier() have taken effect.
+ *
+ * We expect that the barrier will normally be absorbed very quickly by other
+ * backends, so we start by waiting just 1/8 of a second and then back off
+ * by a factor of two every time we time out, to a maximum wait time of
+ * 1 second.
+ */
+void
+WaitForProcSignalBarrier(uint64 generation)
+{
+ long timeout = 125L;
+
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+ uint64 oldval;
+
+ oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ while (oldval < generation)
+ {
+ int events;
+
+ CHECK_FOR_INTERRUPTS();
+
+ events =
+ WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ timeout, WAIT_EVENT_PROC_SIGNAL_BARRIER);
+ ResetLatch(MyLatch);
+
+ oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ if (events & WL_TIMEOUT)
+ timeout = Min(timeout * 2, 1000L);
+ }
+ }
+}
+
+/*
+ * Perform global barrier related interrupt checking.
+ *
+ * Any backend that participates in ProcSignal signalling must arrange to
+ * call this function periodically. It is called from CHECK_FOR_INTERRUPTS(),
+ * which is enough for normal backends, but not necessarily for all types of
+ * background processes.
+ */
+void
+ProcessProcSignalBarrier(void)
+{
+ uint64 generation;
+ uint32 flags;
+
+ /* Exit quickly if there's no work to do. */
+ if (!ProcSignalBarrierPending)
+ return;
+ ProcSignalBarrierPending = false;
+
+ /*
+ * Read the current barrier generation, and then get the flags that
+ * are set for this backend. Note that pg_atomic_exchange_u32 is a full
+ * barrier, so we're guaranteed that the read of the barrier generation
+ * happens before we atomically extract the flags, and that any subsequent
+ * state changes happen afterward.
+ */
+ generation = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ flags = pg_atomic_exchange_u32(&MyProcSignalSlot->pss_barrierCheckMask, 0);
+
+ /*
+ * Process each type of barrier. It's important that nothing we call from
+ * here throws an error, because pss_barrierCheckMask has already been
+ * cleared. If we jumped out of here before processing all barrier types,
+ * then we'd forget about the need to do so later.
+ *
+ * NB: It ought to be OK to call the barrier-processing functions
+ * unconditionally, but it's more efficient to call only the ones that
+ * might need us to do something based on the flags.
+ */
+ if (BARRIER_SHOULD_CHECK(flags, PROCSIGNAL_BARRIER_SAMPLE))
+ ProcessBarrierSample();
+
+ /*
+ * State changes related to all types of barriers that might have been
+ * emitted have now been handled, so we can update our notion of the
+ * generation to the one we observed before beginning the updates. If
+ * things have changed further, it'll get fixed up when this function is
+ * next called.
+ */
+ pg_atomic_write_u64(&MyProcSignalSlot->pss_barrierGeneration, generation);
+}
+
+static void
+ProcessBarrierSample(void)
+{
+ /*
+ * XXX. This should be something no-fail, which elog() is not, but this is
+ * just for testing purposes.
+ */
+ elog(LOG, "ProcessBarrierSample");
+}
+
/*
* CheckProcSignal - check to see if a particular reason has been
* signaled, and clear the signal flag. Should be called after receiving
@@ -253,6 +493,27 @@ CheckProcSignal(ProcSignalReason reason)
return false;
}
+/*
+ * CheckProcSignalBarrier - check for new barriers we need to absorb
+ */
+static bool
+CheckProcSignalBarrier(void)
+{
+ volatile ProcSignalSlot *slot = MyProcSignalSlot;
+
+ if (slot != NULL)
+ {
+ uint64 mygen;
+ uint64 curgen;
+
+ mygen = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ curgen = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ return (mygen != curgen);
+ }
+
+ return false;
+}
+
/*
* procsignal_sigusr1_handler - handle SIGUSR1 signal.
*/
@@ -291,6 +552,12 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ if (CheckProcSignalBarrier())
+ {
+ InterruptPending = true;
+ ProcSignalBarrierPending = true;
+ }
+
SetLatch(MyLatch);
latch_sigusr1_handler();
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d894c3ddf4..25e22bd63b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3162,6 +3162,9 @@ ProcessInterrupts(void)
}
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ParallelMessagePending)
HandleParallelMessages();
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 4473e18e53..3a091022e2 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,6 +32,7 @@ volatile sig_atomic_t QueryCancelPending = false;
volatile sig_atomic_t ProcDiePending = false;
volatile sig_atomic_t ClientConnectionLost = false;
volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
+volatile sig_atomic_t ProcSignalBarrierPending = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 QueryCancelHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 24f43ad686..ed80f1d668 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,6 +82,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d823d..f2e873d048 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -908,6 +908,7 @@ typedef enum
WAIT_EVENT_LOGICAL_REWRITE_SYNC,
WAIT_EVENT_LOGICAL_REWRITE_TRUNCATE,
WAIT_EVENT_LOGICAL_REWRITE_WRITE,
+ WAIT_EVENT_PROC_SIGNAL_BARRIER,
WAIT_EVENT_RELATION_MAP_READ,
WAIT_EVENT_RELATION_MAP_SYNC,
WAIT_EVENT_RELATION_MAP_WRITE,
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 05b186a05c..90eda29d15 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -45,6 +45,11 @@ typedef enum
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
+typedef enum
+{
+ PROCSIGNAL_BARRIER_SAMPLE = 0
+} ProcSignalBarrierType;
+
/*
* prototypes for functions in procsignal.c
*/
@@ -55,6 +60,10 @@ extern void ProcSignalInit(int pss_idx);
extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
BackendId backendId);
+extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
+extern void WaitForProcSignalBarrier(uint64 generation);
+extern void ProcessProcSignalBarrier(void);
+
extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
#endif /* PROCSIGNAL_H */
--
2.17.2 (Apple Git-113)
Hi,
Thanks for the updated version!
On 2019-12-02 13:06:24 -0500, Robert Haas wrote:
On Sun, Nov 17, 2019 at 8:38 AM Magnus Hagander <magnus@hagander.net> wrote:
I'm definitely happy to work with it, but I did not and do not feel I have the skills for doing the "proper review" needed for it. So I am also very happy for you to pick it up and run with it.
OK, here's what I came up with.
0001 is just a code movement patch. It puts the interrupt handling for
each type of background process into a subroutine, instead of having
it all inline in the main loop. I feel this makes things more clear.
Hopefully it's uncontroversial.
I'd suggest marking the shutdown functions as noreturn. Might prevent
some compiler warnings in the future.
0002 and 0003 are optional and slightly off-topic as far as the matter
at hand, but they are not entirely unrelated and I believe that they
are good cleanups. 0002 changes assorted background processes to use
PostgresSigHupHandler and ConfigReloadPending rather than having a
bunch of separate global variables and a bunch of separate signal
handlers that basically do the same stuff.
Hm. Why "Postgres*"? I think that's confusing. So far only backend like
things are named like that, and I'd e.g. not call checkpointer that.
It's probably fine, but it's worthwhile to consider that that checking an
external variable like ConfigReloadPending is more expensive than a
static var like got_SIGHUP. The compiler will most of the time
understand the code flow to understand when something like got_SIGHUP
can change, but it can't assume anything like that with
ConfigReloadPending. But as we're forcing the compilers hand, by marking
the variable as volatile, so it's not likely to be a meaningful difference.
0003 takes this a step
further by trying to unify a bunch more error-handling across
different background process types. Together, 0002 and 0003 save >300
lines of code and as far as I can see there's basically no downside. I
think in general it would be good to strive towards a future where
everybody used the same signal handling and interrupt handling, but
these patches have the much less ambitious goal of just removing
overtly duplicated code. I think they might act as an inducement to
future patch authors to try to make things look more alike, though.
I do agree that this is a good goal. The amount of redundant code we
have across different types of processes is utterly ridiculous. While
obviously not a significant problem, there's also
0004 is the main patch. It is substantially revised from Andres's
version, but the basic principle is along similar lines. Changes:- I felt that it was imprudent to put some of the machinery into
PGPROC and the rest into the ProcSignal mechanism, because there is
nothing that says that everything with a PGPROC must also be a
ProcSignal participant. So I moved everything over into the ProcSignal
mechanism. This seems a lot safer to me.
I don't fully remember how I ended up with that split. But I think there
is an argument that putting the generation in PGPROC is good: That way
there are fewer changes to the shared cachelines in the ProcSignal. As I
think there'll be more modifications to barrierGen/barrierFlags (as I'd
named them) than the number of times they're read, that could be
beneficial in keeping the overhead low.
It's probably still worthwhile to go the way you have.
- There details of the synchronization are different: there's no
ProcSignalKind for this new kind of barrier any more
This I do not get. What's the point in making this unconditional?
, and there's now
only one pg_memory_barrier() and it's in a different place than Andres
had it. It is quite possible that I have screwed things up here, but I
couldn't make heads or tails of the way Andres had it. I don't know
whether that's because it was a POC or because I'm dumb. Also, moving
everything over into ProcSignal seemed to me to suggest some
rejiggering here that might've made less sense in the old scheme.
I don't remember enough to comment usefully on why things were the way
they are.
- I changed the code that waits for a barrier to use a latch wait,
because I think that's our usual convention. So wait-for-barrier gets
woken up early if the latch is set, and also itself manipulates the
latch. I also gave it a variable-length timeout, because when testing
it irked me that the same test randomly took either 0s or 1s. Now it
takes either 0s or 125ms, which is a lot less noticeable.
Sounds good.
From e9bfcbc3a1b1aa4a01a5a281bc71c6f019f5d46b Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 11:29:25 -0500
Subject: [PATCH v2 4/5] Extend the ProcSignal mechanism to support barriers.
+uint64 +EmitProcSignalBarrier(ProcSignalBarrierType type) +{ + uint64 flagbit = UINT64CONST(1) << (uint64) type; + uint64 generation; + + /* + * Set all the flags. + * + * Note that pg_atomic_fetch_or_u32 has full barrier semantics, so this + * is totally ordered with respect to anything the caller did before, and + * anything that we do afterwards. (This is also true of the later call + * to pg_atomic_add_fetch_u64.) + */ + for (int i = NumProcSignalSlots - 1; i >= 0; i--) + { + volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; + + pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit); + }
What's the reason for doing this in reverse? SendProcSignal() does it
because short circuits the search once a match is found, and it
theorizes they're at the end, but that doesn't apply here?
It could potentially be worthwhile to check whether all backends already
have the flag set, and in that case not increment the barrier
generation? Probably not worth it for now.
+/* + * WaitForProcSignalBarrier - wait until it is guaranteed that all changes + * requested by a specific call to EmitProcSignalBarrier() have taken effect. + * + * We expect that the barrier will normally be absorbed very quickly by other + * backends, so we start by waiting just 1/8 of a second and then back off + * by a factor of two every time we time out, to a maximum wait time of + * 1 second. + */ +void +WaitForProcSignalBarrier(uint64 generation) +{ + long timeout = 125L; + + for (int i = NumProcSignalSlots - 1; i >= 0; i--) + { + volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; + uint64 oldval; + + oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration); + while (oldval < generation)
Hm. without a barrier here, is it guaranteed that we can rely on the
state we need? IOW, could it be that we see an update to
pss_barrierGeneration to our desired generation, but still see
"published" state by some other backend that isn't yet in accordance to
the the generation? Seems we ought to have at least a read barrier
somewhere?
+/* + * Perform global barrier related interrupt checking. + * + * Any backend that participates in ProcSignal signalling must arrange to + * call this function periodically. It is called from CHECK_FOR_INTERRUPTS(), + * which is enough for normal backends, but not necessarily for all types of + * background processes. + */ +void +ProcessProcSignalBarrier(void) +{ + uint64 generation; + uint32 flags; + + /* Exit quickly if there's no work to do. */ + if (!ProcSignalBarrierPending) + return; + ProcSignalBarrierPending = false; + + /* + * Read the current barrier generation, and then get the flags that + * are set for this backend. Note that pg_atomic_exchange_u32 is a full + * barrier, so we're guaranteed that the read of the barrier generation + * happens before we atomically extract the flags, and that any subsequent + * state changes happen afterward. + */ + generation = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration); + flags = pg_atomic_exchange_u32(&MyProcSignalSlot->pss_barrierCheckMask, 0); + + /* + * Process each type of barrier. It's important that nothing we call from + * here throws an error, because pss_barrierCheckMask has already been + * cleared. If we jumped out of here before processing all barrier types, + * then we'd forget about the need to do so later. + * + * NB: It ought to be OK to call the barrier-processing functions + * unconditionally, but it's more efficient to call only the ones that + * might need us to do something based on the flags. + */ + if (BARRIER_SHOULD_CHECK(flags, PROCSIGNAL_BARRIER_SAMPLE)) + ProcessBarrierSample();
Hm. In my mental model it would be useful for barrier "processors" to
not acknowledge the state change at certain points. Imagine e.g. needing
to efficiently wait till all backends have processed a config file
reload - since we don't reload while a query is being processed, we
should be able to not ack the barrier at that point.
Greetings,
Andres Freund
On Fri, Dec 6, 2019 at 12:17 PM Andres Freund <andres@anarazel.de> wrote:
0001 is just a code movement patch. It puts the interrupt handling for
each type of background process into a subroutine, instead of having
it all inline in the main loop. I feel this makes things more clear.
Hopefully it's uncontroversial.I'd suggest marking the shutdown functions as noreturn. Might prevent
some compiler warnings in the future.
That patch only adds one shutdown function, and it's already marked as
noreturn, because it prevents a compiler warning in the present. So I
don't know what you're asking for here.
0002 and 0003 are optional and slightly off-topic as far as the matter
at hand, but they are not entirely unrelated and I believe that they
are good cleanups. 0002 changes assorted background processes to use
PostgresSigHupHandler and ConfigReloadPending rather than having a
bunch of separate global variables and a bunch of separate signal
handlers that basically do the same stuff.Hm. Why "Postgres*"? I think that's confusing. So far only backend like
things are named like that, and I'd e.g. not call checkpointer that.
Well, that's the existing function name, and 0003 changes it anyway.
It's probably fine, but it's worthwhile to consider that that checking an
external variable like ConfigReloadPending is more expensive than a
static var like got_SIGHUP. The compiler will most of the time
understand the code flow to understand when something like got_SIGHUP
can change, but it can't assume anything like that with
ConfigReloadPending. But as we're forcing the compilers hand, by marking
the variable as volatile, so it's not likely to be a meaningful difference.
Yeah, I think it's pretty much certain to be in the noise.
0003 takes this a step
further by trying to unify a bunch more error-handling across
different background process types. Together, 0002 and 0003 save >300
lines of code and as far as I can see there's basically no downside. I
think in general it would be good to strive towards a future where
everybody used the same signal handling and interrupt handling, but
these patches have the much less ambitious goal of just removing
overtly duplicated code. I think they might act as an inducement to
future patch authors to try to make things look more alike, though.I do agree that this is a good goal. The amount of redundant code we
have across different types of processes is utterly ridiculous. While
obviously not a significant problem, there's also
You seem to have omitted the end of this sentence.
0004 is the main patch. It is substantially revised from Andres's
version, but the basic principle is along similar lines. Changes:- I felt that it was imprudent to put some of the machinery into
PGPROC and the rest into the ProcSignal mechanism, because there is
nothing that says that everything with a PGPROC must also be a
ProcSignal participant. So I moved everything over into the ProcSignal
mechanism. This seems a lot safer to me.I don't fully remember how I ended up with that split. But I think there
is an argument that putting the generation in PGPROC is good: That way
there are fewer changes to the shared cachelines in the ProcSignal. As I
think there'll be more modifications to barrierGen/barrierFlags (as I'd
named them) than the number of times they're read, that could be
beneficial in keeping the overhead low.It's probably still worthwhile to go the way you have.
I think so. I think there's just too much chance for error/frustration
in having it in two separate mechanisms that might end up out of sync
with each other. We could pad the stuff within ProcSignal separately,
if required, but I'm hoping we're not going to be using this for
properties that change frequently.
- There details of the synchronization are different: there's no
ProcSignalKind for this new kind of barrier any moreThis I do not get. What's the point in making this unconditional?
I wasn't sure about this part, but Occam's razor argues for fewer
moving parts. As I have it, you need to make sure that the signal
handler is going to see our proc's generation as lower than the global
generation, and that when we go to process the interrupt (no longer in
the signal handler) we're going to be able to see the state changes
that we need to absorb. Now, if you also have a ProcSignal thing, then
you have three things to synchronize. When you see the flag set, you
need to be able to see the generations as unequal, and when you see
the generations as unequal, then you need to be able to see the state
changes that you need to absorb. That seems more complex, but it might
be worth it if the cost of comparing the barrier generations is too
much.
+uint64 +EmitProcSignalBarrier(ProcSignalBarrierType type) +{ + uint64 flagbit = UINT64CONST(1) << (uint64) type; + uint64 generation; + + /* + * Set all the flags. + * + * Note that pg_atomic_fetch_or_u32 has full barrier semantics, so this + * is totally ordered with respect to anything the caller did before, and + * anything that we do afterwards. (This is also true of the later call + * to pg_atomic_add_fetch_u64.) + */ + for (int i = NumProcSignalSlots - 1; i >= 0; i--) + { + volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; + + pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit); + }What's the reason for doing this in reverse? SendProcSignal() does it
because short circuits the search once a match is found, and it
theorizes they're at the end, but that doesn't apply here?
Mmm, this was just copy and paste. I can switch it around.
It could potentially be worthwhile to check whether all backends already
have the flag set, and in that case not increment the barrier
generation? Probably not worth it for now.
I don't think it's worth it now, or maybe ever. It seems unlikely to
come up, and it makes the synchronization more complex.
+void +WaitForProcSignalBarrier(uint64 generation) +{ + long timeout = 125L; + + for (int i = NumProcSignalSlots - 1; i >= 0; i--) + { + volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; + uint64 oldval; + + oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration); + while (oldval < generation)Hm. without a barrier here, is it guaranteed that we can rely on the
state we need? IOW, could it be that we see an update to
pss_barrierGeneration to our desired generation, but still see
"published" state by some other backend that isn't yet in accordance to
the the generation? Seems we ought to have at least a read barrier
somewhere?
Yeah, I think now that you mention it it should be a full barrier. The
caller might see that the barrier has been absorbed and immediately
perform either a read or a write.
Hm. In my mental model it would be useful for barrier "processors" to
not acknowledge the state change at certain points. Imagine e.g. needing
to efficiently wait till all backends have processed a config file
reload - since we don't reload while a query is being processed, we
should be able to not ack the barrier at that point.
Yeah, you mentioned this before, but I think we could leave it for
later. I think it's going to be complex. I suppose the way to do it
would be to add an argument to ProcessProcSignalBarrier() that lets
you specify which barrier events you're OK with processing from the
current point in the code. However, that will mean that whenever
somebody adds a new barrier type, they have got to go through all of
those callers and think about whether they should add their new
barrier type into the flags or not. If we try to do the specific thing
you're talking about with config-file processing, it will also mean
that we could be waiting MUCH longer for acknowledgements, which I
think might have pretty far-reaching ramifications. You might get
stuck waiting for that barrier to be absorbed for hours, and that
would also impact later barriers, since you won't see the generation
bump to 42 until it goes through 40 and 41. That sounds fairly awful.
You could try to work around it by looking at which barrier flags are
set, but I think that has ABA problems.
It seems sufficient to me for now to come up with something that can
be used for enabling checksums online and for the thing that got me
motivated to work on this, which is ALTER SYSTEM READ ONLY. If we can
meet the requirements of those two patches with this, I think we
should be happy. And I don't think we need the feature you're talking
about here for either of those use cases. If there's some simple
change you want to propose, we can certainly talk about that, but it
looks to me like you're talking about adding a fairly complex
mechanism that wouldn't actually get used for anything right away, and
I'd rather not go there.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
v3-0003-Partially-deduplicate-interrupt-handling-for-back.patchapplication/octet-stream; name=v3-0003-Partially-deduplicate-interrupt-handling-for-back.patchDownload
From cd84336070bb3246360678cd79457327df3808a0 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 11:19:37 -0500
Subject: [PATCH v3 3/5] Partially deduplicate interrupt handling for
background processes.
Where possible, share signal handler code and main loop interrupt
checking. This saves quite a bit of code and should simplify
maintenance, too.
This commit intends not to change the way anything works, even
though that might allow more code to be unified. It does unify
a bunch of individual variables into a ShutdownRequestPending
flag that has is now used by a bunch of different process types,
though.
---
src/backend/postmaster/Makefile | 1 +
src/backend/postmaster/autovacuum.c | 29 ++----
src/backend/postmaster/bgworker.c | 25 +----
src/backend/postmaster/bgwriter.c | 93 +-----------------
src/backend/postmaster/checkpointer.c | 60 ++----------
src/backend/postmaster/interrupt.c | 108 +++++++++++++++++++++
src/backend/postmaster/pgarch.c | 29 +-----
src/backend/postmaster/pgstat.c | 28 ++----
src/backend/postmaster/startup.c | 34 +------
src/backend/postmaster/walwriter.c | 84 ++--------------
src/backend/replication/logical/launcher.c | 3 +-
src/backend/replication/logical/worker.c | 3 +-
src/backend/replication/walreceiver.c | 31 +-----
src/backend/replication/walsender.c | 4 +-
src/backend/tcop/postgres.c | 22 +----
src/backend/utils/init/globals.c | 1 -
src/include/miscadmin.h | 3 -
src/include/postmaster/interrupt.h | 32 ++++++
18 files changed, 191 insertions(+), 399 deletions(-)
create mode 100644 src/backend/postmaster/interrupt.c
create mode 100644 src/include/postmaster/interrupt.h
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 03e3d3650a..bfdf6a833d 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -18,6 +18,7 @@ OBJS = \
bgwriter.o \
checkpointer.o \
fork_process.o \
+ interrupt.o \
pgarch.o \
pgstat.o \
postmaster.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index b1c2b0a01c..1792008ebe 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -84,6 +84,7 @@
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
@@ -139,7 +140,6 @@ static bool am_autovacuum_worker = false;
/* Flags set by signal handlers */
static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_SIGTERM = false;
/* Comparison points for determining whether freeze_max_age is exceeded */
static TransactionId recentXid;
@@ -344,7 +344,6 @@ static void autovac_report_activity(autovac_table *tab);
static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
const char *nspname, const char *relname);
static void avl_sigusr2_handler(SIGNAL_ARGS);
-static void avl_sigterm_handler(SIGNAL_ARGS);
static void autovac_refresh_stats(void);
@@ -450,9 +449,9 @@ AutoVacLauncherMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler);
- pqsignal(SIGTERM, avl_sigterm_handler);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
pqsignal(SIGQUIT, quickdie);
InitializeTimeouts(); /* establishes SIGALRM handler */
@@ -553,7 +552,7 @@ AutoVacLauncherMain(int argc, char *argv[])
RESUME_INTERRUPTS();
/* if in shutdown mode, no need for anything further; just go away */
- if (got_SIGTERM)
+ if (ShutdownRequestPending)
AutoVacLauncherShutdown();
/*
@@ -605,7 +604,7 @@ AutoVacLauncherMain(int argc, char *argv[])
*/
if (!AutoVacuumingActive())
{
- if (!got_SIGTERM)
+ if (!ShutdownRequestPending)
do_start_worker();
proc_exit(0); /* done */
}
@@ -622,7 +621,7 @@ AutoVacLauncherMain(int argc, char *argv[])
rebuild_database_list(InvalidOid);
/* loop until shutdown request */
- while (!got_SIGTERM)
+ while (!ShutdownRequestPending)
{
struct timeval nap;
TimestampTz current_time = 0;
@@ -800,7 +799,7 @@ static void
HandleAutoVacLauncherInterrupts(void)
{
/* the normal shutdown case */
- if (got_SIGTERM)
+ if (ShutdownRequestPending)
AutoVacLauncherShutdown();
if (ConfigReloadPending)
@@ -1415,18 +1414,6 @@ avl_sigusr2_handler(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGTERM: time to die */
-static void
-avl_sigterm_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGTERM = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/********************************************************************
* AUTOVACUUM WORKER CODE
@@ -1525,7 +1512,7 @@ AutoVacWorkerMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
/*
* SIGINT is used to signal canceling the current table's vacuum; SIGTERM
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 5f8a007e73..a4c347d524 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -12,14 +12,13 @@
#include "postgres.h"
-#include <unistd.h>
-
#include "access/parallel.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "postmaster/bgworker_internals.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
@@ -641,26 +640,6 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel)
return true;
}
-static void
-bgworker_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
/*
* Standard SIGTERM handler for background workers
*/
@@ -754,7 +733,7 @@ StartBackgroundWorker(void)
pqsignal(SIGTERM, bgworker_die);
pqsignal(SIGHUP, SIG_IGN);
- pqsignal(SIGQUIT, bgworker_quickdie);
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
InitializeTimeouts(); /* establishes SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index bca46de6d5..9cc343b74f 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -34,16 +34,13 @@
*/
#include "postgres.h"
-#include <signal.h>
-#include <sys/time.h>
-#include <unistd.h>
-
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
@@ -86,18 +83,6 @@ int BgWriterDelay = 200;
static TimestampTz last_snapshot_ts;
static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t shutdown_requested = false;
-
-static void HandleBackgroundWriterInterrupts(void);
-
-/* Signal handlers */
-
-static void bg_quickdie(SIGNAL_ARGS);
-static void ReqShutdownHandler(SIGNAL_ARGS);
-
/*
* Main entry point for bgwriter process
@@ -116,10 +101,10 @@ BackgroundWriterMain(void)
/*
* Properly accept or ignore signals that might be sent to us.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
- pqsignal(SIGTERM, ReqShutdownHandler); /* shutdown */
- pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
@@ -241,7 +226,7 @@ BackgroundWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- HandleBackgroundWriterInterrupts();
+ HandleMainLoopInterrupts();
/*
* Do one cycle of dirty-buffer writing.
@@ -354,71 +339,3 @@ BackgroundWriterMain(void)
prev_hibernate = can_hibernate;
}
}
-
-/*
- * Process any new interrupts.
- */
-static void
-HandleBackgroundWriterInterrupts(void)
-{
- if (ConfigReloadPending)
- {
- ConfigReloadPending = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
-
- if (shutdown_requested)
- {
- /*
- * From here on, elog(ERROR) should end with exit(1), not send
- * control back to the sigsetjmp block above
- */
- ExitOnAnyError = true;
- /* Normal exit from the bgwriter is here */
- proc_exit(0); /* done */
- }
-}
-
-
-/* --------------------------------
- * signal handler routines
- * --------------------------------
- */
-
-/*
- * bg_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-bg_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
-/* SIGTERM: set flag to shutdown and exit */
-static void
-ReqShutdownHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- shutdown_requested = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 9cf91b0d35..3f35b324c3 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -36,10 +36,7 @@
*/
#include "postgres.h"
-#include <signal.h>
#include <sys/time.h>
-#include <time.h>
-#include <unistd.h>
#include "access/xlog.h"
#include "access/xlog_internal.h"
@@ -47,6 +44,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
@@ -148,11 +146,6 @@ int CheckPointTimeout = 300;
int CheckPointWarning = 30;
double CheckPointCompletionTarget = 0.5;
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t shutdown_requested = false;
-
/*
* Private state
*/
@@ -176,10 +169,7 @@ static bool CompactCheckpointerRequestQueue(void);
static void UpdateSharedMemoryConfig(void);
/* Signal handlers */
-
-static void chkpt_quickdie(SIGNAL_ARGS);
static void ReqCheckpointHandler(SIGNAL_ARGS);
-static void ReqShutdownHandler(SIGNAL_ARGS);
/*
@@ -204,14 +194,14 @@ CheckpointerMain(void)
* want to wait for the backends to exit, whereupon the postmaster will
* tell us it's okay to shut down (via SIGUSR2).
*/
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */
pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
- pqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
- pqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */
+ pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
/*
* Reset some signals that are accepted by postmaster but not here
@@ -551,7 +541,7 @@ HandleCheckpointerInterrupts(void)
*/
UpdateSharedMemoryConfig();
}
- if (shutdown_requested)
+ if (ShutdownRequestPending)
{
/*
* From here on, elog(ERROR) should end with exit(1), not send
@@ -679,7 +669,7 @@ CheckpointWriteDelay(int flags, double progress)
* in which case we just try to catch up as quickly as possible.
*/
if (!(flags & CHECKPOINT_IMMEDIATE) &&
- !shutdown_requested &&
+ !ShutdownRequestPending &&
!ImmediateCheckpointRequested() &&
IsCheckpointOnSchedule(progress))
{
@@ -807,32 +797,6 @@ IsCheckpointOnSchedule(double progress)
* --------------------------------
*/
-/*
- * chkpt_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-chkpt_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
/* SIGINT: set flag to run a normal checkpoint right away */
static void
ReqCheckpointHandler(SIGNAL_ARGS)
@@ -848,18 +812,6 @@ ReqCheckpointHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGUSR2: set flag to run a shutdown checkpoint and exit */
-static void
-ReqShutdownHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- shutdown_requested = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* --------------------------------
* communication with backends
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
new file mode 100644
index 0000000000..6900cd02f6
--- /dev/null
+++ b/src/backend/postmaster/interrupt.c
@@ -0,0 +1,108 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ * Interrupt handling routines.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "miscadmin.h"
+#include "postmaster/interrupt.h"
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "utils/guc.h"
+
+volatile sig_atomic_t ConfigReloadPending = false;
+volatile sig_atomic_t ShutdownRequestPending = false;
+
+/*
+ * Simple interrupt handler for main loops of background processes.
+ */
+void
+HandleMainLoopInterrupts(void)
+{
+ if (ConfigReloadPending)
+ {
+ ConfigReloadPending = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+
+ if (ShutdownRequestPending)
+ proc_exit(0);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the ConfigReloadPending flag at
+ * convenient places inside main loops, or else call HandleMainLoopInterrupts.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ ConfigReloadPending = true;
+ SetLatch(MyLatch);
+
+ errno = save_errno;
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+ /*
+ * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+ * because shared memory may be corrupted, so we don't want to try to
+ * clean up our transaction. Just nail the windows shut and get out of
+ * town. The callbacks wouldn't be safe to run from a signal handler,
+ * anyway.
+ *
+ * Note we do _exit(2) not _exit(0). This is to force the postmaster into
+ * a system reset cycle if someone sends a manual SIGQUIT to a random
+ * backend. This is necessary precisely because we don't clean up our
+ * shared memory state. (The "dead man switch" mechanism in pmsignal.c
+ * should ensure the postmaster sees this as a crash, too, but no harm in
+ * being doubly sure.)
+ */
+ _exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running background process to
+ * shut down and exit.
+ *
+ * Typically, this handler would be used for SIGTERM, but some procesess use
+ * other signals. In particular, the checkpointer exits on SIGUSR2, the
+ * stats collector on SIGQUIT, and the WAL writer exits on either SIGINT
+ * or SIGTERM.
+ *
+ * ShutdownRequestPending should be checked at a convenient place within the
+ * main loop, or else the main loop should call HandleMainLoopInterrupts.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+ int save_errno = errno;
+
+ ShutdownRequestPending = true;
+ SetLatch(MyLatch);
+
+ errno = save_errno;
+}
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 09a9c66b4b..7824180ac7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -39,6 +39,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "postmaster/postmaster.h"
#include "storage/dsm.h"
@@ -83,7 +84,6 @@ static time_t last_sigterm_time = 0;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGTERM = false;
static volatile sig_atomic_t wakened = false;
static volatile sig_atomic_t ready_to_stop = false;
@@ -97,7 +97,6 @@ static pid_t pgarch_forkexec(void);
NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn();
static void pgarch_exit(SIGNAL_ARGS);
-static void ArchSigTermHandler(SIGNAL_ARGS);
static void pgarch_waken(SIGNAL_ARGS);
static void pgarch_waken_stop(SIGNAL_ARGS);
static void pgarch_MainLoop(void);
@@ -227,9 +226,9 @@ PgArchiverMain(int argc, char *argv[])
* Ignore all signals usually bound to some action in the postmaster,
* except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
- pqsignal(SIGTERM, ArchSigTermHandler);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
pqsignal(SIGQUIT, pgarch_exit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
@@ -257,24 +256,6 @@ pgarch_exit(SIGNAL_ARGS)
exit(1);
}
-/* SIGTERM signal handler for archiver process */
-static void
-ArchSigTermHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- /*
- * The postmaster never sends us SIGTERM, so we assume that this means
- * that init is trying to shut down the whole system. If we hang around
- * too long we'll get SIGKILL'd. Set flag to prevent starting any more
- * archive commands.
- */
- got_SIGTERM = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGUSR1 signal handler for archiver process */
static void
pgarch_waken(SIGNAL_ARGS)
@@ -346,7 +327,7 @@ pgarch_MainLoop(void)
* idea. If more than 60 seconds pass since SIGTERM, exit anyway, so
* that the postmaster can start a new archiver if needed.
*/
- if (got_SIGTERM)
+ if (ShutdownRequestPending)
{
time_t curtime = time(NULL);
@@ -434,7 +415,7 @@ pgarch_ArchiverCopyLoop(void)
* command, and the second is to avoid conflicts with another
* archiver spawned by a newer postmaster.
*/
- if (got_SIGTERM || !PostmasterIsAlive())
+ if (ShutdownRequestPending || !PostmasterIsAlive())
return;
/*
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 96a9e09ced..e931512203 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -49,6 +49,7 @@
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/walsender.h"
#include "storage/backendid.h"
@@ -262,9 +263,6 @@ static PgStat_GlobalStats globalStats;
*/
static List *pending_write_requests = NIL;
-/* Signal handler flags */
-static volatile bool need_exit = false;
-
/*
* Total time charged to functions so far in the current backend.
* We use this to help separate "self" and "other" time charges.
@@ -282,7 +280,6 @@ static pid_t pgstat_forkexec(void);
#endif
NON_EXEC_STATIC void PgstatCollectorMain(int argc, char *argv[]) pg_attribute_noreturn();
-static void pgstat_exit(SIGNAL_ARGS);
static void pgstat_beshutdown_hook(int code, Datum arg);
static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create);
@@ -4432,10 +4429,10 @@ PgstatCollectorMain(int argc, char *argv[])
* except SIGHUP and SIGQUIT. Note we don't need a SIGUSR1 handler to
* support latch operations, because we only use a local latch.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, SIG_IGN);
- pqsignal(SIGQUIT, pgstat_exit);
+ pqsignal(SIGQUIT, SignalHandlerForShutdownRequest);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, SIG_IGN);
@@ -4477,14 +4474,14 @@ PgstatCollectorMain(int argc, char *argv[])
/*
* Quit if we get SIGQUIT from the postmaster.
*/
- if (need_exit)
+ if (ShutdownRequestPending)
break;
/*
* Inner loop iterates as long as we keep getting messages, or until
- * need_exit becomes set.
+ * ShutdownRequestPending becomes set.
*/
- while (!need_exit)
+ while (!ShutdownRequestPending)
{
/*
* Reload configuration if we got SIGHUP from the postmaster.
@@ -4676,19 +4673,6 @@ PgstatCollectorMain(int argc, char *argv[])
exit(0);
}
-
-/* SIGQUIT signal handler for collector process */
-static void
-pgstat_exit(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- need_exit = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* Subroutine to clear stats in a database entry
*
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index f43e57dadb..4f59c71f73 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -19,13 +19,11 @@
*/
#include "postgres.h"
-#include <signal.h>
-#include <unistd.h>
-
#include "access/xlog.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -50,7 +48,6 @@ static volatile sig_atomic_t promote_triggered = false;
static volatile sig_atomic_t in_restore_command = false;
/* Signal handlers */
-static void startupproc_quickdie(SIGNAL_ARGS);
static void StartupProcTriggerHandler(SIGNAL_ARGS);
static void StartupProcSigHupHandler(SIGNAL_ARGS);
@@ -60,33 +57,6 @@ static void StartupProcSigHupHandler(SIGNAL_ARGS);
* --------------------------------
*/
-/*
- * startupproc_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-startupproc_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
-
/* SIGUSR2: set flag to finish recovery */
static void
StartupProcTriggerHandler(SIGNAL_ARGS)
@@ -167,7 +137,7 @@ StartupProcessMain(void)
pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
pqsignal(SIGINT, SIG_IGN); /* ignore query cancel */
pqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */
- pqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
InitializeTimeouts(); /* establishes SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 599478530f..38e9eb1304 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -48,6 +48,7 @@
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "postmaster/walwriter.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
@@ -77,17 +78,6 @@ int WalWriterFlushAfter = 128;
#define LOOPS_UNTIL_HIBERNATE 50
#define HIBERNATE_FACTOR 25
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t shutdown_requested = false;
-
-static void HandleWalWriterInterrupts(void);
-
-/* Signal handlers */
-static void wal_quickdie(SIGNAL_ARGS);
-static void WalShutdownHandler(SIGNAL_ARGS);
-
/*
* Main entry point for walwriter process
*
@@ -108,10 +98,10 @@ WalWriterMain(void)
* We have no particular use for SIGINT at the moment, but seems
* reasonable to treat like SIGTERM.
*/
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
- pqsignal(SIGINT, WalShutdownHandler); /* request shutdown */
- pqsignal(SIGTERM, WalShutdownHandler); /* request shutdown */
- pqsignal(SIGQUIT, wal_quickdie); /* hard crash time */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
+ pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+ pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
@@ -242,7 +232,7 @@ WalWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- HandleWalWriterInterrupts();
+ HandleMainLoopInterrupts();
/*
* Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -269,65 +259,3 @@ WalWriterMain(void)
WAIT_EVENT_WAL_WRITER_MAIN);
}
}
-
-/*
- * Process any new interrupts.
- */
-static void
-HandleWalWriterInterrupts(void)
-{
- if (ConfigReloadPending)
- {
- ConfigReloadPending = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
- if (shutdown_requested)
- {
- /* Normal exit from the walwriter is here */
- proc_exit(0); /* done */
- }
-}
-
-
-/* --------------------------------
- * signal handler routines
- * --------------------------------
- */
-
-/*
- * wal_quickdie() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm,
- * so we need to stop what we're doing and exit.
- */
-static void
-wal_quickdie(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we do _exit(2) not _exit(0). This is to force the postmaster into
- * a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
-/* SIGTERM: set flag to exit normally */
-static void
-WalShutdownHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- shutdown_requested = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index edca70c58c..99cfa5d8b4 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -30,6 +30,7 @@
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
@@ -955,7 +956,7 @@ ApplyLauncherMain(Datum main_arg)
LogicalRepCtx->launcher_pid = MyProcPid;
/* Establish signal handlers. */
- pqsignal(SIGTERM, PostgresSigHupHandler);
+ pqsignal(SIGTERM, SignalHandlerForConfigReload);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 3b12ad6400..4bf6f5e427 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -45,6 +45,7 @@
#include "parser/parse_relation.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "postmaster/walwriter.h"
#include "replication/decode.h"
@@ -1575,7 +1576,7 @@ ApplyWorkerMain(Datum main_arg)
logicalrep_worker_attach(worker_slot);
/* Setup signal handling */
- pqsignal(SIGHUP, PostgresSigHupHandler);
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c1e439adb4..c36bcc08ec 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -43,7 +43,6 @@
*/
#include "postgres.h"
-#include <signal.h>
#include <unistd.h>
#include "access/htup_details.h"
@@ -58,6 +57,7 @@
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
@@ -127,7 +127,6 @@ static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
/* Signal handlers */
static void WalRcvSigHupHandler(SIGNAL_ARGS);
static void WalRcvShutdownHandler(SIGNAL_ARGS);
-static void WalRcvQuickDieHandler(SIGNAL_ARGS);
/*
@@ -249,7 +248,7 @@ WalReceiverMain(void)
pqsignal(SIGHUP, WalRcvSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, WalRcvShutdownHandler); /* request shutdown */
- pqsignal(SIGQUIT, WalRcvQuickDieHandler); /* hard crash time */
+ pqsignal(SIGQUIT, SignalHandlerForCrashExit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
@@ -780,32 +779,6 @@ WalRcvShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/*
- * WalRcvQuickDieHandler() occurs when signalled SIGQUIT by the postmaster.
- *
- * Some backend has bought the farm, so we need to stop what we're doing and
- * exit.
- */
-static void
-WalRcvQuickDieHandler(SIGNAL_ARGS)
-{
- /*
- * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
- * because shared memory may be corrupted, so we don't want to try to
- * clean up our transaction. Just nail the windows shut and get out of
- * town. The callbacks wouldn't be safe to run from a signal handler,
- * anyway.
- *
- * Note we use _exit(2) not _exit(0). This is to force the postmaster
- * into a system reset cycle if someone sends a manual SIGQUIT to a random
- * backend. This is necessary precisely because we don't clean up our
- * shared memory state. (The "dead man switch" mechanism in pmsignal.c
- * should ensure the postmaster sees this as a crash, too, but no harm in
- * being doubly sure.)
- */
- _exit(2);
-}
-
/*
* Accept the message from XLOG stream, and process it.
*/
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 8b2a2be1c0..8e74177de4 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -66,6 +66,7 @@
#include "miscadmin.h"
#include "nodes/replnodes.h"
#include "pgstat.h"
+#include "postmaster/interrupt.h"
#include "replication/basebackup.h"
#include "replication/decode.h"
#include "replication/logical.h"
@@ -2969,8 +2970,7 @@ void
WalSndSignals(void)
{
/* Set up signal handlers */
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config
- * file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler); /* query cancel */
pqsignal(SIGTERM, die); /* request shutdown */
pqsignal(SIGQUIT, quickdie); /* hard crash time */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 3b85e48333..d894c3ddf4 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -57,6 +57,7 @@
#include "pg_trace.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
+#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
@@ -2843,24 +2844,6 @@ FloatExceptionHandler(SIGNAL_ARGS)
"invalid operation, such as division by zero.")));
}
-/*
- * SIGHUP: set flag to re-read config file at next convenient time.
- *
- * Sets the ConfigReloadPending flag, which should be checked at convenient
- * places inside main loops. (Better than doing the reading in the signal
- * handler, ey?)
- */
-void
-PostgresSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- ConfigReloadPending = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* RecoveryConflictInterrupt: out-of-line portion of recovery conflict
* handling following receipt of SIGUSR1. Designed to be similar to die()
@@ -3809,8 +3792,7 @@ PostgresMain(int argc, char *argv[],
WalSndSignals();
else
{
- pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config
- * file */
+ pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
pqsignal(SIGTERM, die); /* cancel current query and exit */
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 3bf96de256..4473e18e53 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,7 +32,6 @@ volatile sig_atomic_t QueryCancelPending = false;
volatile sig_atomic_t ProcDiePending = false;
volatile sig_atomic_t ClientConnectionLost = false;
volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t ConfigReloadPending = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 QueryCancelHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index bc6e03fbc7..24f43ad686 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,7 +82,6 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
@@ -279,8 +278,6 @@ extern void restore_stack_base(pg_stack_base_t base);
extern void check_stack_depth(void);
extern bool stack_is_too_deep(void);
-extern void PostgresSigHupHandler(SIGNAL_ARGS);
-
/* in tcop/utility.c */
extern void PreventCommandIfReadOnly(const char *cmdname);
extern void PreventCommandIfParallelMode(const char *cmdname);
diff --git a/src/include/postmaster/interrupt.h b/src/include/postmaster/interrupt.h
new file mode 100644
index 0000000000..8f4e43be20
--- /dev/null
+++ b/src/include/postmaster/interrupt.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ * Interrupt handling routines.
+ *
+ * Responses to interrupts are fairly varied and many types of backends
+ * have their own implementations, but we provide a few generic things
+ * here to facilitate code reuse.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/include/postmaster/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef INTERRUPT_H
+#define INTERRUPT_H
+
+#include <signal.h>
+
+extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
+extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
+
+extern void HandleMainLoopInterrupts(void);
+extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
+extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
+extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+
+#endif
--
2.17.2 (Apple Git-113)
v3-0004-Extend-the-ProcSignal-mechanism-to-support-barrie.patchapplication/octet-stream; name=v3-0004-Extend-the-ProcSignal-mechanism-to-support-barrie.patchDownload
From f314b81c8c8e55326ccd095f8849a6ab91fa9004 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 11:29:25 -0500
Subject: [PATCH v3 4/5] Extend the ProcSignal mechanism to support barriers.
A new function EmitProcSignalBarrier() can be used to emit a global
barrier which all backends that participate in the ProcSignal
mechanism must absorb, and a new function WaitForProcSignalBarrier()
can be used to wait until all relevant backends have in fact
absorbed the barrier.
This can be used to coordinate global state changes, such as turning
checksums on while the system is running.
Andres Freund and Robert Haas
---
doc/src/sgml/monitoring.sgml | 4 +
src/backend/postmaster/autovacuum.c | 4 +
src/backend/postmaster/checkpointer.c | 7 +
src/backend/postmaster/interrupt.c | 4 +
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/startup.c | 6 +-
src/backend/replication/walreceiver.c | 3 +-
src/backend/storage/buffer/bufmgr.c | 10 +
src/backend/storage/ipc/procsignal.c | 298 +++++++++++++++++++++++++-
src/backend/tcop/postgres.c | 3 +
src/backend/utils/init/globals.c | 1 +
src/include/miscadmin.h | 1 +
src/include/pgstat.h | 1 +
src/include/storage/procsignal.h | 9 +
14 files changed, 341 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index a3c5f86b7e..76957c0a43 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1593,6 +1593,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<entry><literal>LogicalRewriteWrite</literal></entry>
<entry>Waiting for a write of logical rewrite mappings.</entry>
</row>
+ <row>
+ <entry><literal>ProcSignalBarrier</literal></entry>
+ <entry>Waiting for a barrier event to be processed by all backends.</entry>
+ </row>
<row>
<entry><literal>RelationMapRead</literal></entry>
<entry>Waiting for a read of the relation map file.</entry>
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1792008ebe..52ad99f716 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -820,6 +820,10 @@ HandleAutoVacLauncherInterrupts(void)
rebuild_database_list(InvalidOid);
}
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
/* Process sinval catchup interrupts that happened while sleeping */
ProcessCatchupInterrupt();
}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3f35b324c3..d1185cc135 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -523,6 +523,9 @@ CheckpointerMain(void)
static void
HandleCheckpointerInterrupts(void)
{
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ConfigReloadPending)
{
ConfigReloadPending = false;
@@ -709,6 +712,10 @@ CheckpointWriteDelay(int flags, double progress)
AbsorbSyncRequests();
absorb_counter = WRITES_PER_ABSORB;
}
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
/*
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index 6900cd02f6..214ccaf34b 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -20,6 +20,7 @@
#include "postmaster/interrupt.h"
#include "storage/ipc.h"
#include "storage/latch.h"
+#include "storage/procsignal.h"
#include "utils/guc.h"
volatile sig_atomic_t ConfigReloadPending = false;
@@ -31,6 +32,9 @@ volatile sig_atomic_t ShutdownRequestPending = false;
void
HandleMainLoopInterrupts(void)
{
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ConfigReloadPending)
{
ConfigReloadPending = false;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index e931512203..7410b2ff5e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3988,6 +3988,9 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_LOGICAL_REWRITE_WRITE:
event_name = "LogicalRewriteWrite";
break;
+ case WAIT_EVENT_PROC_SIGNAL_BARRIER:
+ event_name = "ProcSignalBarrier";
+ break;
case WAIT_EVENT_RELATION_MAP_READ:
event_name = "RelationMapRead";
break;
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 4f59c71f73..11f7052e78 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -96,7 +96,7 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/* Handle SIGHUP and SIGTERM signals of startup process */
+/* Handle various signals that might be sent to the startup process */
void
HandleStartupProcInterrupts(void)
{
@@ -121,6 +121,10 @@ HandleStartupProcInterrupts(void)
*/
if (IsUnderPostmaster && !PostmasterIsAlive())
exit(1);
+
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c36bcc08ec..a4de8a9cd8 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -148,7 +148,8 @@ ProcessWalRcvInterrupts(void)
/*
* Although walreceiver interrupt handling doesn't use the same scheme as
* regular backends, call CHECK_FOR_INTERRUPTS() to make sure we receive
- * any incoming signals on Win32.
+ * any incoming signals on Win32, and also to make sure we process any
+ * barrier events.
*/
CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7ad10736d5..1f10a97dc7 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1852,6 +1852,10 @@ BufferSync(int flags)
}
UnlockBufHdr(bufHdr, buf_state);
+
+ /* Check for barrier events in case NBuffers is large. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
if (num_to_scan == 0)
@@ -1930,6 +1934,10 @@ BufferSync(int flags)
}
s->num_to_scan++;
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
Assert(num_spaces > 0);
@@ -2018,6 +2026,8 @@ BufferSync(int flags)
/*
* Sleep to throttle our I/O rate.
+ *
+ * (This will check for barrier events even if it doesn't sleep.)
*/
CheckpointWriteDelay(flags, (double) num_processed / num_to_scan);
}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index fde97a1036..9399ab0be4 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -20,6 +20,7 @@
#include "access/parallel.h"
#include "commands/async.h"
#include "miscadmin.h"
+#include "pgstat.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -45,13 +46,36 @@
* The flags are actually declared as "volatile sig_atomic_t" for maximum
* portability. This should ensure that loads and stores of the flag
* values are atomic, allowing us to dispense with any explicit locking.
+ *
+ * pss_signalFlags are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal,
+ * but sometimes we need confirmation, as when making a global state change
+ * that cannot be considered complete until all backends have taken notice
+ * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * increment the current "barrier generation"; when the new barrier generation
+ * (or greater) appears in the pss_barrierGeneration flag of every process,
+ * we know that the message has been received everywhere.
*/
typedef struct
{
pid_t pss_pid;
sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
+ pg_atomic_uint64 pss_barrierGeneration;
+ pg_atomic_uint32 pss_barrierCheckMask;
} ProcSignalSlot;
+/*
+ * Information that is global to the entire ProcSignal system can be stored
+ * here.
+ *
+ * psh_barrierGeneration is the highest barrier generation in existence.
+ */
+typedef struct
+{
+ pg_atomic_uint64 psh_barrierGeneration;
+ ProcSignalSlot psh_slot[FLEXIBLE_ARRAY_MEMBER];
+} ProcSignalHeader;
+
/*
* We reserve a slot for each possible BackendId, plus one for each
* possible auxiliary process type. (This scheme assumes there is not
@@ -59,11 +83,16 @@ typedef struct
*/
#define NumProcSignalSlots (MaxBackends + NUM_AUXPROCTYPES)
-static ProcSignalSlot *ProcSignalSlots = NULL;
+/* Check whether the relevant type bit is set in the flags. */
+#define BARRIER_SHOULD_CHECK(flags, type) \
+ (((flags) & (((uint32) 1) << (uint32) (type))) != 0)
+
+static ProcSignalHeader *ProcSignal = NULL;
static volatile ProcSignalSlot *MyProcSignalSlot = NULL;
static bool CheckProcSignal(ProcSignalReason reason);
static void CleanupProcSignalState(int status, Datum arg);
+static void ProcessBarrierSample(void);
/*
* ProcSignalShmemSize
@@ -72,7 +101,11 @@ static void CleanupProcSignalState(int status, Datum arg);
Size
ProcSignalShmemSize(void)
{
- return NumProcSignalSlots * sizeof(ProcSignalSlot);
+ Size size;
+
+ size = mul_size(NumProcSignalSlots, sizeof(ProcSignalSlot));
+ size = add_size(size, offsetof(ProcSignalHeader, psh_slot));
+ return size;
}
/*
@@ -85,12 +118,26 @@ ProcSignalShmemInit(void)
Size size = ProcSignalShmemSize();
bool found;
- ProcSignalSlots = (ProcSignalSlot *)
- ShmemInitStruct("ProcSignalSlots", size, &found);
+ ProcSignal = (ProcSignalHeader *)
+ ShmemInitStruct("ProcSignal", size, &found);
- /* If we're first, set everything to zeroes */
+ /* If we're first, initialize. */
if (!found)
- MemSet(ProcSignalSlots, 0, size);
+ {
+ int i;
+
+ pg_atomic_init_u64(&ProcSignal->psh_barrierGeneration, 0);
+
+ for (i = 0; i < NumProcSignalSlots; ++i)
+ {
+ ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+
+ slot->pss_pid = 0;
+ MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
+ pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+ pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
+ }
+ }
}
/*
@@ -104,10 +151,11 @@ void
ProcSignalInit(int pss_idx)
{
volatile ProcSignalSlot *slot;
+ uint64 barrier_generation;
Assert(pss_idx >= 1 && pss_idx <= NumProcSignalSlots);
- slot = &ProcSignalSlots[pss_idx - 1];
+ slot = &ProcSignal->psh_slot[pss_idx - 1];
/* sanity check */
if (slot->pss_pid != 0)
@@ -117,6 +165,23 @@ ProcSignalInit(int pss_idx)
/* Clear out any leftover signal reasons */
MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
+ /*
+ * Initialize barrier state. Since we're a brand-new process, there
+ * shouldn't be any leftover backend-private state that needs to be
+ * updated. Therefore, we can broadcast the latest barrier generation
+ * and disregard any previously-set check bits.
+ *
+ * NB: This only works if this initialization happens early enough in the
+ * startup sequence that we haven't yet cached any state that might need
+ * to be invalidated. That's also why we have a memory barrier here, to
+ * be sure that any later reads of memory happen strictly after this.
+ */
+ pg_atomic_write_u32(&slot->pss_barrierCheckMask, 0);
+ barrier_generation =
+ pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation);
+ pg_memory_barrier();
+
/* Mark slot with my PID */
slot->pss_pid = MyProcPid;
@@ -129,7 +194,7 @@ ProcSignalInit(int pss_idx)
/*
* CleanupProcSignalState
- * Remove current process from ProcSignalSlots
+ * Remove current process from ProcSignal mechanism
*
* This function is called via on_shmem_exit() during backend shutdown.
*/
@@ -139,7 +204,7 @@ CleanupProcSignalState(int status, Datum arg)
int pss_idx = DatumGetInt32(arg);
volatile ProcSignalSlot *slot;
- slot = &ProcSignalSlots[pss_idx - 1];
+ slot = &ProcSignal->psh_slot[pss_idx - 1];
Assert(slot == MyProcSignalSlot);
/*
@@ -161,6 +226,12 @@ CleanupProcSignalState(int status, Datum arg)
return; /* XXX better to zero the slot anyway? */
}
+ /*
+ * Make this slot look like it's absorbed all possible barriers, so that
+ * no barrier waits block on it.
+ */
+ pg_atomic_write_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+
slot->pss_pid = 0;
}
@@ -182,7 +253,7 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
if (backendId != InvalidBackendId)
{
- slot = &ProcSignalSlots[backendId - 1];
+ slot = &ProcSignal->psh_slot[backendId - 1];
/*
* Note: Since there's no locking, it's possible that the target
@@ -212,7 +283,7 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
for (i = NumProcSignalSlots - 1; i >= 0; i--)
{
- slot = &ProcSignalSlots[i];
+ slot = &ProcSignal->psh_slot[i];
if (slot->pss_pid == pid)
{
@@ -230,6 +301,184 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
return -1;
}
+/*
+ * EmitProcSignalBarrier
+ * Send a signal to every Postgres process
+ *
+ * The return value of this function is the barrier "generation" created
+ * by this operation. This value can be passed to WaitForProcSignalBarrier
+ * to wait until it is known that every participant in the ProcSignal
+ * mechanism has absorbed the signal (or started afterwards).
+ *
+ * Note that it would be a bad idea to use this for anything that happens
+ * frequently, as interrupting every backend could cause a noticeable
+ * performance hit.
+ *
+ * Callers are entitled to assume that this function will not throw ERROR
+ * or FATAL.
+ */
+uint64
+EmitProcSignalBarrier(ProcSignalBarrierType type)
+{
+ uint64 flagbit = UINT64CONST(1) << (uint64) type;
+ uint64 generation;
+
+ /*
+ * Set all the flags.
+ *
+ * Note that pg_atomic_fetch_or_u32 has full barrier semantics, so this
+ * is totally ordered with respect to anything the caller did before, and
+ * anything that we do afterwards. (This is also true of the later call
+ * to pg_atomic_add_fetch_u64.)
+ */
+ for (int i = 0; i < NumProcSignalSlots; i++)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+
+ pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit);
+ }
+
+ /*
+ * Increment the generation counter.
+ */
+ generation =
+ pg_atomic_add_fetch_u64(&ProcSignal->psh_barrierGeneration, 1);
+
+ /*
+ * Signal all the processes, so that they update their advertised barrier
+ * generation.
+ *
+ * Concurrency is not a problem here. Backends that have exited don't
+ * matter, and new backends that have joined since we entered this function
+ * must already have current state, since the caller is responsible for
+ * making sure that the relevant state is entirely visible before calling
+ * this function in the first place. We still have to wake them up -
+ * because we can't distinguish between such backends and older backends
+ * that need to update state - but they won't actually need to change
+ * any state.
+ */
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+ pid_t pid = slot->pss_pid;
+
+ if (pid != 0)
+ kill(pid, SIGUSR1);
+ }
+
+ return generation;
+}
+
+/*
+ * WaitForProcSignalBarrier - wait until it is guaranteed that all changes
+ * requested by a specific call to EmitProcSignalBarrier() have taken effect.
+ *
+ * We expect that the barrier will normally be absorbed very quickly by other
+ * backends, so we start by waiting just 1/8 of a second and then back off
+ * by a factor of two every time we time out, to a maximum wait time of
+ * 1 second.
+ */
+void
+WaitForProcSignalBarrier(uint64 generation)
+{
+ long timeout = 125L;
+
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+ uint64 oldval;
+
+ oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ while (oldval < generation)
+ {
+ int events;
+
+ CHECK_FOR_INTERRUPTS();
+
+ events =
+ WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ timeout, WAIT_EVENT_PROC_SIGNAL_BARRIER);
+ ResetLatch(MyLatch);
+
+ oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ if (events & WL_TIMEOUT)
+ timeout = Min(timeout * 2, 1000L);
+ }
+ }
+
+ /*
+ * The caller is probably calling this function because it wants to
+ * read the shared state or perform further writes to shared state once
+ * all backends are known to have absorbed the barrier. However, the
+ * read of pss_barrierGeneration was performed unlocked; insert a memory
+ * barrier to separate it from whatever follows.
+ */
+ pg_memory_barrier();
+}
+
+/*
+ * Perform global barrier related interrupt checking.
+ *
+ * Any backend that participates in ProcSignal signalling must arrange to
+ * call this function periodically. It is called from CHECK_FOR_INTERRUPTS(),
+ * which is enough for normal backends, but not necessarily for all types of
+ * background processes.
+ */
+void
+ProcessProcSignalBarrier(void)
+{
+ uint64 generation;
+ uint32 flags;
+
+ /* Exit quickly if there's no work to do. */
+ if (!ProcSignalBarrierPending)
+ return;
+ ProcSignalBarrierPending = false;
+
+ /*
+ * Read the current barrier generation, and then get the flags that
+ * are set for this backend. Note that pg_atomic_exchange_u32 is a full
+ * barrier, so we're guaranteed that the read of the barrier generation
+ * happens before we atomically extract the flags, and that any subsequent
+ * state changes happen afterward.
+ */
+ generation = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ flags = pg_atomic_exchange_u32(&MyProcSignalSlot->pss_barrierCheckMask, 0);
+
+ /*
+ * Process each type of barrier. It's important that nothing we call from
+ * here throws an error, because pss_barrierCheckMask has already been
+ * cleared. If we jumped out of here before processing all barrier types,
+ * then we'd forget about the need to do so later.
+ *
+ * NB: It ought to be OK to call the barrier-processing functions
+ * unconditionally, but it's more efficient to call only the ones that
+ * might need us to do something based on the flags.
+ */
+ if (BARRIER_SHOULD_CHECK(flags, PROCSIGNAL_BARRIER_SAMPLE))
+ ProcessBarrierSample();
+
+ /*
+ * State changes related to all types of barriers that might have been
+ * emitted have now been handled, so we can update our notion of the
+ * generation to the one we observed before beginning the updates. If
+ * things have changed further, it'll get fixed up when this function is
+ * next called.
+ */
+ pg_atomic_write_u64(&MyProcSignalSlot->pss_barrierGeneration, generation);
+}
+
+static void
+ProcessBarrierSample(void)
+{
+ /*
+ * XXX. This should be something no-fail, which elog() is not, but this is
+ * just for testing purposes.
+ */
+ elog(LOG, "ProcessBarrierSample");
+}
+
/*
* CheckProcSignal - check to see if a particular reason has been
* signaled, and clear the signal flag. Should be called after receiving
@@ -253,6 +502,27 @@ CheckProcSignal(ProcSignalReason reason)
return false;
}
+/*
+ * CheckProcSignalBarrier - check for new barriers we need to absorb
+ */
+static bool
+CheckProcSignalBarrier(void)
+{
+ volatile ProcSignalSlot *slot = MyProcSignalSlot;
+
+ if (slot != NULL)
+ {
+ uint64 mygen;
+ uint64 curgen;
+
+ mygen = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ curgen = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ return (mygen != curgen);
+ }
+
+ return false;
+}
+
/*
* procsignal_sigusr1_handler - handle SIGUSR1 signal.
*/
@@ -291,6 +561,12 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ if (CheckProcSignalBarrier())
+ {
+ InterruptPending = true;
+ ProcSignalBarrierPending = true;
+ }
+
SetLatch(MyLatch);
latch_sigusr1_handler();
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d894c3ddf4..25e22bd63b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3162,6 +3162,9 @@ ProcessInterrupts(void)
}
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ParallelMessagePending)
HandleParallelMessages();
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 4473e18e53..3a091022e2 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,6 +32,7 @@ volatile sig_atomic_t QueryCancelPending = false;
volatile sig_atomic_t ProcDiePending = false;
volatile sig_atomic_t ClientConnectionLost = false;
volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
+volatile sig_atomic_t ProcSignalBarrierPending = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 QueryCancelHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 24f43ad686..ed80f1d668 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,6 +82,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d823d..f2e873d048 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -908,6 +908,7 @@ typedef enum
WAIT_EVENT_LOGICAL_REWRITE_SYNC,
WAIT_EVENT_LOGICAL_REWRITE_TRUNCATE,
WAIT_EVENT_LOGICAL_REWRITE_WRITE,
+ WAIT_EVENT_PROC_SIGNAL_BARRIER,
WAIT_EVENT_RELATION_MAP_READ,
WAIT_EVENT_RELATION_MAP_SYNC,
WAIT_EVENT_RELATION_MAP_WRITE,
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 05b186a05c..90eda29d15 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -45,6 +45,11 @@ typedef enum
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
+typedef enum
+{
+ PROCSIGNAL_BARRIER_SAMPLE = 0
+} ProcSignalBarrierType;
+
/*
* prototypes for functions in procsignal.c
*/
@@ -55,6 +60,10 @@ extern void ProcSignalInit(int pss_idx);
extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
BackendId backendId);
+extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
+extern void WaitForProcSignalBarrier(uint64 generation);
+extern void ProcessProcSignalBarrier(void);
+
extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
#endif /* PROCSIGNAL_H */
--
2.17.2 (Apple Git-113)
v3-0005-Not-for-commit-test-code.patchapplication/octet-stream; name=v3-0005-Not-for-commit-test-code.patchDownload
From 2fd42691849f490b70cbd1003de6c4300a1ac465 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 2 Dec 2019 11:53:25 -0500
Subject: [PATCH v3 5/5] Not for commit: test code.
---
src/backend/storage/ipc/signalfuncs.c | 11 +++++++++++
src/include/catalog/pg_proc.dat | 5 +++++
2 files changed, 16 insertions(+)
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index ade8d713aa..f6deebcb5a 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -215,3 +215,14 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
PG_RETURN_BOOL(true);
}
+
+Datum
+sample_barrier(PG_FUNCTION_ARGS)
+{
+ uint64 bgen;
+
+ bgen = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_SAMPLE);
+ elog(NOTICE, "emitted barrier");
+ WaitForProcSignalBarrier(bgen);
+ PG_RETURN_VOID();
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ac8f64b219..380bdfea45 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10729,4 +10729,9 @@
proname => 'pg_partition_root', prorettype => 'regclass',
proargtypes => 'regclass', prosrc => 'pg_partition_root' },
+# function to get the top-most partition root parent
+{ oid => '9758', descr => 'send sample barrier',
+ proname => 'sample_barrier', prorettype => 'void',
+ proargtypes => '', prosrc => 'sample_barrier' },
+
]
--
2.17.2 (Apple Git-113)
v3-0002-Use-PostgresSigHupHandler-in-more-places.patchapplication/octet-stream; name=v3-0002-Use-PostgresSigHupHandler-in-more-places.patchDownload
From c9ad049519eab0bdbdb539dcca99c6f052291b14 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Nov 2019 09:01:16 -0500
Subject: [PATCH v3 2/5] Use PostgresSigHupHandler in more places.
There seems to be no reason for every background process to have
its own flag indicating that a config-file reload is needed.
Instead, let's just use ConfigFilePending for that purpose
everywhere.
---
src/backend/postmaster/autovacuum.c | 30 ++++++----------------
src/backend/postmaster/bgwriter.c | 20 +++------------
src/backend/postmaster/checkpointer.c | 24 ++++-------------
src/backend/postmaster/pgarch.c | 25 ++++--------------
src/backend/postmaster/pgstat.c | 28 +++++---------------
src/backend/postmaster/walwriter.c | 20 +++------------
src/backend/replication/logical/launcher.c | 23 +++--------------
src/backend/replication/logical/worker.c | 23 +++--------------
8 files changed, 37 insertions(+), 156 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 5766203aaf..b1c2b0a01c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -138,7 +138,6 @@ static bool am_autovacuum_launcher = false;
static bool am_autovacuum_worker = false;
/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t got_SIGUSR2 = false;
static volatile sig_atomic_t got_SIGTERM = false;
@@ -344,7 +343,6 @@ static void perform_work_item(AutoVacuumWorkItem *workitem);
static void autovac_report_activity(autovac_table *tab);
static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
const char *nspname, const char *relname);
-static void av_sighup_handler(SIGNAL_ARGS);
static void avl_sigusr2_handler(SIGNAL_ARGS);
static void avl_sigterm_handler(SIGNAL_ARGS);
static void autovac_refresh_stats(void);
@@ -452,7 +450,7 @@ AutoVacLauncherMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, av_sighup_handler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGINT, StatementCancelHandler);
pqsignal(SIGTERM, avl_sigterm_handler);
@@ -805,9 +803,9 @@ HandleAutoVacLauncherInterrupts(void)
if (got_SIGTERM)
AutoVacLauncherShutdown();
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/* shutdown requested in config file? */
@@ -1405,18 +1403,6 @@ AutoVacWorkerFailed(void)
AutoVacuumShmem->av_signal[AutoVacForkFailed] = true;
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-av_sighup_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGUSR2: a worker is up and running, or just finished, or failed to fork */
static void
avl_sigusr2_handler(SIGNAL_ARGS)
@@ -1539,7 +1525,7 @@ AutoVacWorkerMain(int argc, char *argv[])
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
- pqsignal(SIGHUP, av_sighup_handler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
/*
* SIGINT is used to signal canceling the current table's vacuum; SIGTERM
@@ -2332,9 +2318,9 @@ do_autovacuum(void)
/*
* Check for config changes before processing each collected table.
*/
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/*
@@ -2580,9 +2566,9 @@ deleted:
* Check for config changes before acquiring lock for further jobs.
*/
CHECK_FOR_INTERRUPTS();
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index c7500f1d71..bca46de6d5 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -89,7 +89,6 @@ static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
static void HandleBackgroundWriterInterrupts(void);
@@ -97,7 +96,6 @@ static void HandleBackgroundWriterInterrupts(void);
/* Signal handlers */
static void bg_quickdie(SIGNAL_ARGS);
-static void BgSigHupHandler(SIGNAL_ARGS);
static void ReqShutdownHandler(SIGNAL_ARGS);
@@ -118,7 +116,7 @@ BackgroundWriterMain(void)
/*
* Properly accept or ignore signals that might be sent to us.
*/
- pqsignal(SIGHUP, BgSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, ReqShutdownHandler); /* shutdown */
pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
@@ -363,9 +361,9 @@ BackgroundWriterMain(void)
static void
HandleBackgroundWriterInterrupts(void)
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -413,18 +411,6 @@ bg_quickdie(SIGNAL_ARGS)
_exit(2);
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-BgSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGTERM: set flag to shutdown and exit */
static void
ReqShutdownHandler(SIGNAL_ARGS)
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index d087ee9c74..9cf91b0d35 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -151,7 +151,6 @@ double CheckPointCompletionTarget = 0.5;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
/*
@@ -179,7 +178,6 @@ static void UpdateSharedMemoryConfig(void);
/* Signal handlers */
static void chkpt_quickdie(SIGNAL_ARGS);
-static void ChkptSigHupHandler(SIGNAL_ARGS);
static void ReqCheckpointHandler(SIGNAL_ARGS);
static void ReqShutdownHandler(SIGNAL_ARGS);
@@ -206,7 +204,7 @@ CheckpointerMain(void)
* want to wait for the backends to exit, whereupon the postmaster will
* tell us it's okay to shut down (via SIGUSR2).
*/
- pqsignal(SIGHUP, ChkptSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */
pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
pqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */
@@ -535,9 +533,9 @@ CheckpointerMain(void)
static void
HandleCheckpointerInterrupts(void)
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/*
@@ -685,9 +683,9 @@ CheckpointWriteDelay(int flags, double progress)
!ImmediateCheckpointRequested() &&
IsCheckpointOnSchedule(progress))
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
/* update shmem copies of config variables */
UpdateSharedMemoryConfig();
@@ -835,18 +833,6 @@ chkpt_quickdie(SIGNAL_ARGS)
_exit(2);
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-ChkptSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGINT: set flag to run a normal checkpoint right away */
static void
ReqCheckpointHandler(SIGNAL_ARGS)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index f84f882c4c..09a9c66b4b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -83,7 +83,6 @@ static time_t last_sigterm_time = 0;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t got_SIGTERM = false;
static volatile sig_atomic_t wakened = false;
static volatile sig_atomic_t ready_to_stop = false;
@@ -98,7 +97,6 @@ static pid_t pgarch_forkexec(void);
NON_EXEC_STATIC void PgArchiverMain(int argc, char *argv[]) pg_attribute_noreturn();
static void pgarch_exit(SIGNAL_ARGS);
-static void ArchSigHupHandler(SIGNAL_ARGS);
static void ArchSigTermHandler(SIGNAL_ARGS);
static void pgarch_waken(SIGNAL_ARGS);
static void pgarch_waken_stop(SIGNAL_ARGS);
@@ -229,7 +227,7 @@ PgArchiverMain(int argc, char *argv[])
* Ignore all signals usually bound to some action in the postmaster,
* except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
*/
- pqsignal(SIGHUP, ArchSigHupHandler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, ArchSigTermHandler);
pqsignal(SIGQUIT, pgarch_exit);
@@ -259,19 +257,6 @@ pgarch_exit(SIGNAL_ARGS)
exit(1);
}
-/* SIGHUP signal handler for archiver process */
-static void
-ArchSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- /* set flag to re-read config file at next convenient time */
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGTERM signal handler for archiver process */
static void
ArchSigTermHandler(SIGNAL_ARGS)
@@ -348,9 +333,9 @@ pgarch_MainLoop(void)
time_to_stop = ready_to_stop;
/* Check for config update */
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -457,9 +442,9 @@ pgarch_ArchiverCopyLoop(void)
* setting for archive_command as soon as possible, even if there
* is a backlog of files to be archived.
*/
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index fabcf31de8..96a9e09ced 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -264,7 +264,6 @@ static List *pending_write_requests = NIL;
/* Signal handler flags */
static volatile bool need_exit = false;
-static volatile bool got_SIGHUP = false;
/*
* Total time charged to functions so far in the current backend.
@@ -285,7 +284,6 @@ static pid_t pgstat_forkexec(void);
NON_EXEC_STATIC void PgstatCollectorMain(int argc, char *argv[]) pg_attribute_noreturn();
static void pgstat_exit(SIGNAL_ARGS);
static void pgstat_beshutdown_hook(int code, Datum arg);
-static void pgstat_sighup_handler(SIGNAL_ARGS);
static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create);
static PgStat_StatTabEntry *pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry,
@@ -4434,7 +4432,7 @@ PgstatCollectorMain(int argc, char *argv[])
* except SIGHUP and SIGQUIT. Note we don't need a SIGUSR1 handler to
* support latch operations, because we only use a local latch.
*/
- pqsignal(SIGHUP, pgstat_sighup_handler);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, SIG_IGN);
pqsignal(SIGQUIT, pgstat_exit);
@@ -4466,10 +4464,10 @@ PgstatCollectorMain(int argc, char *argv[])
* message. (This effectively means that if backends are sending us stuff
* like mad, we won't notice postmaster death until things slack off a
* bit; which seems fine.) To do that, we have an inner loop that
- * iterates as long as recv() succeeds. We do recognize got_SIGHUP inside
- * the inner loop, which means that such interrupts will get serviced but
- * the latch won't get cleared until next time there is a break in the
- * action.
+ * iterates as long as recv() succeeds. We do check ConfigReloadPending
+ * inside the inner loop, which means that such interrupts will get
+ * serviced but the latch won't get cleared until next time there is a
+ * break in the action.
*/
for (;;)
{
@@ -4491,9 +4489,9 @@ PgstatCollectorMain(int argc, char *argv[])
/*
* Reload configuration if we got SIGHUP from the postmaster.
*/
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -4691,18 +4689,6 @@ pgstat_exit(SIGNAL_ARGS)
errno = save_errno;
}
-/* SIGHUP handler for collector process */
-static void
-pgstat_sighup_handler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* Subroutine to clear stats in a database entry
*
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 5a3573503c..599478530f 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -80,14 +80,12 @@ int WalWriterFlushAfter = 128;
/*
* Flags set by interrupt handlers for later service in the main loop.
*/
-static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
static void HandleWalWriterInterrupts(void);
/* Signal handlers */
static void wal_quickdie(SIGNAL_ARGS);
-static void WalSigHupHandler(SIGNAL_ARGS);
static void WalShutdownHandler(SIGNAL_ARGS);
/*
@@ -110,7 +108,7 @@ WalWriterMain(void)
* We have no particular use for SIGINT at the moment, but seems
* reasonable to treat like SIGTERM.
*/
- pqsignal(SIGHUP, WalSigHupHandler); /* set flag to read config file */
+ pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, WalShutdownHandler); /* request shutdown */
pqsignal(SIGTERM, WalShutdownHandler); /* request shutdown */
pqsignal(SIGQUIT, wal_quickdie); /* hard crash time */
@@ -278,9 +276,9 @@ WalWriterMain(void)
static void
HandleWalWriterInterrupts(void)
{
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
if (shutdown_requested)
@@ -322,18 +320,6 @@ wal_quickdie(SIGNAL_ARGS)
_exit(2);
}
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-WalSigHupHandler(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* SIGTERM: set flag to exit normally */
static void
WalShutdownHandler(SIGNAL_ARGS)
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 4643af95fe..edca70c58c 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -92,9 +92,6 @@ static void logicalrep_worker_onexit(int code, Datum arg);
static void logicalrep_worker_detach(void);
static void logicalrep_worker_cleanup(LogicalRepWorker *worker);
-/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGHUP = false;
-
static bool on_commit_launcher_wakeup = false;
Datum pg_stat_get_subscription(PG_FUNCTION_ARGS);
@@ -714,20 +711,6 @@ logicalrep_worker_onexit(int code, Datum arg)
ApplyLauncherWakeup();
}
-/* SIGHUP: set flag to reload configuration at next convenient time */
-static void
-logicalrep_launcher_sighup(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
-
- /* Waken anything waiting on the process latch */
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/*
* Count the number of registered (not necessarily running) sync workers
* for a subscription.
@@ -972,7 +955,7 @@ ApplyLauncherMain(Datum main_arg)
LogicalRepCtx->launcher_pid = MyProcPid;
/* Establish signal handlers. */
- pqsignal(SIGHUP, logicalrep_launcher_sighup);
+ pqsignal(SIGTERM, PostgresSigHupHandler);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
@@ -1061,9 +1044,9 @@ ApplyLauncherMain(Datum main_arg)
CHECK_FOR_INTERRUPTS();
}
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
}
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index ced0d191c2..3b12ad6400 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -111,9 +111,6 @@ static void store_flush_position(XLogRecPtr remote_lsn);
static void maybe_reread_subscription(void);
-/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGHUP = false;
-
/*
* Should this worker apply changes for given relation.
*
@@ -1270,9 +1267,9 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
CHECK_FOR_INTERRUPTS();
}
- if (got_SIGHUP)
+ if (ConfigReloadPending)
{
- got_SIGHUP = false;
+ ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
@@ -1563,20 +1560,6 @@ subscription_change_cb(Datum arg, int cacheid, uint32 hashvalue)
MySubscriptionValid = false;
}
-/* SIGHUP: set flag to reload configuration at next convenient time */
-static void
-logicalrep_worker_sighup(SIGNAL_ARGS)
-{
- int save_errno = errno;
-
- got_SIGHUP = true;
-
- /* Waken anything waiting on the process latch */
- SetLatch(MyLatch);
-
- errno = save_errno;
-}
-
/* Logical Replication Apply worker entry point */
void
ApplyWorkerMain(Datum main_arg)
@@ -1592,7 +1575,7 @@ ApplyWorkerMain(Datum main_arg)
logicalrep_worker_attach(worker_slot);
/* Setup signal handling */
- pqsignal(SIGHUP, logicalrep_worker_sighup);
+ pqsignal(SIGHUP, PostgresSigHupHandler);
pqsignal(SIGTERM, die);
BackgroundWorkerUnblockSignals();
--
2.17.2 (Apple Git-113)
v3-0001-Move-interrupting-handling-code-into-subroutines.patchapplication/octet-stream; name=v3-0001-Move-interrupting-handling-code-into-subroutines.patchDownload
From be1b8bd29985572598fd5881ec6e0854f9138dac Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 26 Nov 2019 13:14:13 -0500
Subject: [PATCH v3 1/5] Move interrupting-handling code into subroutines.
Some auxiliary processes, as well as the autovacuum launcher,
have interrupt handling code directly in their main loops.
Try to abstract things a little better by moving it into
separate functions.
This doesn't make any functional difference, and leaves
in place relatively large differences among processes in how
interrupts are handled, but hopefully it at least makes it
easier to see the commonalities and differences across
process types.
---
src/backend/postmaster/autovacuum.c | 72 +++++++++++++++++----------
src/backend/postmaster/bgwriter.c | 42 ++++++++++------
src/backend/postmaster/checkpointer.c | 71 ++++++++++++++------------
src/backend/postmaster/walwriter.c | 34 ++++++++-----
4 files changed, 133 insertions(+), 86 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1dd8168ca..5766203aaf 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -311,6 +311,8 @@ NON_EXEC_STATIC void AutoVacWorkerMain(int argc, char *argv[]) pg_attribute_nore
NON_EXEC_STATIC void AutoVacLauncherMain(int argc, char *argv[]) pg_attribute_noreturn();
static Oid do_start_worker(void);
+static void HandleAutoVacLauncherInterrupts(void);
+static void AutoVacLauncherShutdown() pg_attribute_noreturn();
static void launcher_determine_sleep(bool canlaunch, bool recursing,
struct timeval *nap);
static void launch_worker(TimestampTz now);
@@ -554,7 +556,7 @@ AutoVacLauncherMain(int argc, char *argv[])
/* if in shutdown mode, no need for anything further; just go away */
if (got_SIGTERM)
- goto shutdown;
+ AutoVacLauncherShutdown();
/*
* Sleep at least 1 second after any error. We don't want to be
@@ -649,30 +651,7 @@ AutoVacLauncherMain(int argc, char *argv[])
ResetLatch(MyLatch);
- /* Process sinval catchup interrupts that happened while sleeping */
- ProcessCatchupInterrupt();
-
- /* the normal shutdown case */
- if (got_SIGTERM)
- break;
-
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
-
- /* shutdown requested in config file? */
- if (!AutoVacuumingActive())
- break;
-
- /* rebalance in case the default cost parameters changed */
- LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
- autovac_balance_cost();
- LWLockRelease(AutovacuumLock);
-
- /* rebuild the list in case the naptime changed */
- rebuild_database_list(InvalidOid);
- }
+ HandleAutoVacLauncherInterrupts();
/*
* a worker finished, or postmaster signalled failure to start a
@@ -813,8 +792,47 @@ AutoVacLauncherMain(int argc, char *argv[])
}
}
- /* Normal exit from the autovac launcher is here */
-shutdown:
+ AutoVacLauncherShutdown();
+}
+
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleAutoVacLauncherInterrupts(void)
+{
+ /* the normal shutdown case */
+ if (got_SIGTERM)
+ AutoVacLauncherShutdown();
+
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ /* shutdown requested in config file? */
+ if (!AutoVacuumingActive())
+ AutoVacLauncherShutdown();
+
+ /* rebalance in case the default cost parameters changed */
+ LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
+ autovac_balance_cost();
+ LWLockRelease(AutovacuumLock);
+
+ /* rebuild the list in case the naptime changed */
+ rebuild_database_list(InvalidOid);
+ }
+
+ /* Process sinval catchup interrupts that happened while sleeping */
+ ProcessCatchupInterrupt();
+}
+
+/*
+ * Perform a normal exit from the autovac launcher.
+ */
+static void
+AutoVacLauncherShutdown()
+{
ereport(DEBUG1,
(errmsg("autovacuum launcher shutting down")));
AutoVacuumShmem->av_launcherpid = 0;
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 2fa631ea7a..c7500f1d71 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -92,6 +92,8 @@ static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
+static void HandleBackgroundWriterInterrupts(void);
+
/* Signal handlers */
static void bg_quickdie(SIGNAL_ARGS);
@@ -241,21 +243,7 @@ BackgroundWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
- if (shutdown_requested)
- {
- /*
- * From here on, elog(ERROR) should end with exit(1), not send
- * control back to the sigsetjmp block above
- */
- ExitOnAnyError = true;
- /* Normal exit from the bgwriter is here */
- proc_exit(0); /* done */
- }
+ HandleBackgroundWriterInterrupts();
/*
* Do one cycle of dirty-buffer writing.
@@ -369,6 +357,30 @@ BackgroundWriterMain(void)
}
}
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleBackgroundWriterInterrupts(void)
+{
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+
+ if (shutdown_requested)
+ {
+ /*
+ * From here on, elog(ERROR) should end with exit(1), not send
+ * control back to the sigsetjmp block above
+ */
+ ExitOnAnyError = true;
+ /* Normal exit from the bgwriter is here */
+ proc_exit(0); /* done */
+ }
+}
+
/* --------------------------------
* signal handler routines
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index d93c941871..d087ee9c74 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -169,6 +169,7 @@ static pg_time_t last_xlog_switch_time;
/* Prototypes for private functions */
+static void HandleCheckpointerInterrupts();
static void CheckArchiveTimeout(void);
static bool IsCheckpointOnSchedule(double progress);
static bool ImmediateCheckpointRequested(void);
@@ -350,37 +351,7 @@ CheckpointerMain(void)
* Process any requests or signals received recently.
*/
AbsorbSyncRequests();
-
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
-
- /*
- * Checkpointer is the last process to shut down, so we ask it to
- * hold the keys for a range of other tasks required most of which
- * have nothing to do with checkpointing at all.
- *
- * For various reasons, some config values can change dynamically
- * so the primary copy of them is held in shared memory to make
- * sure all backends see the same value. We make Checkpointer
- * responsible for updating the shared memory copy if the
- * parameter setting changes because of SIGHUP.
- */
- UpdateSharedMemoryConfig();
- }
- if (shutdown_requested)
- {
- /*
- * From here on, elog(ERROR) should end with exit(1), not send
- * control back to the sigsetjmp block above
- */
- ExitOnAnyError = true;
- /* Close down the database */
- ShutdownXLOG(0, 0);
- /* Normal exit from the checkpointer is here */
- proc_exit(0); /* done */
- }
+ HandleCheckpointerInterrupts();
/*
* Detect a pending checkpoint request by checking whether the flags
@@ -558,6 +529,44 @@ CheckpointerMain(void)
}
}
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleCheckpointerInterrupts(void)
+{
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+
+ /*
+ * Checkpointer is the last process to shut down, so we ask it to
+ * hold the keys for a range of other tasks required most of which
+ * have nothing to do with checkpointing at all.
+ *
+ * For various reasons, some config values can change dynamically
+ * so the primary copy of them is held in shared memory to make
+ * sure all backends see the same value. We make Checkpointer
+ * responsible for updating the shared memory copy if the
+ * parameter setting changes because of SIGHUP.
+ */
+ UpdateSharedMemoryConfig();
+ }
+ if (shutdown_requested)
+ {
+ /*
+ * From here on, elog(ERROR) should end with exit(1), not send
+ * control back to the sigsetjmp block above
+ */
+ ExitOnAnyError = true;
+ /* Close down the database */
+ ShutdownXLOG(0, 0);
+ /* Normal exit from the checkpointer is here */
+ proc_exit(0); /* done */
+ }
+}
+
/*
* CheckArchiveTimeout -- check for archive_timeout and switch xlog files
*
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index cce9713408..5a3573503c 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -83,6 +83,8 @@ int WalWriterFlushAfter = 128;
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t shutdown_requested = false;
+static void HandleWalWriterInterrupts(void);
+
/* Signal handlers */
static void wal_quickdie(SIGNAL_ARGS);
static void WalSigHupHandler(SIGNAL_ARGS);
@@ -242,19 +244,7 @@ WalWriterMain(void)
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
- /*
- * Process any requests or signals received recently.
- */
- if (got_SIGHUP)
- {
- got_SIGHUP = false;
- ProcessConfigFile(PGC_SIGHUP);
- }
- if (shutdown_requested)
- {
- /* Normal exit from the walwriter is here */
- proc_exit(0); /* done */
- }
+ HandleWalWriterInterrupts();
/*
* Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -282,6 +272,24 @@ WalWriterMain(void)
}
}
+/*
+ * Process any new interrupts.
+ */
+static void
+HandleWalWriterInterrupts(void)
+{
+ if (got_SIGHUP)
+ {
+ got_SIGHUP = false;
+ ProcessConfigFile(PGC_SIGHUP);
+ }
+ if (shutdown_requested)
+ {
+ /* Normal exit from the walwriter is here */
+ proc_exit(0); /* done */
+ }
+}
+
/* --------------------------------
* signal handler routines
--
2.17.2 (Apple Git-113)
On 9 Dec 2019, at 16:42, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Dec 6, 2019 at 12:17 PM Andres Freund <andres@anarazel.de> wrote:
I've read through the patchset and played around with it to try and break it
and understand it (not in that order). Being a bit out of my comfort zone, I
can't offer the deep insights that Andres has done; but in reading the code it
all makes sense, and it works as I expect it to.
Hm. Why "Postgres*"? I think that's confusing. So far only backend like
things are named like that, and I'd e.g. not call checkpointer that.Well, that's the existing function name, and 0003 changes it anyway.
Reading the patches in sequence I was irked by Postgres* as well, and was going
to suggest PostgresProcess*, but since 0003 changes it immediately it's less of
an issue.
0003 takes this a step
further by trying to unify a bunch more error-handling across
different background process types. Together, 0002 and 0003 save >300
lines of code and as far as I can see there's basically no downside.
Agreed, I cannot see any negative impacts. The cleanup of common code makes
the remaining process code more readable so +1.
The patch has a tiny typo though, s/procesess/processes/:
+ * Typically, this handler would be used for SIGTERM, but some procesess use
This patch probably needs some more changes before commit. One likely
candidate: the use of a sample barrier type here probably needs to be
replaced with something else. But the way this is set up doesn't
really lend itself to starting with 0 types of barrier and then adding
them later, so I am not sure exactly what to do to make this patch
independent of what follows.
Something which can be observed in order to hook up a test for it, but which
has no sideeffects? A NOOP barrier which only does a debug elog?
The callback proposal I made before
could've accommodated that (as well as, perhaps, some automated
testing by dynamically loading up a barrier type) but Andres didn't
like that. Perhaps he'll relent, or have a counter-proposal, or
someone else will feel differently.
I sort of like the callback idea conceptually, but Andres is making a good
point about the extensibility actually making it harder to reason about.
It seems sufficient to me for now to come up with something that can
be used for enabling checksums online
FWIW I've rebased the online checksums patch on top of this patchset instead of
Andres' previous patch and everything seems to work fine. All tests pass, and
I've been unable to trigger anything unexpected. I'll post the new version of
said patch shortly.
and for the thing that got me
motivated to work on this, which is ALTER SYSTEM READ ONLY.
Aha, thats a pretty neat usecase.
In order to play around with and try to understand the patchset I wrote a PoC
PGC_PROCSIGNALBARRIER class for GUCs and converted a few PGC_SIGHUP GUCs to use
a barrier rather than a HUP. The idea was to test interacting with the API a
bit more than the online checksum patch does, as its needs are pretty basic.
In hacking this up I didn't really come across anything in particular that I
lacked, and the result worked fine (insofar as a PoC can be considered
working).
cheers ./daniel
On Mon, Dec 9, 2019 at 7:37 PM Daniel Gustafsson <daniel@yesql.se> wrote:
I've read through the patchset and played around with it to try and break it
and understand it (not in that order). Being a bit out of my comfort zone, I
can't offer the deep insights that Andres has done; but in reading the code it
all makes sense, and it works as I expect it to.
Stellar. If nobody objects in the meantime, I plan to commit 0001-0003
next week.
The patch has a tiny typo though, s/procesess/processes/:
But I'll fix that first. Thanks for the review.
Something which can be observed in order to hook up a test for it, but which
has no sideeffects? A NOOP barrier which only does a debug elog?
It would be a bit hard to hook up a test for that, because a normal
test doesn't see the logs, and a TAP test could, but in this case what
you want to check for is that there are no stragglers who should have
gotten the memo and didn't, which supposes you have a list of everyone
who ought to get the memo. You could manually list out all the
background processes but that seems like it would be at risk of
becoming obsolete. You could check that everything in pg_stat_activity
emitted a message except for a known list of exceptions, which would
be less likely to become obsolete, but would also be complex. Also, I
think it's a crock to have something like that in there long term just
for testing purposes.
I sort of like the callback idea conceptually, but Andres is making a good
point about the extensibility actually making it harder to reason about.
That objection doesn't hold any water for me, because this is open
source. People can always patch the core. If we don't add hooks, then
we make life easier for fork maintainers (including my employer) and
harder for extension authors. I think that's *definitely* the wrong
bet for the community to be making; we should be trying very hard to
help extension authors and minimize the need for forks. If you install
an extension that uses a hook, any hook, and it breaks things, then
you get to keep both pieces.
And that approach would let us do spiffy testing without having to
leave cruft in core.
FWIW I've rebased the online checksums patch on top of this patchset instead of
Andres' previous patch and everything seems to work fine. All tests pass, and
I've been unable to trigger anything unexpected. I'll post the new version of
said patch shortly.
Cool.
and for the thing that got me
motivated to work on this, which is ALTER SYSTEM READ ONLY.Aha, thats a pretty neat usecase.
Thanks.
In order to play around with and try to understand the patchset I wrote a PoC
PGC_PROCSIGNALBARRIER class for GUCs and converted a few PGC_SIGHUP GUCs to use
a barrier rather than a HUP. The idea was to test interacting with the API a
bit more than the online checksum patch does, as its needs are pretty basic.
In hacking this up I didn't really come across anything in particular that I
lacked, and the result worked fine (insofar as a PoC can be considered
working).
Cool.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Hi,
On 2019-12-11 09:12:49 -0500, Robert Haas wrote:
On Mon, Dec 9, 2019 at 7:37 PM Daniel Gustafsson <daniel@yesql.se> wrote:
I sort of like the callback idea conceptually, but Andres is making a good
point about the extensibility actually making it harder to reason about.That objection doesn't hold any water for me, because this is open
source. People can always patch the core.
I just don't buy this argument. There's a difference in between an
unpatched version of postgres suddenly potentially running hooks
everywhere CFI() etc is called, and some user patching postgres to
behave differently. In the former case we'll have to ask to reproduce
problems without extension in a lot more cases. For me code like this
that runs in pretty low level situations that we've gotten wrong more
than once, doesn't benefit by being extensible. We just make things more
fragile, and provide traps for extension authors.
If we don't add hooks, then we make life easier for fork maintainers
(including my employer) and harder for extension authors. I think
that's *definitely* the wrong bet for the community to be making; we
should be trying very hard to help extension authors and minimize the
need for forks. If you install an extension that uses a hook, any
hook, and it breaks things, then you get to keep both pieces.
But that's just not how it ends up working in a lot of cases? People
still report bugs to the list, and the debugging experience of problems
where an extension causes crashes-at-a-distance is pretty bad.
Greetings,
Andres Freund
On Wed, Dec 11, 2019 at 12:38 PM Andres Freund <andres@anarazel.de> wrote:
I just don't buy this argument. There's a difference in between an
unpatched version of postgres suddenly potentially running hooks
everywhere CFI() etc is called, and some user patching postgres to
behave differently. In the former case we'll have to ask to reproduce
problems without extension in a lot more cases. For me code like this
that runs in pretty low level situations that we've gotten wrong more
than once, doesn't benefit by being extensible. We just make things more
fragile, and provide traps for extension authors.
It seems to me that this amounts to an argument that (a) core
developers are smarter than extension developers, and are thus the
only ones entitled to play with sharp tools, and/or (b) core
developers are more important than extension developers, and thus
inconveniencing extension developers is OK if it makes life better for
core developers. Both propositions seem pretty arrogant to me.
But that's just not how it ends up working in a lot of cases? People
still report bugs to the list, and the debugging experience of problems
where an extension causes crashes-at-a-distance is pretty bad.
I agree that crashes at a distance are bad, and that those caused by
extensions are more annoying than those caused by core code, because
in the latter case there is a silent contributor to the problem about
which we may have little information. However, I disagree with the
idea that the right solution is to try to lock things down so that
extension authors can't do stuff. Extensibility is an important part
of what has made PostgreSQL successful, and I think we need more of
it, not less.
We can to some extent mitigate these kinds of problems by adding
assertions to our code that will catch usage errors, which has the
advantage that those things also get caught when somebody accidentally
introduces such bugs into core. To the extent that we can't mitigate
them, I think we should live with them, because I think extensibility
is far too valuable to cast aside over such concerns.
While I have passionate philosophical feelings about this topic, for
purposes of the present thread the really important question (IMV,
anyway) is whether there's any way of getting a patch for global
barriers committed in advance of the first user of such barriers. Both
your version and mine add an enum, and I think that you can't have an
enum with no elements. If you have a suggestion as to how we can
structure things so that we can start out with 0 users of this
mechanism rather than needing to start out with 1, I'd like to hear
them. If not, then I guess we'll need to decide which of checksum
enable/disable and ALTER SYSTEM READ ONLY is going to go first and
commit this only when that patch is ready to go as well. Or, I
suppose, commit it with a dummy placeholder that then gets replaced by
whichever patch goes first, but I'm not sure whether people would be
OK with that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Hi,
On 2019-12-11 13:35:26 -0500, Robert Haas wrote:
While I have passionate philosophical feelings about this topic, for
purposes of the present thread the really important question (IMV,
anyway) is whether there's any way of getting a patch for global
barriers committed in advance of the first user of such barriers.
Right. I think there is.
If not, then I guess we'll need to decide which of checksum
enable/disable and ALTER SYSTEM READ ONLY is going to go first and
commit this only when that patch is ready to go as well. Or, I
suppose, commit it with a dummy placeholder that then gets replaced by
whichever patch goes first, but I'm not sure whether people would be
OK with that.
I'd either add a test (if we have some) or placeholder kind
initially. But I'd also be ok with going for either of the other
versions directly - but it seems harder to tackle the patches together.
Greetings,
Andres Freund
On Thu, Dec 12, 2019 at 2:54 PM Andres Freund <andres@anarazel.de> wrote:
I'd either add a test (if we have some) or placeholder kind
initially. But I'd also be ok with going for either of the other
versions directly - but it seems harder to tackle the patches together.
OK. I have committed 0001-0003 as I had mentioned last week that I
intended to do. For 0004, I have replaced "sample" with "placeholder"
and added comments explaining that this is intended to be replaced by
the first real user of the mechanism. If there are no further
comments/objections I'll go ahead with this one as well.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
v4-0001-Extend-the-ProcSignal-mechanism-to-support-barrie.patchapplication/octet-stream; name=v4-0001-Extend-the-ProcSignal-mechanism-to-support-barrie.patchDownload
From 3c747e912b894ebe201e4f6200ff3d10947dbc54 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 17 Dec 2019 13:35:09 -0500
Subject: [PATCH v4] Extend the ProcSignal mechanism to support barriers.
A new function EmitProcSignalBarrier() can be used to emit a global
barrier which all backends that participate in the ProcSignal
mechanism must absorb, and a new function WaitForProcSignalBarrier()
can be used to wait until all relevant backends have in fact
absorbed the barrier.
This can be used to coordinate global state changes, such as turning
checksums on while the system is running.
Andres Freund and Robert Haas, reviewed by Daniel Gustafsson.
Discussion: http://postgr.es/m/CA+TgmoZwDk=BguVDVa+qdA6SBKef=PKbaKDQALTC_9qoz1mJqg@mail.gmail.com
---
doc/src/sgml/monitoring.sgml | 4 +
src/backend/postmaster/autovacuum.c | 4 +
src/backend/postmaster/checkpointer.c | 7 +
src/backend/postmaster/interrupt.c | 4 +
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/startup.c | 6 +-
src/backend/replication/walreceiver.c | 3 +-
src/backend/storage/buffer/bufmgr.c | 10 +
src/backend/storage/ipc/procsignal.c | 301 +++++++++++++++++++++++++-
src/backend/tcop/postgres.c | 3 +
src/backend/utils/init/globals.c | 1 +
src/include/miscadmin.h | 1 +
src/include/pgstat.h | 1 +
src/include/storage/procsignal.h | 14 ++
14 files changed, 349 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index a3c5f86b7e..76957c0a43 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -1593,6 +1593,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<entry><literal>LogicalRewriteWrite</literal></entry>
<entry>Waiting for a write of logical rewrite mappings.</entry>
</row>
+ <row>
+ <entry><literal>ProcSignalBarrier</literal></entry>
+ <entry>Waiting for a barrier event to be processed by all backends.</entry>
+ </row>
<row>
<entry><literal>RelationMapRead</literal></entry>
<entry>Waiting for a read of the relation map file.</entry>
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1792008ebe..52ad99f716 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -820,6 +820,10 @@ HandleAutoVacLauncherInterrupts(void)
rebuild_database_list(InvalidOid);
}
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
/* Process sinval catchup interrupts that happened while sleeping */
ProcessCatchupInterrupt();
}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3f35b324c3..d1185cc135 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -523,6 +523,9 @@ CheckpointerMain(void)
static void
HandleCheckpointerInterrupts(void)
{
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ConfigReloadPending)
{
ConfigReloadPending = false;
@@ -709,6 +712,10 @@ CheckpointWriteDelay(int flags, double progress)
AbsorbSyncRequests();
absorb_counter = WRITES_PER_ABSORB;
}
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
/*
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index 6900cd02f6..214ccaf34b 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -20,6 +20,7 @@
#include "postmaster/interrupt.h"
#include "storage/ipc.h"
#include "storage/latch.h"
+#include "storage/procsignal.h"
#include "utils/guc.h"
volatile sig_atomic_t ConfigReloadPending = false;
@@ -31,6 +32,9 @@ volatile sig_atomic_t ShutdownRequestPending = false;
void
HandleMainLoopInterrupts(void)
{
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ConfigReloadPending)
{
ConfigReloadPending = false;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index e931512203..7410b2ff5e 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3988,6 +3988,9 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_LOGICAL_REWRITE_WRITE:
event_name = "LogicalRewriteWrite";
break;
+ case WAIT_EVENT_PROC_SIGNAL_BARRIER:
+ event_name = "ProcSignalBarrier";
+ break;
case WAIT_EVENT_RELATION_MAP_READ:
event_name = "RelationMapRead";
break;
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 4f59c71f73..11f7052e78 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -96,7 +96,7 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
errno = save_errno;
}
-/* Handle SIGHUP and SIGTERM signals of startup process */
+/* Handle various signals that might be sent to the startup process */
void
HandleStartupProcInterrupts(void)
{
@@ -121,6 +121,10 @@ HandleStartupProcInterrupts(void)
*/
if (IsUnderPostmaster && !PostmasterIsAlive())
exit(1);
+
+ /* Process barrier events */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index c36bcc08ec..a4de8a9cd8 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -148,7 +148,8 @@ ProcessWalRcvInterrupts(void)
/*
* Although walreceiver interrupt handling doesn't use the same scheme as
* regular backends, call CHECK_FOR_INTERRUPTS() to make sure we receive
- * any incoming signals on Win32.
+ * any incoming signals on Win32, and also to make sure we process any
+ * barrier events.
*/
CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7ad10736d5..1f10a97dc7 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -1852,6 +1852,10 @@ BufferSync(int flags)
}
UnlockBufHdr(bufHdr, buf_state);
+
+ /* Check for barrier events in case NBuffers is large. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
if (num_to_scan == 0)
@@ -1930,6 +1934,10 @@ BufferSync(int flags)
}
s->num_to_scan++;
+
+ /* Check for barrier events. */
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
}
Assert(num_spaces > 0);
@@ -2018,6 +2026,8 @@ BufferSync(int flags)
/*
* Sleep to throttle our I/O rate.
+ *
+ * (This will check for barrier events even if it doesn't sleep.)
*/
CheckpointWriteDelay(flags, (double) num_processed / num_to_scan);
}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index fde97a1036..06e00eae15 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -20,6 +20,7 @@
#include "access/parallel.h"
#include "commands/async.h"
#include "miscadmin.h"
+#include "pgstat.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -45,13 +46,36 @@
* The flags are actually declared as "volatile sig_atomic_t" for maximum
* portability. This should ensure that loads and stores of the flag
* values are atomic, allowing us to dispense with any explicit locking.
+ *
+ * pss_signalFlags are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal,
+ * but sometimes we need confirmation, as when making a global state change
+ * that cannot be considered complete until all backends have taken notice
+ * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * increment the current "barrier generation"; when the new barrier generation
+ * (or greater) appears in the pss_barrierGeneration flag of every process,
+ * we know that the message has been received everywhere.
*/
typedef struct
{
pid_t pss_pid;
sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
+ pg_atomic_uint64 pss_barrierGeneration;
+ pg_atomic_uint32 pss_barrierCheckMask;
} ProcSignalSlot;
+/*
+ * Information that is global to the entire ProcSignal system can be stored
+ * here.
+ *
+ * psh_barrierGeneration is the highest barrier generation in existence.
+ */
+typedef struct
+{
+ pg_atomic_uint64 psh_barrierGeneration;
+ ProcSignalSlot psh_slot[FLEXIBLE_ARRAY_MEMBER];
+} ProcSignalHeader;
+
/*
* We reserve a slot for each possible BackendId, plus one for each
* possible auxiliary process type. (This scheme assumes there is not
@@ -59,11 +83,16 @@ typedef struct
*/
#define NumProcSignalSlots (MaxBackends + NUM_AUXPROCTYPES)
-static ProcSignalSlot *ProcSignalSlots = NULL;
+/* Check whether the relevant type bit is set in the flags. */
+#define BARRIER_SHOULD_CHECK(flags, type) \
+ (((flags) & (((uint32) 1) << (uint32) (type))) != 0)
+
+static ProcSignalHeader *ProcSignal = NULL;
static volatile ProcSignalSlot *MyProcSignalSlot = NULL;
static bool CheckProcSignal(ProcSignalReason reason);
static void CleanupProcSignalState(int status, Datum arg);
+static void ProcessBarrierPlaceholder(void);
/*
* ProcSignalShmemSize
@@ -72,7 +101,11 @@ static void CleanupProcSignalState(int status, Datum arg);
Size
ProcSignalShmemSize(void)
{
- return NumProcSignalSlots * sizeof(ProcSignalSlot);
+ Size size;
+
+ size = mul_size(NumProcSignalSlots, sizeof(ProcSignalSlot));
+ size = add_size(size, offsetof(ProcSignalHeader, psh_slot));
+ return size;
}
/*
@@ -85,12 +118,26 @@ ProcSignalShmemInit(void)
Size size = ProcSignalShmemSize();
bool found;
- ProcSignalSlots = (ProcSignalSlot *)
- ShmemInitStruct("ProcSignalSlots", size, &found);
+ ProcSignal = (ProcSignalHeader *)
+ ShmemInitStruct("ProcSignal", size, &found);
- /* If we're first, set everything to zeroes */
+ /* If we're first, initialize. */
if (!found)
- MemSet(ProcSignalSlots, 0, size);
+ {
+ int i;
+
+ pg_atomic_init_u64(&ProcSignal->psh_barrierGeneration, 0);
+
+ for (i = 0; i < NumProcSignalSlots; ++i)
+ {
+ ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+
+ slot->pss_pid = 0;
+ MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
+ pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+ pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
+ }
+ }
}
/*
@@ -104,10 +151,11 @@ void
ProcSignalInit(int pss_idx)
{
volatile ProcSignalSlot *slot;
+ uint64 barrier_generation;
Assert(pss_idx >= 1 && pss_idx <= NumProcSignalSlots);
- slot = &ProcSignalSlots[pss_idx - 1];
+ slot = &ProcSignal->psh_slot[pss_idx - 1];
/* sanity check */
if (slot->pss_pid != 0)
@@ -117,6 +165,23 @@ ProcSignalInit(int pss_idx)
/* Clear out any leftover signal reasons */
MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
+ /*
+ * Initialize barrier state. Since we're a brand-new process, there
+ * shouldn't be any leftover backend-private state that needs to be
+ * updated. Therefore, we can broadcast the latest barrier generation
+ * and disregard any previously-set check bits.
+ *
+ * NB: This only works if this initialization happens early enough in the
+ * startup sequence that we haven't yet cached any state that might need
+ * to be invalidated. That's also why we have a memory barrier here, to
+ * be sure that any later reads of memory happen strictly after this.
+ */
+ pg_atomic_write_u32(&slot->pss_barrierCheckMask, 0);
+ barrier_generation =
+ pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation);
+ pg_memory_barrier();
+
/* Mark slot with my PID */
slot->pss_pid = MyProcPid;
@@ -129,7 +194,7 @@ ProcSignalInit(int pss_idx)
/*
* CleanupProcSignalState
- * Remove current process from ProcSignalSlots
+ * Remove current process from ProcSignal mechanism
*
* This function is called via on_shmem_exit() during backend shutdown.
*/
@@ -139,7 +204,7 @@ CleanupProcSignalState(int status, Datum arg)
int pss_idx = DatumGetInt32(arg);
volatile ProcSignalSlot *slot;
- slot = &ProcSignalSlots[pss_idx - 1];
+ slot = &ProcSignal->psh_slot[pss_idx - 1];
Assert(slot == MyProcSignalSlot);
/*
@@ -161,6 +226,12 @@ CleanupProcSignalState(int status, Datum arg)
return; /* XXX better to zero the slot anyway? */
}
+ /*
+ * Make this slot look like it's absorbed all possible barriers, so that
+ * no barrier waits block on it.
+ */
+ pg_atomic_write_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
+
slot->pss_pid = 0;
}
@@ -182,7 +253,7 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
if (backendId != InvalidBackendId)
{
- slot = &ProcSignalSlots[backendId - 1];
+ slot = &ProcSignal->psh_slot[backendId - 1];
/*
* Note: Since there's no locking, it's possible that the target
@@ -212,7 +283,7 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
for (i = NumProcSignalSlots - 1; i >= 0; i--)
{
- slot = &ProcSignalSlots[i];
+ slot = &ProcSignal->psh_slot[i];
if (slot->pss_pid == pid)
{
@@ -230,6 +301,187 @@ SendProcSignal(pid_t pid, ProcSignalReason reason, BackendId backendId)
return -1;
}
+/*
+ * EmitProcSignalBarrier
+ * Send a signal to every Postgres process
+ *
+ * The return value of this function is the barrier "generation" created
+ * by this operation. This value can be passed to WaitForProcSignalBarrier
+ * to wait until it is known that every participant in the ProcSignal
+ * mechanism has absorbed the signal (or started afterwards).
+ *
+ * Note that it would be a bad idea to use this for anything that happens
+ * frequently, as interrupting every backend could cause a noticeable
+ * performance hit.
+ *
+ * Callers are entitled to assume that this function will not throw ERROR
+ * or FATAL.
+ */
+uint64
+EmitProcSignalBarrier(ProcSignalBarrierType type)
+{
+ uint64 flagbit = UINT64CONST(1) << (uint64) type;
+ uint64 generation;
+
+ /*
+ * Set all the flags.
+ *
+ * Note that pg_atomic_fetch_or_u32 has full barrier semantics, so this
+ * is totally ordered with respect to anything the caller did before, and
+ * anything that we do afterwards. (This is also true of the later call
+ * to pg_atomic_add_fetch_u64.)
+ */
+ for (int i = 0; i < NumProcSignalSlots; i++)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+
+ pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit);
+ }
+
+ /*
+ * Increment the generation counter.
+ */
+ generation =
+ pg_atomic_add_fetch_u64(&ProcSignal->psh_barrierGeneration, 1);
+
+ /*
+ * Signal all the processes, so that they update their advertised barrier
+ * generation.
+ *
+ * Concurrency is not a problem here. Backends that have exited don't
+ * matter, and new backends that have joined since we entered this function
+ * must already have current state, since the caller is responsible for
+ * making sure that the relevant state is entirely visible before calling
+ * this function in the first place. We still have to wake them up -
+ * because we can't distinguish between such backends and older backends
+ * that need to update state - but they won't actually need to change
+ * any state.
+ */
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+ pid_t pid = slot->pss_pid;
+
+ if (pid != 0)
+ kill(pid, SIGUSR1);
+ }
+
+ return generation;
+}
+
+/*
+ * WaitForProcSignalBarrier - wait until it is guaranteed that all changes
+ * requested by a specific call to EmitProcSignalBarrier() have taken effect.
+ *
+ * We expect that the barrier will normally be absorbed very quickly by other
+ * backends, so we start by waiting just 1/8 of a second and then back off
+ * by a factor of two every time we time out, to a maximum wait time of
+ * 1 second.
+ */
+void
+WaitForProcSignalBarrier(uint64 generation)
+{
+ long timeout = 125L;
+
+ for (int i = NumProcSignalSlots - 1; i >= 0; i--)
+ {
+ volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+ uint64 oldval;
+
+ oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ while (oldval < generation)
+ {
+ int events;
+
+ CHECK_FOR_INTERRUPTS();
+
+ events =
+ WaitLatch(MyLatch,
+ WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+ timeout, WAIT_EVENT_PROC_SIGNAL_BARRIER);
+ ResetLatch(MyLatch);
+
+ oldval = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ if (events & WL_TIMEOUT)
+ timeout = Min(timeout * 2, 1000L);
+ }
+ }
+
+ /*
+ * The caller is probably calling this function because it wants to
+ * read the shared state or perform further writes to shared state once
+ * all backends are known to have absorbed the barrier. However, the
+ * read of pss_barrierGeneration was performed unlocked; insert a memory
+ * barrier to separate it from whatever follows.
+ */
+ pg_memory_barrier();
+}
+
+/*
+ * Perform global barrier related interrupt checking.
+ *
+ * Any backend that participates in ProcSignal signalling must arrange to
+ * call this function periodically. It is called from CHECK_FOR_INTERRUPTS(),
+ * which is enough for normal backends, but not necessarily for all types of
+ * background processes.
+ */
+void
+ProcessProcSignalBarrier(void)
+{
+ uint64 generation;
+ uint32 flags;
+
+ /* Exit quickly if there's no work to do. */
+ if (!ProcSignalBarrierPending)
+ return;
+ ProcSignalBarrierPending = false;
+
+ /*
+ * Read the current barrier generation, and then get the flags that
+ * are set for this backend. Note that pg_atomic_exchange_u32 is a full
+ * barrier, so we're guaranteed that the read of the barrier generation
+ * happens before we atomically extract the flags, and that any subsequent
+ * state changes happen afterward.
+ */
+ generation = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ flags = pg_atomic_exchange_u32(&MyProcSignalSlot->pss_barrierCheckMask, 0);
+
+ /*
+ * Process each type of barrier. It's important that nothing we call from
+ * here throws an error, because pss_barrierCheckMask has already been
+ * cleared. If we jumped out of here before processing all barrier types,
+ * then we'd forget about the need to do so later.
+ *
+ * NB: It ought to be OK to call the barrier-processing functions
+ * unconditionally, but it's more efficient to call only the ones that
+ * might need us to do something based on the flags.
+ */
+ if (BARRIER_SHOULD_CHECK(flags, PROCSIGNAL_BARRIER_PLACEHOLDER))
+ ProcessBarrierPlaceholder();
+
+ /*
+ * State changes related to all types of barriers that might have been
+ * emitted have now been handled, so we can update our notion of the
+ * generation to the one we observed before beginning the updates. If
+ * things have changed further, it'll get fixed up when this function is
+ * next called.
+ */
+ pg_atomic_write_u64(&MyProcSignalSlot->pss_barrierGeneration, generation);
+}
+
+static void
+ProcessBarrierPlaceholder(void)
+{
+ /*
+ * XXX. This is just a placeholder until the first real user of this
+ * machinery gets committed. Rename PROCSIGNAL_BARRIER_PLACEHOLDER to
+ * PROCSIGNAL_BARRIER_SOMETHING_ELSE where SOMETHING_ELSE is something
+ * appropriately descriptive. Get rid of this function and instead have
+ * ProcessBarrierSomethingElse. Most likely, that function should live
+ * in the file pertaining to that subsystem, rather than here.
+ */
+}
+
/*
* CheckProcSignal - check to see if a particular reason has been
* signaled, and clear the signal flag. Should be called after receiving
@@ -253,6 +505,27 @@ CheckProcSignal(ProcSignalReason reason)
return false;
}
+/*
+ * CheckProcSignalBarrier - check for new barriers we need to absorb
+ */
+static bool
+CheckProcSignalBarrier(void)
+{
+ volatile ProcSignalSlot *slot = MyProcSignalSlot;
+
+ if (slot != NULL)
+ {
+ uint64 mygen;
+ uint64 curgen;
+
+ mygen = pg_atomic_read_u64(&slot->pss_barrierGeneration);
+ curgen = pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration);
+ return (mygen != curgen);
+ }
+
+ return false;
+}
+
/*
* procsignal_sigusr1_handler - handle SIGUSR1 signal.
*/
@@ -291,6 +564,12 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ if (CheckProcSignalBarrier())
+ {
+ InterruptPending = true;
+ ProcSignalBarrierPending = true;
+ }
+
SetLatch(MyLatch);
latch_sigusr1_handler();
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ef5a952968..c42d9ce09a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3180,6 +3180,9 @@ ProcessInterrupts(void)
}
+ if (ProcSignalBarrierPending)
+ ProcessProcSignalBarrier();
+
if (ParallelMessagePending)
HandleParallelMessages();
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 4473e18e53..3a091022e2 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,6 +32,7 @@ volatile sig_atomic_t QueryCancelPending = false;
volatile sig_atomic_t ProcDiePending = false;
volatile sig_atomic_t ClientConnectionLost = false;
volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
+volatile sig_atomic_t ProcSignalBarrierPending = false;
volatile uint32 InterruptHoldoffCount = 0;
volatile uint32 QueryCancelHoldoffCount = 0;
volatile uint32 CritSectionCount = 0;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 24f43ad686..ed80f1d668 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,6 +82,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index fe076d823d..f2e873d048 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -908,6 +908,7 @@ typedef enum
WAIT_EVENT_LOGICAL_REWRITE_SYNC,
WAIT_EVENT_LOGICAL_REWRITE_TRUNCATE,
WAIT_EVENT_LOGICAL_REWRITE_WRITE,
+ WAIT_EVENT_PROC_SIGNAL_BARRIER,
WAIT_EVENT_RELATION_MAP_READ,
WAIT_EVENT_RELATION_MAP_SYNC,
WAIT_EVENT_RELATION_MAP_WRITE,
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 05b186a05c..b7d0d43f0d 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -45,6 +45,16 @@ typedef enum
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
+typedef enum
+{
+ /*
+ * XXX. PROCSIGNAL_BARRIER_PLACEHOLDER should be replaced when the first
+ * real user of the ProcSignalBarrier mechanism is added. It's just here
+ * for now because we can't have an empty enum.
+ */
+ PROCSIGNAL_BARRIER_PLACEHOLDER = 0
+} ProcSignalBarrierType;
+
/*
* prototypes for functions in procsignal.c
*/
@@ -55,6 +65,10 @@ extern void ProcSignalInit(int pss_idx);
extern int SendProcSignal(pid_t pid, ProcSignalReason reason,
BackendId backendId);
+extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
+extern void WaitForProcSignalBarrier(uint64 generation);
+extern void ProcessProcSignalBarrier(void);
+
extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
#endif /* PROCSIGNAL_H */
--
2.17.2 (Apple Git-113)
Hello
Stellar. If nobody objects in the meantime, I plan to commit 0001-0003
next week.
My compiler (gcc 8.3.0) is not happy with recent 5910d6c7e311f0b14e3d3cb9ce3597c01d3a3cde commit:
autovacuum.c:831:1: error: ‘AutoVacLauncherShutdown’ was used with no prototype before its definition [-Werror=missing-prototypes]
checkpointer.c:524:1: error: ‘HandleCheckpointerInterrupts’ was used with no prototype before its definition [-Werror=missing-prototypes]
I think definition should looks as in attached patch. With this change build is clean
regards, Sergei
Attachments:
gcc_missing_prototypes.patchtext/x-diff; name=gcc_missing_prototypes.patchDownload
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 1792008ebe..47b4bacadb 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -311,7 +311,7 @@ NON_EXEC_STATIC void AutoVacLauncherMain(int argc, char *argv[]) pg_attribute_no
static Oid do_start_worker(void);
static void HandleAutoVacLauncherInterrupts(void);
-static void AutoVacLauncherShutdown() pg_attribute_noreturn();
+static void AutoVacLauncherShutdown(void) pg_attribute_noreturn();
static void launcher_determine_sleep(bool canlaunch, bool recursing,
struct timeval *nap);
static void launch_worker(TimestampTz now);
@@ -828,7 +828,7 @@ HandleAutoVacLauncherInterrupts(void)
* Perform a normal exit from the autovac launcher.
*/
static void
-AutoVacLauncherShutdown()
+AutoVacLauncherShutdown(void)
{
ereport(DEBUG1,
(errmsg("autovacuum launcher shutting down")));
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3f35b324c3..df527ac021 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -161,7 +161,7 @@ static pg_time_t last_xlog_switch_time;
/* Prototypes for private functions */
-static void HandleCheckpointerInterrupts();
+static void HandleCheckpointerInterrupts(void);
static void CheckArchiveTimeout(void);
static bool IsCheckpointOnSchedule(double progress);
static bool ImmediateCheckpointRequested(void);
On Tue, Dec 17, 2019 at 1:44 PM Sergei Kornilov <sk@zsrv.org> wrote:
Stellar. If nobody objects in the meantime, I plan to commit 0001-0003
next week.My compiler (gcc 8.3.0) is not happy with recent 5910d6c7e311f0b14e3d3cb9ce3597c01d3a3cde commit:
autovacuum.c:831:1: error: ‘AutoVacLauncherShutdown’ was used with no prototype before its definition [-Werror=missing-prototypes]
checkpointer.c:524:1: error: ‘HandleCheckpointerInterrupts’ was used with no prototype before its definition [-Werror=missing-prototypes]I think definition should looks as in attached patch. With this change build is clean
Andrew Gierth complained about this too over on -committers, and I saw
his message first and pushed a fix. It includes the first and third
hunks from your proposed patch, but not the second one.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Hi
Andrew Gierth complained about this too over on -committers, and I saw
his message first and pushed a fix. It includes the first and third
hunks from your proposed patch, but not the second one.
Yep, I received his email just after sending mine. Thanks, my build is clean now.
regards, Sergei
On Tue, Dec 17, 2019 at 1:38 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Dec 12, 2019 at 2:54 PM Andres Freund <andres@anarazel.de> wrote:
I'd either add a test (if we have some) or placeholder kind
initially. But I'd also be ok with going for either of the other
versions directly - but it seems harder to tackle the patches together.OK. I have committed 0001-0003 as I had mentioned last week that I
intended to do. For 0004, I have replaced "sample" with "placeholder"
and added comments explaining that this is intended to be replaced by
the first real user of the mechanism. If there are no further
comments/objections I'll go ahead with this one as well.
And, done.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Thu, Dec 19, 2019 at 8:57 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Dec 17, 2019 at 1:38 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Dec 12, 2019 at 2:54 PM Andres Freund <andres@anarazel.de>
wrote:
I'd either add a test (if we have some) or placeholder kind
initially. But I'd also be ok with going for either of the other
versions directly - but it seems harder to tackle the patches together.OK. I have committed 0001-0003 as I had mentioned last week that I
intended to do. For 0004, I have replaced "sample" with "placeholder"
and added comments explaining that this is intended to be replaced by
the first real user of the mechanism. If there are no further
comments/objections I'll go ahead with this one as well.And, done.
\o/
//Magnus
On Mon, Dec 9, 2019 at 10:42 AM Robert Haas <robertmhaas@gmail.com> wrote:
Hm. In my mental model it would be useful for barrier "processors" to
not acknowledge the state change at certain points. Imagine e.g. needing
to efficiently wait till all backends have processed a config file
reload - since we don't reload while a query is being processed, we
should be able to not ack the barrier at that point.Yeah, you mentioned this before, but I think we could leave it for
later. I think it's going to be complex. I suppose the way to do it
would be to add an argument to ProcessProcSignalBarrier() that lets
you specify which barrier events you're OK with processing from the
current point in the code. However, that will mean that whenever
somebody adds a new barrier type, they have got to go through all of
those callers and think about whether they should add their new
barrier type into the flags or not. If we try to do the specific thing
you're talking about with config-file processing, it will also mean
that we could be waiting MUCH longer for acknowledgements, which I
think might have pretty far-reaching ramifications. You might get
stuck waiting for that barrier to be absorbed for hours, and that
would also impact later barriers, since you won't see the generation
bump to 42 until it goes through 40 and 41. That sounds fairly awful.
You could try to work around it by looking at which barrier flags are
set, but I think that has ABA problems.
I might have rejected this idea too hastily. At any rate, I have a new
view on it having studied the issue a bit more.
In the case of ALTER SYSTEM READ ONLY, the big problem is that we
can't afford to find out that we're unable to emit WAL after we've
already entered a critical section, because "ERROR: system is read
only" or similar will get promoted to PANIC due to the critical
section and that will be sad. (Before someone asks, not promoting the
ERROR to PANIC would be unsafe, even for this specific case.) Some WAL
records don't use a critical section, though it's recently been
proposed[1]/messages/by-id/20191207001232.klidxnm756wqxvwx@alap3.anarazel.de that we add a critical section in at least some of the
cases that currently lack one. For those, I think we can just upgrade
the existing test-and-elog cases in XLogInsert() and XLogBeginInsert()
to ereport() and call it good. But the cases that do have a critical
section are harder.
Currently, I see only the following two options:
(1) Check just before entering a critical section whether or not the
system is read only, and if so, error out. Once we've entered a
critical section, ProcessInterrupts() will not do anything, so at that
point we're safe. This could be done either by making
START_CRIT_SECTION() itself do it or by finding every critical section
that is used for inserting WAL and adding a call just beforehand. The
latter is probably better, as some critical sections appear to be used
for other purposes; see PGSTAT_BEGIN_WRITE_ACTIVITY in particular. But
it means changing things in a bunch of places, adding an if-test. That
wouldn't add *much* overhead, but it's not quite zero.
(2) Accept barrier events for read-only/read-write changes only at
command boundaries. In that case, we only need to check for a read
only state around the places where we currently do
PreventCommandIfReadOnly and similar, and the additional overhead
should be basically nil. However, this seems pretty unappealing to me,
because it means that if you try to make the system READ ONLY, it
might run for hours before actually going read only. If you're trying
to make the system read only because the master has become isolated
from the rest of your network, that's not a fast enough response.
In general, I think the problem here is to avoid TOCTTOU problems. I
think the checksums patch has this problem too. For instance, the
changes to basebackup.c in that function check
DataChecksumsNeedVerify() only once per file, but what is to say that
a call to ProcessInterrupts() couldn't happen in the middle of sending
a file, changing prevailing value? If someone sets checksums off, and
everyone acknowledges that they're off, then backends may begin
writing blocks without setting checksums, and then this code will
potentially try to verify checksums in a block written without them.
That patch also modifies the definition of XLogHintBitIsNeeded(), so
e.g. log_heap_visible is wrong if a CHECK_FOR_INTERRUPTS() can happen
in XLogRegisterBuffer(). I don't think it can currently, but it seems
like a heck of a fragile assumption, and I don't see how we can be
sure that there's no test for XLogHintBitIsNeeded() that does
CHECK_FOR_INTERRUPTS() between where it's tested and where it does
something critical with the result of that test.
At the moment, the ALTER SYSTEM READ ONLY problem appears to me to be
the lesser of the two (although I may be wrong). Assuming people are
OK with inserting an additional check before each xlog-writing
critical section, we can just go do that and then I think the problem
is pretty much solved. The only way there would be a residual problem,
I think, is if something could change between the time XLogInsert()
checks whether xlog writing is allowed and the time when it does the
insert. Even if that's an issue, it can be fixed with a
HOLD_INTERRUPTS/RESUME_INTERRUPTS in just that function. The meaning
of READ ONLY is just that xlog insertion is prohibited, so the only
use of the value is for allowing XLogInsert() to go ahead or not. But
for checksums, there's not such a clear definition of what it means to
use the value, nor does it get used in only one place, so the
situation seems less clear.
In short, I am not still not sure that we need the facility Andres was
asking for here, but I do now understand better why Andres was worried
about having this capability, and I do think that patches using it are
going to need to be very carefully studied for TOCTTOU problems.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
[1]: /messages/by-id/20191207001232.klidxnm756wqxvwx@alap3.anarazel.de