Re: Parallel query hangs after a smart shutdown is issued - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Parallel query hangs after a smart shutdown is issued
Date
Msg-id 393137.1597286241@sss.pgh.pa.us
Whole thread Raw
In response to Re: Parallel query hangs after a smart shutdown is issued  (Thomas Munro <thomas.munro@gmail.com>)
Responses Re: Parallel query hangs after a smart shutdown is issued
List pgsql-hackers
Thomas Munro <thomas.munro@gmail.com> writes:
> On Thu, Aug 13, 2020 at 10:21 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Also, the state before PM_WAIT_READONLY could have been
>> PM_RECOVERY or PM_STARTUP, in which case we don't really want to think
>> it's like PM_HOT_STANDBY either; only the BgWorkerStart_PostmasterStart
>> case should be accepted.  That suggests that we need yet another pmState,
>> or else a more thoroughgoing refactoring of how the postmaster's state
>> is represented.

> Hmm.

I experimented with separating the shutdown-in-progress state into a
separate variable, letting us actually reduce not increase the number of
pmStates.  This way, PM_RUN and other states still apply until we're
ready to pull the shutdown trigger, so that we don't need to complicate
state-based decisions about launching auxiliary processes.  This patch
also unifies the signal-sending for the smart and fast shutdown paths,
which seems like a nice improvement.  I kind of like this, though I'm not
in love with the particular variable name I used here (smartShutState).

If we go this way, CAC_WAITBACKUP ought to be renamed since the PMState
it's named after no longer exists.  I left that alone pending making
final naming choices, though.

            regards, tom lane

diff --git a/doc/src/sgml/ref/pg_ctl-ref.sgml b/doc/src/sgml/ref/pg_ctl-ref.sgml
index e31275a04e..3946fa52ea 100644
--- a/doc/src/sgml/ref/pg_ctl-ref.sgml
+++ b/doc/src/sgml/ref/pg_ctl-ref.sgml
@@ -185,8 +185,8 @@ PostgreSQL documentation
    <option>stop</option> mode shuts down the server that is running in
    the specified data directory.  Three different
    shutdown methods can be selected with the <option>-m</option>
-   option.  <quote>Smart</quote> mode waits for all active
-   clients to disconnect and any online backup to finish.
+   option.  <quote>Smart</quote> mode disallows new connections, then waits
+   for all existing clients to disconnect and any online backup to finish.
    If the server is in hot standby, recovery and streaming replication
    will be terminated once all clients have disconnected.
    <quote>Fast</quote> mode (the default) does not wait for clients to disconnect and
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 38e2c16ac2..66b402e7f7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -148,8 +148,6 @@
 #define BACKEND_TYPE_BGWORKER    0x0008    /* bgworker process */
 #define BACKEND_TYPE_ALL        0x000F    /* OR of all the above */

-#define BACKEND_TYPE_WORKER        (BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER)
-
 /*
  * List of active backends (or child processes anyway; we don't actually
  * know whether a given child has become a backend or is still in the
@@ -304,8 +302,7 @@ static bool FatalError = false; /* T if recovering from backend crash */
  * and we switch to PM_RUN state.
  *
  * Normal child backends can only be launched when we are in PM_RUN or
- * PM_HOT_STANDBY state.  (We also allow launch of normal
- * child backends in PM_WAIT_BACKUP state, but only for superusers.)
+ * PM_HOT_STANDBY state.  (smartShutState can also restrict launching.)
  * In other states we handle connection requests by launching "dead_end"
  * child processes, which will simply send the client an error message and
  * quit.  (We track these in the BackendList so that we can know when they
@@ -319,10 +316,10 @@ static bool FatalError = false; /* T if recovering from backend crash */
  *
  * Notice that this state variable does not distinguish *why* we entered
  * states later than PM_RUN --- Shutdown and FatalError must be consulted
- * to find that out.  FatalError is never true in PM_RECOVERY_* or PM_RUN
- * states, nor in PM_SHUTDOWN states (because we don't enter those states
- * when trying to recover from a crash).  It can be true in PM_STARTUP state,
- * because we don't clear it until we've successfully started WAL redo.
+ * to find that out.  FatalError is never true in PM_RECOVERY, PM_HOT_STANDBY,
+ * or PM_RUN states, nor in PM_SHUTDOWN states (because we don't enter those
+ * states when trying to recover from a crash).  It can be true in PM_STARTUP
+ * state, because we don't clear it until we've successfully started WAL redo.
  */
 typedef enum
 {
@@ -331,8 +328,7 @@ typedef enum
     PM_RECOVERY,                /* in archive recovery mode */
     PM_HOT_STANDBY,                /* in hot standby mode */
     PM_RUN,                        /* normal "database is alive" state */
-    PM_WAIT_BACKUP,                /* waiting for online backup mode to end */
-    PM_WAIT_READONLY,            /* waiting for read only backends to exit */
+    PM_STOP_BACKENDS,            /* need to stop remaining backends */
     PM_WAIT_BACKENDS,            /* waiting for live backends to exit */
     PM_SHUTDOWN,                /* waiting for checkpointer to do shutdown
                                  * ckpt */
@@ -344,6 +340,21 @@ typedef enum

 static PMState pmState = PM_INIT;

+/*
+ * While performing a "smart shutdown", we restrict new connections but stay
+ * in PM_RUN or PM_HOT_STANDBY state until all the client backends are gone.
+ * smartShutState is a sub-state indicator showing the active restriction.
+ * It must always be SMART_NORMAL_USAGE when pmState isn't RUN or HOT_STANDBY.
+ */
+typedef enum
+{
+    SMART_NORMAL_USAGE,            /* normal not-shutting-down state */
+    SMART_SUPERUSER_ONLY,        /* only superusers can connect */
+    SMART_NO_CONNECTIONS        /* no new connections allowed, period */
+} SmartShutState;
+
+static SmartShutState smartShutState = SMART_NORMAL_USAGE;
+
 /* Start time of SIGKILL timeout during immediate shutdown or child crash */
 /* Zero means timeout is not running */
 static time_t AbortStartTime = 0;
@@ -1396,6 +1407,7 @@ PostmasterMain(int argc, char *argv[])
     Assert(StartupPID != 0);
     StartupStatus = STARTUP_RUNNING;
     pmState = PM_STARTUP;
+    smartShutState = SMART_NORMAL_USAGE;

     /* Some workers may be scheduled to start now */
     maybe_start_bgworkers();
@@ -2441,21 +2453,27 @@ canAcceptConnections(int backend_type)
     /*
      * Can't start backends when in startup/shutdown/inconsistent recovery
      * state.  We treat autovac workers the same as user backends for this
-     * purpose.  However, bgworkers are excluded from this test; we expect
+     * purpose.  However, bgworkers are excluded from these tests; we expect
      * bgworker_should_start_now() decided whether the DB state allows them.
      *
-     * In state PM_WAIT_BACKUP only superusers can connect (this must be
-     * allowed so that a superuser can end online backup mode); we return
-     * CAC_WAITBACKUP code to indicate that this must be checked later. Note
-     * that neither CAC_OK nor CAC_WAITBACKUP can safely be returned until we
-     * have checked for too many children.
+     * If smartShutState == SMART_SUPERUSER_ONLY then only superusers can
+     * connect (this must be allowed so that a superuser can end online backup
+     * mode); we return CAC_WAITBACKUP code to indicate that this must be
+     * checked later.  Note that neither CAC_OK nor CAC_WAITBACKUP can safely
+     * be returned until we have checked for too many children.
      */
-    if (pmState != PM_RUN &&
+    if (smartShutState != SMART_NORMAL_USAGE &&
         backend_type != BACKEND_TYPE_BGWORKER)
     {
-        if (pmState == PM_WAIT_BACKUP)
+        if (smartShutState == SMART_SUPERUSER_ONLY)
             result = CAC_WAITBACKUP;    /* allow superusers only */
-        else if (Shutdown > NoShutdown)
+        else
+            return CAC_SHUTDOWN;    /* shutdown is pending */
+    }
+    if (pmState != PM_RUN &&
+        backend_type != BACKEND_TYPE_BGWORKER)
+    {
+        if (Shutdown > NoShutdown)
             return CAC_SHUTDOWN;    /* shutdown is pending */
         else if (!FatalError &&
                  (pmState == PM_STARTUP ||
@@ -2793,34 +2811,23 @@ pmdie(SIGNAL_ARGS)
             sd_notify(0, "STOPPING=1");
 #endif

-            if (pmState == PM_RUN || pmState == PM_RECOVERY ||
-                pmState == PM_HOT_STANDBY || pmState == PM_STARTUP)
+            /*
+             * If we reached normal running, we have to wait for any online
+             * backup mode to end; otherwise go straight to waiting for client
+             * backends to exit.  (The difference is that in the former state,
+             * we'll still let in new superuser clients, so that somebody can
+             * end the online backup mode.)  If already in PM_STOP_BACKENDS or
+             * a later state, do not change it.
+             */
+            if (pmState == PM_RUN)
+                smartShutState = SMART_SUPERUSER_ONLY;
+            else if (pmState == PM_HOT_STANDBY)
+                smartShutState = SMART_NO_CONNECTIONS;
+            else if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
             {
-                /* autovac workers are told to shut down immediately */
-                /* and bgworkers too; does this need tweaking? */
-                SignalSomeChildren(SIGTERM,
-                                   BACKEND_TYPE_AUTOVAC | BACKEND_TYPE_BGWORKER);
-                /* and the autovac launcher too */
-                if (AutoVacPID != 0)
-                    signal_child(AutoVacPID, SIGTERM);
-                /* and the bgwriter too */
-                if (BgWriterPID != 0)
-                    signal_child(BgWriterPID, SIGTERM);
-                /* and the walwriter too */
-                if (WalWriterPID != 0)
-                    signal_child(WalWriterPID, SIGTERM);
-
-                /*
-                 * If we're in recovery, we can't kill the startup process
-                 * right away, because at present doing so does not release
-                 * its locks.  We might want to change this in a future
-                 * release.  For the time being, the PM_WAIT_READONLY state
-                 * indicates that we're waiting for the regular (read only)
-                 * backends to die off; once they do, we'll kill the startup
-                 * and walreceiver processes.
-                 */
-                pmState = (pmState == PM_RUN) ?
-                    PM_WAIT_BACKUP : PM_WAIT_READONLY;
+                /* There should be no clients, so proceed to stop children */
+                pmState = PM_STOP_BACKENDS;
+                smartShutState = SMART_NORMAL_USAGE;
             }

             /*
@@ -2851,48 +2858,25 @@ pmdie(SIGNAL_ARGS)
             sd_notify(0, "STOPPING=1");
 #endif

-            if (StartupPID != 0)
-                signal_child(StartupPID, SIGTERM);
-            if (BgWriterPID != 0)
-                signal_child(BgWriterPID, SIGTERM);
-            if (WalReceiverPID != 0)
-                signal_child(WalReceiverPID, SIGTERM);
             if (pmState == PM_STARTUP || pmState == PM_RECOVERY)
             {
-                SignalSomeChildren(SIGTERM, BACKEND_TYPE_BGWORKER);
-
-                /*
-                 * Only startup, bgwriter, walreceiver, possibly bgworkers,
-                 * and/or checkpointer should be active in this state; we just
-                 * signaled the first four, and we don't want to kill
-                 * checkpointer yet.
-                 */
-                pmState = PM_WAIT_BACKENDS;
+                /* Just shut down background processes silently */
+                pmState = PM_STOP_BACKENDS;
+                smartShutState = SMART_NORMAL_USAGE;
             }
             else if (pmState == PM_RUN ||
-                     pmState == PM_WAIT_BACKUP ||
-                     pmState == PM_WAIT_READONLY ||
-                     pmState == PM_WAIT_BACKENDS ||
                      pmState == PM_HOT_STANDBY)
             {
+                /* Report that we're about to zap live client sessions */
                 ereport(LOG,
                         (errmsg("aborting any active transactions")));
-                /* shut down all backends and workers */
-                SignalSomeChildren(SIGTERM,
-                                   BACKEND_TYPE_NORMAL | BACKEND_TYPE_AUTOVAC |
-                                   BACKEND_TYPE_BGWORKER);
-                /* and the autovac launcher too */
-                if (AutoVacPID != 0)
-                    signal_child(AutoVacPID, SIGTERM);
-                /* and the walwriter too */
-                if (WalWriterPID != 0)
-                    signal_child(WalWriterPID, SIGTERM);
-                pmState = PM_WAIT_BACKENDS;
+                pmState = PM_STOP_BACKENDS;
+                smartShutState = SMART_NORMAL_USAGE;
             }

             /*
-             * Now wait for backends to exit.  If there are none,
-             * PostmasterStateMachine will take the next step.
+             * PostmasterStateMachine will issue any necessary signals, or
+             * take the next step if no child processes need to be killed.
              */
             PostmasterStateMachine();
             break;
@@ -2920,6 +2904,7 @@ pmdie(SIGNAL_ARGS)

             TerminateChildren(SIGQUIT);
             pmState = PM_WAIT_BACKENDS;
+            smartShutState = SMART_NORMAL_USAGE;

             /* set stopwatch for them to die */
             AbortStartTime = time(NULL);
@@ -2978,6 +2963,7 @@ reaper(SIGNAL_ARGS)
             {
                 StartupStatus = STARTUP_NOT_RUNNING;
                 pmState = PM_WAIT_BACKENDS;
+                smartShutState = SMART_NORMAL_USAGE;
                 /* PostmasterStateMachine logic does the rest */
                 continue;
             }
@@ -2987,9 +2973,10 @@ reaper(SIGNAL_ARGS)
                 ereport(LOG,
                         (errmsg("shutdown at recovery target")));
                 StartupStatus = STARTUP_NOT_RUNNING;
-                Shutdown = SmartShutdown;
+                Shutdown = Max(Shutdown, SmartShutdown);
                 TerminateChildren(SIGTERM);
                 pmState = PM_WAIT_BACKENDS;
+                smartShutState = SMART_NORMAL_USAGE;
                 /* PostmasterStateMachine logic does the rest */
                 continue;
             }
@@ -3051,6 +3038,7 @@ reaper(SIGNAL_ARGS)
             AbortStartTime = 0;
             ReachedNormalRunning = true;
             pmState = PM_RUN;
+            smartShutState = SMART_NORMAL_USAGE;

             /*
              * Crank up the background tasks, if we didn't do that already
@@ -3712,10 +3700,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
     if (pmState == PM_RECOVERY ||
         pmState == PM_HOT_STANDBY ||
         pmState == PM_RUN ||
-        pmState == PM_WAIT_BACKUP ||
-        pmState == PM_WAIT_READONLY ||
+        pmState == PM_STOP_BACKENDS ||
         pmState == PM_SHUTDOWN)
+    {
         pmState = PM_WAIT_BACKENDS;
+        smartShutState = SMART_NORMAL_USAGE;
+    }

     /*
      * .. and if this doesn't happen quickly enough, now the clock is ticking
@@ -3796,35 +3786,59 @@ LogChildExit(int lev, const char *procname, int pid, int exitstatus)
 static void
 PostmasterStateMachine(void)
 {
-    if (pmState == PM_WAIT_BACKUP)
+    if (smartShutState == SMART_SUPERUSER_ONLY)
     {
         /*
-         * PM_WAIT_BACKUP state ends when online backup mode is not active.
+         * SMART_SUPERUSER_ONLY state ends as soon as online backup mode is
+         * not active.
          */
         if (!BackupInProgress())
-            pmState = PM_WAIT_BACKENDS;
+            smartShutState = SMART_NO_CONNECTIONS;
     }

-    if (pmState == PM_WAIT_READONLY)
+    if (smartShutState == SMART_NO_CONNECTIONS)
     {
         /*
-         * PM_WAIT_READONLY state ends when we have no regular backends that
-         * have been started during recovery.  We kill the startup and
-         * walreceiver processes and transition to PM_WAIT_BACKENDS.  Ideally,
-         * we might like to kill these processes first and then wait for
-         * backends to die off, but that doesn't work at present because
-         * killing the startup process doesn't release its locks.
+         * SMART_NO_CONNECTIONS state ends when we have no normal client
+         * backends running.  Then we're ready to shut down other children.
          */
         if (CountChildren(BACKEND_TYPE_NORMAL) == 0)
         {
-            if (StartupPID != 0)
-                signal_child(StartupPID, SIGTERM);
-            if (WalReceiverPID != 0)
-                signal_child(WalReceiverPID, SIGTERM);
-            pmState = PM_WAIT_BACKENDS;
+            pmState = PM_STOP_BACKENDS;
+            smartShutState = SMART_NORMAL_USAGE;
         }
     }

+    /*
+     * If we're ready to do so, signal child processes to shut down.  (This
+     * isn't a persistent state, but treating it as a distinct pmState allows
+     * us to share this code across multiple shutdown paths.)
+     */
+    if (pmState == PM_STOP_BACKENDS)
+    {
+        /* Signal all backend children except walsenders */
+        SignalSomeChildren(SIGTERM,
+                           BACKEND_TYPE_ALL - BACKEND_TYPE_WALSND);
+        /* and the autovac launcher too */
+        if (AutoVacPID != 0)
+            signal_child(AutoVacPID, SIGTERM);
+        /* and the bgwriter too */
+        if (BgWriterPID != 0)
+            signal_child(BgWriterPID, SIGTERM);
+        /* and the walwriter too */
+        if (WalWriterPID != 0)
+            signal_child(WalWriterPID, SIGTERM);
+        /* If we're in recovery, also stop startup and walreceiver procs */
+        if (StartupPID != 0)
+            signal_child(StartupPID, SIGTERM);
+        if (WalReceiverPID != 0)
+            signal_child(WalReceiverPID, SIGTERM);
+        /* checkpointer, archiver, stats, and syslogger may continue for now */
+
+        /* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
+        pmState = PM_WAIT_BACKENDS;
+    }
+
     /*
      * If we are in a state-machine state that implies waiting for backends to
      * exit, see if they're all gone, and change state if so.
@@ -3843,7 +3857,7 @@ PostmasterStateMachine(void)
          * later after writing the checkpoint record, like the archiver
          * process.
          */
-        if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_WORKER) == 0 &&
+        if (CountChildren(BACKEND_TYPE_ALL - BACKEND_TYPE_WALSND) == 0 &&
             StartupPID == 0 &&
             WalReceiverPID == 0 &&
             BgWriterPID == 0 &&
@@ -5287,7 +5301,7 @@ sigusr1_handler(SIGNAL_ARGS)
     }

     if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER) &&
-        Shutdown == NoShutdown)
+        Shutdown <= SmartShutdown && pmState < PM_STOP_BACKENDS)
     {
         /*
          * Start one iteration of the autovacuum daemon, even if autovacuuming
@@ -5302,7 +5316,7 @@ sigusr1_handler(SIGNAL_ARGS)
     }

     if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER) &&
-        Shutdown == NoShutdown)
+        Shutdown <= SmartShutdown && pmState < PM_STOP_BACKENDS)
     {
         /* The autovacuum launcher wants us to start a worker process. */
         StartAutovacuumWorker();
@@ -5333,7 +5347,7 @@ sigusr1_handler(SIGNAL_ARGS)

     if (StartupPID != 0 &&
         (pmState == PM_STARTUP || pmState == PM_RECOVERY ||
-         pmState == PM_HOT_STANDBY || pmState == PM_WAIT_READONLY) &&
+         pmState == PM_HOT_STANDBY) &&
         CheckPromoteSignal())
     {
         /*
@@ -5651,8 +5665,8 @@ MaybeStartWalReceiver(void)
 {
     if (WalReceiverPID == 0 &&
         (pmState == PM_STARTUP || pmState == PM_RECOVERY ||
-         pmState == PM_HOT_STANDBY || pmState == PM_WAIT_READONLY) &&
-        Shutdown == NoShutdown)
+         pmState == PM_HOT_STANDBY) &&
+        Shutdown <= SmartShutdown)
     {
         WalReceiverPID = StartWalReceiver();
         if (WalReceiverPID != 0)
@@ -5905,8 +5919,7 @@ bgworker_should_start_now(BgWorkerStartTime start_time)
         case PM_SHUTDOWN_2:
         case PM_SHUTDOWN:
         case PM_WAIT_BACKENDS:
-        case PM_WAIT_READONLY:
-        case PM_WAIT_BACKUP:
+        case PM_STOP_BACKENDS:
             break;

         case PM_RUN:

pgsql-hackers by date:

Previous
From: Hao Wu
Date:
Subject: Missing iso-8859-1 status in PGresult
Next
From: Tom Lane
Date:
Subject: Re: security_context_t marked as deprecated in libselinux 3.1