From f1bfa198c014c411b64b5ae0054f07ee277fb414 Mon Sep 17 00:00:00 2001 From: Taiki Koshino Date: Tue, 21 Jul 2026 14:46:40 +0900 Subject: [PATCH v1] Convert close_idle_connection (SIGUSR1) to flag-only handler with main-loop processing. The SIGUSR1 handler in the child process directly performed connection cleanup, including pool_free_startup_packet() and pool_close(). These operations are not async-signal-safe and can modify connection pool memory while the main loop is using it. In particular, SIGUSR1 can arrive while connect_using_existing_connection() is replacing startup packet pointers. This can cause the signal handler to free the same startup packet or connection pool that the main loop is currently accessing. This issue was reproduced in a local test environment, where sending SIGUSR1 during startup packet replacement caused the child process to terminate with a segmentation fault. Changes: Flag-only Signal Handler: Make close_idle_connection() only set a volatile sig_atomic_t flag and return. Main-loop Processing: Process the pending request from the child main loop and perform connection cleanup outside the signal handler. Direct Cleanup: Update the failover/failback restart path to call the cleanup function directly. Reported-by: Emond Papegaaij Reported-by: Claude code Author: Taiki Koshino Discussion: Backpatch-through: v4.3 --- src/protocol/child.c | 46 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/protocol/child.c b/src/protocol/child.c index 9e521e9d0..eaa953d83 100644 --- a/src/protocol/child.c +++ b/src/protocol/child.c @@ -72,6 +72,8 @@ static StartupPacket *read_startup_packet(POOL_CONNECTION *cp); static POOL_CONNECTION_POOL *connect_backend(StartupPacket *sp, POOL_CONNECTION *frontend); static RETSIGTYPE die(int sig); static RETSIGTYPE close_idle_connection(int sig); +static void close_idle_connection_now(void); +static void check_close_idle_connection_request(void); static RETSIGTYPE wakeup_handler(int sig); static RETSIGTYPE reload_config_handler(int sig); static RETSIGTYPE authentication_timeout(int sig); @@ -112,6 +114,15 @@ static volatile sig_atomic_t alarm_enabled = false; */ volatile sig_atomic_t ignore_sigusr1 = 0; +/* + * Set by close_idle_connection() (SIGUSR1 handler) so the work is deferred to + * a safe point in the PT_CHILD main loop. The handler body must not call + * pfree(), pool_write(), or any OpenSSL routine — none of which are + * async-signal-safe and several of which (SSL_*, the palloc/pfree free-list) + * can be re-entered while the main loop is mid-mutation. + */ +static volatile sig_atomic_t close_idle_connection_pending = 0; + /* * si modules use SIGUSR2 */ @@ -323,6 +334,7 @@ do_child(int *fds) check_stop_request(); check_restart_request(); check_exit_request(); + check_close_idle_connection_request(); accepted = 0; /* Destroy session context for just in case... */ pool_session_context_destroy(); @@ -1179,15 +1191,41 @@ static RETSIGTYPE die(int sig) /* * signal handler for SIGUSR1 - * close all idle connections + * + * Async-signal-safe body only: just record that a close-idle request has + * arrived. The actual work (which calls pfree(), pool_write(), and OpenSSL + * SSL_shutdown/SSL_free, none of which are async-signal-safe) is performed + * by close_idle_connection_now() at a safe point in the main loop. */ static RETSIGTYPE close_idle_connection(int sig) +{ + close_idle_connection_pending = 1; +} + +/* + * Process a pending SIGUSR1 close-idle request from the child main loop. + * Must NOT be called from signal context. + */ +static void +check_close_idle_connection_request(void) +{ + if (!close_idle_connection_pending) + return; + close_idle_connection_pending = 0; + close_idle_connection_now(); +} + +/* + * Close all idle connections. Original body of close_idle_connection(), + * relocated out of signal context. + */ +static void +close_idle_connection_now(void) { int i, j; POOL_CONNECTION_POOL *p = pool_connection_pool; ConnectionInfo *info; - int save_errno = errno; int main_node_id; /* @@ -1234,8 +1272,6 @@ static RETSIGTYPE close_idle_connection(int sig) memset(p->info, 0, sizeof(ConnectionInfo)); } } - - errno = save_errno; } /* @@ -2064,7 +2100,7 @@ retry_startup: errdetail("failover or failback event detected, discarding existing connections"))); pool_get_my_process_info()->need_to_restart = 0; - close_idle_connection(0); + close_idle_connection_now(); pool_initialize_private_backend_status(); } -- 2.47.3