From a52a89204e7664d4a60e6f07fca92ae2ecf1c840 Mon Sep 17 00:00:00 2001 From: Zexin Li Date: Thu, 20 Aug 2026 00:43:06 +0000 Subject: [PATCH] Fix postmaster wedge when startup process crashes during crash restart Commit 9b43e6793b0f removed the PM_STARTUP shortcut that made the postmaster exit when the startup process died unexpectedly, relying on the regular crash path (HandleChildCrash()) to also cover this case, so that the other children that may now be running during PM_STARTUP are cleaned up rather than orphaned. However, HandleChildCrash() returns without doing anything when FatalError is already set, which is exactly the state when a relaunched startup process crashes during a crash restart before WAL redo has started (receipt of PMSIGNAL_RECOVERY_STARTED is what clears FatalError). In that case nothing moves the state machine off PM_STARTUP: StartupStatus is set to STARTUP_CRASHED, but the only place consulting that flag (PM_NO_CHILDREN handling) is never reached, and the postmaster keeps relaunching the checkpointer, the background writer, and the IO workers forever, without writing anything to the log. In the report, a Windows postmaster whose console had gone away -- so that every child it created died with 0xC0000142 during process initialization -- burned a full CPU core silently respawning children, while pg_ctl still reported the server as running and shutdown requests were not honored. Fix by restoring the historical give-up behavior for this case: when the startup process crashes while FatalError is still set, log the child exit, signal the remaining children, and proceed towards PM_NO_CHILDREN, where the existing STARTUP_CRASHED check makes the postmaster exit. Unlike before 9b43e6793b0f, this goes through the regular fatal-error path instead of exiting directly, so no children are orphaned. Bug: #19623 Reported-by: KUAN-TING KUO Author: Zexin Li Discussion: https://postgr.es/m/19623-f9bd331940be1273@postgresql.org --- src/backend/postmaster/postmaster.c | 33 ++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 1711743f1a..2f0580699d 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -432,6 +432,7 @@ static void process_pm_shutdown_request(void); static void dummy_handler(SIGNAL_ARGS); static void CleanupBackend(PMChild *bp, int exitstatus); static void HandleChildCrash(int pid, int exitstatus, const char *procname); +static void HandleFatalError(QuitSignalReason reason, bool consider_sigabrt); static void LogChildExit(int lev, const char *procname, int pid, int exitstatus); static void PostmasterStateMachine(void); @@ -2333,8 +2334,30 @@ process_pm_child_exit(void) } else StartupStatus = STARTUP_CRASHED; - HandleChildCrash(pid, exitstatus, - _("startup process")); + + /* + * If the startup process crashed while we were still + * reinitializing after a previous crash -- FatalError is + * still set, because this startup process died before WAL + * redo started -- then HandleChildCrash() would return + * without doing anything, and nothing else would ever move + * the state machine off PM_STARTUP: we would keep + * relaunching the remaining background processes forever, + * without logging anything. Instead, give up on startup: + * signal the remaining children and head for + * PM_NO_CHILDREN, where STARTUP_CRASHED makes us exit. + */ + if (StartupStatus == STARTUP_CRASHED && + FatalError && Shutdown != ImmediateShutdown) + { + LogChildExit(LOG, _("startup process"), pid, exitstatus); + ereport(LOG, + (errmsg("aborting startup due to startup process failure"))); + HandleFatalError(PMQUIT_FOR_CRASH, true); + } + else + HandleChildCrash(pid, exitstatus, + _("startup process")); continue; } @@ -2725,15 +2748,15 @@ CleanupBackend(PMChild *bp, * happened. Commonly the caller will have logged the reason for entering * FatalError state. * - * This should only be called when not already in FatalError or - * ImmediateShutdown state. + * This should only be called when not already in ImmediateShutdown state. + * Calling it again while FatalError is already set is fine (and re-signals + * any children launched since the previous call). */ static void HandleFatalError(QuitSignalReason reason, bool consider_sigabrt) { int sigtosend; - Assert(!FatalError); Assert(Shutdown != ImmediateShutdown); SetQuitSignalReason(reason); -- 2.34.1