From 7f07c21f247fa6d900c82ececb2a48f879ce5814 Mon Sep 17 00:00:00 2001
From: Andrey Borodin <amborodin@acm.org>
Date: Tue, 1 Sep 2026 23:58:07 +0500
Subject: [PATCH v1 3/3] Fix postmaster hang on shutdown requested during crash
 restart

If a smart or fast shutdown request arrives while the postmaster is still
reinitializing after a crash -- that is, while FatalError is set and before
the startup process has signalled PMSIGNAL_RECOVERY_STARTED -- then
process_pm_shutdown_request() moved the state machine to PM_STOP_BACKENDS and
signalled the children with SIGTERM.  The checkpointer relaunched for crash
recovery ignores SIGTERM, so the postmaster then waited for it in
PM_WAIT_BACKENDS forever.

There is no consistent state to preserve in the middle of crash recovery, so in
that situation terminate the children the same crash-style way the postmaster
already uses elsewhere, via HandleFatalError(), which sends SIGQUIT and heads
for PM_WAIT_BACKENDS / PM_NO_CHILDREN.  Immediate shutdown was already handled
this way; this brings the smart and fast paths in line for the crash-restart
window.

This is distinct from commit ead8f696b7cd, which handles the startup process
*crashing* during crash restart; here the startup process is healthy and the
trigger is an incoming shutdown request.

Reported-by: Sergey Shinderuk
Discussion: https://postgr.es/m/63dcad16-22de-4326-a395-5310bc7e05ff@postgrespro.ru
---
 src/backend/postmaster/postmaster.c | 35 +++++++++++++++++++++++++----
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5f7a7b368e1..aaf8873c0e7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2156,8 +2156,16 @@ process_pm_shutdown_request(void)
 				connsAllowed = false;
 			else if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
 			{
-				/* There should be no clients, so proceed to stop children */
-				UpdatePMState(PM_STOP_BACKENDS);
+				if (FatalError)
+				{
+					/* Crash recovery in progress; see the FastShutdown case */
+					HandleFatalError(PMQUIT_FOR_STOP, false);
+				}
+				else
+				{
+					/* No clients, so proceed to stop children */
+					UpdatePMState(PM_STOP_BACKENDS);
+				}
 			}
 
 			/*
@@ -2190,8 +2198,27 @@ process_pm_shutdown_request(void)
 
 			if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
 			{
-				/* Just shut down background processes silently */
-				UpdatePMState(PM_STOP_BACKENDS);
+				if (FatalError)
+				{
+					/*
+					 * We are reinitializing after a crash, and the shutdown
+					 * request arrived before the startup process signalled
+					 * PMSIGNAL_RECOVERY_STARTED, which is when FatalError
+					 * would have been cleared.  The auxiliary processes
+					 * relaunched for crash recovery expect crash-style
+					 * signalling: in particular the checkpointer ignores
+					 * SIGTERM, so the regular PM_STOP_BACKENDS path would
+					 * wait for it forever.  There is no consistent state to
+					 * save mid-recovery anyway, so terminate the children the
+					 * same way the crash path does and head for exit.
+					 */
+					HandleFatalError(PMQUIT_FOR_STOP, false);
+				}
+				else
+				{
+					/* Just shut down background processes silently */
+					UpdatePMState(PM_STOP_BACKENDS);
+				}
 			}
 			else if (pmState == PM_RUN ||
 					 pmState == PM_HOT_STANDBY)
-- 
That's all, folks. May the source be with you.

