Hi folks A few times lately I've been doing things in extensions that've made me want to be able to run my own code whenever InterruptPending is true and CHECK_FOR_INTERRUPTS() calls ProcessInterrupts()
So here's a simple patch to add ProcessInterrupts_hook. It follows the usual pattern like ProcessUtility_hook and standard_ProcessUtility. Why? Because sometimes I want most of the behaviour of die(), but the option to override it with some bgworker-specific choices occasionally. HOLD_INTERRUPTS() is too big a hammer. What I really want to go along with this is a way for any backend to observe the postmaster's pmState and its "Shutdown" variable's value, so any backend can tell if we're in FastShutdown, SmartShutdown, etc. Copies in shmem only obviously. But I'm not convinced it's right to just copy these vars as-is to shmem, and I don't want to use the memory for a ProcSignal slot for something that won't be relevant for most backends for most of the postmaster lifetime. Ideas welcomed.
From 1c8c477a5814420011fa034323e82d8eabc6bc5f Mon Sep 17 00:00:00 2001 From: Craig Ringer <craig.rin...@2ndquadrant.com> Date: Mon, 18 Jan 2021 14:14:46 +0800 Subject: [PATCH v1 1/4] Provide a hook for ProcessInterrupts() Code may now register a ProcessInterrupts_hook to fire its own logic before and/or after the main ProcessInterrupts handler for signal processing. The hook must call standard_ProcessInterrupts to execute the normal interrupt handling or set InterruptsPending to true to cause the interrupt to be re-processed at the next opportunity. This follows the consistent pattern used by other PostgreSQL hooks like the ProcessUtility_hook and standard_ProcessUtility() function. The purpose of this hook is to allow extensions to react to their own custom interrupt flags during CHECK_FOR_INTERRUPTS() calls invoked in main PostgreSQL code paths. --- src/backend/tcop/postgres.c | 20 ++++++++++++++++++++ src/include/miscadmin.h | 3 +++ 2 files changed, 23 insertions(+) diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 28055680aa..8cf1359e33 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -102,6 +102,8 @@ int max_stack_depth = 100; /* wait N seconds to allow attach from a debugger */ int PostAuthDelay = 0; +/* Intercept CHECK_FOR_INTERRUPTS() responses */ +ProcessInterrupts_hook_type ProcessInterrupts_hook = NULL; /* ---------------- @@ -3064,8 +3066,26 @@ ProcessInterrupts(void) /* OK to accept any interrupts now? */ if (InterruptHoldoffCount != 0 || CritSectionCount != 0) return; + InterruptPending = false; + if (ProcessInterrupts_hook) + ProcessInterrupts_hook(); + else + standard_ProcessInterrupts(); +} + +/* + * Implement the default signal handling behaviour for most backend types + * including user backends and bgworkers. + * + * This is where CHECK_FOR_INTERRUPTS() eventually lands up unless intercepted + * by ProcessInterrupts_hook. + */ +void +standard_ProcessInterrupts(void) +{ + if (ProcDiePending) { ProcDiePending = false; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 1bdc97e308..f082d04448 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -94,6 +94,9 @@ extern PGDLLIMPORT volatile uint32 CritSectionCount; /* in tcop/postgres.c */ extern void ProcessInterrupts(void); +typedef void (*ProcessInterrupts_hook_type)(void); +extern ProcessInterrupts_hook_type ProcessInterrupts_hook; +extern void standard_ProcessInterrupts(void); #ifndef WIN32 -- 2.29.2
From 58b9ac884ef5bd35a2aac9da85079e24097612be Mon Sep 17 00:00:00 2001 From: Craig Ringer <craig.rin...@2ndquadrant.com> Date: Mon, 18 Jan 2021 15:26:43 +0800 Subject: [PATCH v1 2/4] Test for ProcessInterrupts_hook --- src/test/modules/Makefile | 3 +- src/test/modules/test_hooks/.gitignore | 4 + src/test/modules/test_hooks/Makefile | 28 ++++ .../expected/test_processinterrupt_hook.out | 0 src/test/modules/test_hooks/hooks.conf | 1 + .../sql/test_processinterrupt_hook.sql | 16 ++ .../modules/test_hooks/test_hooks--1.0.sql | 8 + src/test/modules/test_hooks/test_hooks.c | 37 +++++ .../modules/test_hooks/test_hooks.control | 4 + src/test/modules/test_hooks/test_hooks.h | 20 +++ .../test_hooks/test_process_interrupts_hook.c | 140 ++++++++++++++++++ 11 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 src/test/modules/test_hooks/.gitignore create mode 100644 src/test/modules/test_hooks/Makefile create mode 100644 src/test/modules/test_hooks/expected/test_processinterrupt_hook.out create mode 100644 src/test/modules/test_hooks/hooks.conf create mode 100644 src/test/modules/test_hooks/sql/test_processinterrupt_hook.sql create mode 100644 src/test/modules/test_hooks/test_hooks--1.0.sql create mode 100644 src/test/modules/test_hooks/test_hooks.c create mode 100644 src/test/modules/test_hooks/test_hooks.control create mode 100644 src/test/modules/test_hooks/test_hooks.h create mode 100644 src/test/modules/test_hooks/test_process_interrupts_hook.c diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 59921b46cf..b6f1f25451 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -26,7 +26,8 @@ SUBDIRS = \ test_rls_hooks \ test_shm_mq \ unsafe_tests \ - worker_spi + worker_spi \ + test_hooks ifeq ($(with_openssl),yes) SUBDIRS += ssl_passphrase_callback diff --git a/src/test/modules/test_hooks/.gitignore b/src/test/modules/test_hooks/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_hooks/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_hooks/Makefile b/src/test/modules/test_hooks/Makefile new file mode 100644 index 0000000000..50be27ea34 --- /dev/null +++ b/src/test/modules/test_hooks/Makefile @@ -0,0 +1,28 @@ +# src/test/modules/test_hooks/Makefile + +MODULE_big = test_hooks +OBJS = \ + $(WIN32RES) \ + test_hooks.o \ + test_process_interrupts_hook.o +PGFILEDESC = "test_hooks - example use of assorted PostgreSQL hooks" + +EXTENSION = test_hooks +DATA = test_hooks--1.0.sql + +REGRESS = test_processinterrupt_hook +REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_hooks/hooks.conf +# Disabled because these tests require "shared_preload_libraries=test_hooks", +# which typical installcheck users do not have (e.g. buildfarm clients). +NO_INSTALLCHECK = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_hooks +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_hooks/expected/test_processinterrupt_hook.out b/src/test/modules/test_hooks/expected/test_processinterrupt_hook.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/test/modules/test_hooks/hooks.conf b/src/test/modules/test_hooks/hooks.conf new file mode 100644 index 0000000000..d574606694 --- /dev/null +++ b/src/test/modules/test_hooks/hooks.conf @@ -0,0 +1 @@ +shared_preload_libraries = test_hooks diff --git a/src/test/modules/test_hooks/sql/test_processinterrupt_hook.sql b/src/test/modules/test_hooks/sql/test_processinterrupt_hook.sql new file mode 100644 index 0000000000..4de9712346 --- /dev/null +++ b/src/test/modules/test_hooks/sql/test_processinterrupt_hook.sql @@ -0,0 +1,16 @@ +CREATE EXTENSION test_hooks; + +SELECT get_times_processed_interrupts() > 0; + +SELECT test_hold_interrupts_hook(); + +SELECT set_exit_deferred(true); +SELECT pg_terminate_backend(pg_backend_pid()); +SELECT 1; +SELECT set_exit_deferred(false); +SELECT 1; + +SELECT pg_backend_pid(); +SELECT pg_terminate_backend(pg_backend_pid()); +SELECT pg_backend_pid(); +SELECT 1; diff --git a/src/test/modules/test_hooks/test_hooks--1.0.sql b/src/test/modules/test_hooks/test_hooks--1.0.sql new file mode 100644 index 0000000000..5a9ea757a7 --- /dev/null +++ b/src/test/modules/test_hooks/test_hooks--1.0.sql @@ -0,0 +1,8 @@ +CREATE FUNCTION get_times_processed_interrupts() +RETURNS pg_catalog.int4 LANGUAGE c VOLATILE AS 'MODULE_PATHNAME'; + +CREATE FUNCTION set_exit_deferred(pg_catalog.bool) +RETURNS pg_catalog.void LANGUAGE c VOLATILE AS 'MODULE_PATHNAME'; + +CREATE FUNCTION test_hold_interrupts_hook() +RETURNS pg_catalog.void LANGUAGE c VOLATILE AS 'MODULE_PATHNAME'; diff --git a/src/test/modules/test_hooks/test_hooks.c b/src/test/modules/test_hooks/test_hooks.c new file mode 100644 index 0000000000..9d5422e2ac --- /dev/null +++ b/src/test/modules/test_hooks/test_hooks.c @@ -0,0 +1,37 @@ +/*-------------------------------------------------------------------------- + * + * test_hooks.c + * Code for testing RLS hooks. + * + * Copyright (c) 2015-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_hooks/test_hooks.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "miscadmin.h" +#include "test_hooks.h" + +PG_MODULE_MAGIC; + +void _PG_init(void); +void _PG_fini(void); + +/* Install hooks */ +void +_PG_init(void) +{ + install_test_process_interrupts_hook(); +} + +/* Uninstall hooks */ +void +_PG_fini(void) +{ + uninstall_test_process_interrupts_hook(); +} diff --git a/src/test/modules/test_hooks/test_hooks.control b/src/test/modules/test_hooks/test_hooks.control new file mode 100644 index 0000000000..539f5f0fa3 --- /dev/null +++ b/src/test/modules/test_hooks/test_hooks.control @@ -0,0 +1,4 @@ +comment = 'Test code for various PostgreSQL hooks' +default_version = '1.0' +module_pathname = '$libdir/test_hooks' +relocatable = true diff --git a/src/test/modules/test_hooks/test_hooks.h b/src/test/modules/test_hooks/test_hooks.h new file mode 100644 index 0000000000..6879a0e3bd --- /dev/null +++ b/src/test/modules/test_hooks/test_hooks.h @@ -0,0 +1,20 @@ +/*-------------------------------------------------------------------------- + * + * test_hooks.h + * Definitions for various PostgreSQL hooks + * + * Copyright (c) 2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_hooks/test_hooks.h + * + * ------------------------------------------------------------------------- + */ + +#ifndef TEST_HOOKS_H +#define TEST_HOOKS_H + +extern void install_test_process_interrupts_hook(void); +extern void uninstall_test_process_interrupts_hook(void); + +#endif diff --git a/src/test/modules/test_hooks/test_process_interrupts_hook.c b/src/test/modules/test_hooks/test_process_interrupts_hook.c new file mode 100644 index 0000000000..8f39ad5e91 --- /dev/null +++ b/src/test/modules/test_hooks/test_process_interrupts_hook.c @@ -0,0 +1,140 @@ +/*-------------------------------------------------------------------------- + * + * test_process_interrupts_hook.c + * Code for testing RLS hooks. + * + * Copyright (c) 2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_hooks/test_process_interrupts_hook.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "storage/latch.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "test_hooks.h" + +/* Saved hook values in case of unload */ +static ProcessInterrupts_hook_type original_ProcessInterrupts_hook = NULL; + +/* Next hooks to call in the chain */ +static ProcessInterrupts_hook_type next_ProcessInterrupts_hook = NULL; + +static int times_processed_interrupts; +static bool exit_deferred; +static bool saved_ProcDiePending; + +static void +test_ProcessInterrupts_hook(void) +{ + /* Hook never gets to fire if interrupts are held */ + Assert(InterruptHoldoffCount == 0); + + /* + * Typically you'd HOLD_INTERRUPTS() instead of this hack to defer + * interrupts. But just as an example, this extension will defer + * processing of SIGTERM via die() while acting normally on other + * signals. + * + * It'll react to the die() when exit_deferred=false is set and its + * latch is set or it's woken for some other reason, since it does not + * restore InterruptsPending=true. + * + * This relies on running in a normal user backend that uses die() + * as its SIGTERM handler. + */ + if (exit_deferred && ProcDiePending) + { + saved_ProcDiePending = true; + ProcDiePending = false; + } + else if (!exit_deferred && saved_ProcDiePending) + { + ProcDiePending = true; + saved_ProcDiePending = false; + } + + if (next_ProcessInterrupts_hook) + next_ProcessInterrupts_hook(); + else + standard_ProcessInterrupts(); + + times_processed_interrupts ++; +} + +void +install_test_process_interrupts_hook(void) +{ + /* Save values for unload */ + original_ProcessInterrupts_hook = ProcessInterrupts_hook; + + /* Set our hooks */ + next_ProcessInterrupts_hook = ProcessInterrupts_hook; + ProcessInterrupts_hook = test_ProcessInterrupts_hook; + + times_processed_interrupts = 0; + exit_deferred = false; +} + +void +uninstall_test_process_interrupts_hook(void) +{ + if (ProcessInterrupts_hook != test_ProcessInterrupts_hook) + elog(ERROR, "cannot unload test_hooks: another hook has been registered on the ProcessInterrupts_hook"); + + ProcessInterrupts_hook = original_ProcessInterrupts_hook; +} + +PG_FUNCTION_INFO_V1(get_times_processed_interrupts); + +Datum +get_times_processed_interrupts(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(times_processed_interrupts); +} + +PG_FUNCTION_INFO_V1(set_exit_deferred); + +Datum +set_exit_deferred(PG_FUNCTION_ARGS) +{ + if (PG_ARGISNULL(0)) + elog(ERROR, "argument must be non-null"); + exit_deferred = PG_GETARG_BOOL(0); + if (!exit_deferred) + InterruptPending = true; + PG_RETURN_VOID(); +} + +PG_FUNCTION_INFO_V1(test_hold_interrupts_hook); + +/* + * Make sure the hook doesn't fire when interrupts are held + */ +Datum +test_hold_interrupts_hook(PG_FUNCTION_ARGS) +{ + int rc; + + HOLD_INTERRUPTS(); + + SetLatch(MyLatch); + + CHECK_FOR_INTERRUPTS(); + + rc = WaitLatch(MyLatch, WL_LATCH_SET|WL_TIMEOUT|WL_EXIT_ON_PM_DEATH, 1000, PG_WAIT_EXTENSION); + + RESUME_INTERRUPTS(); + + if (rc & WL_LATCH_SET) + elog(ERROR, "received latch set event with interrupts held"); + if (rc & WL_TIMEOUT) + elog(INFO, "timed out as expected"); + + PG_RETURN_VOID(); +} -- 2.29.2