Add functions to wait for backend termination
authorMagnus Hagander <magnus@hagander.net>
Thu, 8 Apr 2021 09:32:14 +0000 (11:32 +0200)
committerMagnus Hagander <magnus@hagander.net>
Thu, 8 Apr 2021 09:40:54 +0000 (11:40 +0200)
This adds a function, pg_wait_for_backend_termination(), and a new
timeout argument to pg_terminate_backend(), which will wait for the
backend to actually terminate (with or without signaling it to do so
depending on which function is called). The default behaviour of
pg_terminate_backend() remains being timeout=0 which does not waiting.
For pg_wait_for_backend_termination() the default wait is 5 seconds.

Author: Bharath Rupireddy
Reviewed-By: Fujii Masao, David Johnston, Muhammad Usama,
             Hou Zhijie, Magnus Hagander
Discussion: https://postgr.es/m/CALj2ACUBpunmyhYZw-kXCYs5NM+h6oG_7Df_Tn4mLmmUQifkqA@mail.gmail.com

doc/src/sgml/func.sgml
doc/src/sgml/monitoring.sgml
src/backend/catalog/system_views.sql
src/backend/storage/ipc/signalfuncs.c
src/backend/utils/activity/wait_event.c
src/include/catalog/pg_proc.dat
src/include/utils/wait_event.h

index c6a45d9e55c8ed42f3f4cf046b36a75e765045c1..0606b6a9aa41ee9d64edee060d353297bebb86f2 100644 (file)
@@ -24977,7 +24977,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
         <indexterm>
          <primary>pg_terminate_backend</primary>
         </indexterm>
-        <function>pg_terminate_backend</function> ( <parameter>pid</parameter> <type>integer</type> )
+        <function>pg_terminate_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>timeout</parameter> <type>bigint</type> <literal>DEFAULT</literal> <literal>0</literal> )
         <returnvalue>boolean</returnvalue>
        </para>
        <para>
@@ -24986,6 +24986,34 @@ SELECT collation for ('foo' COLLATE "de_DE");
         is a member of the role whose backend is being terminated or the
         calling role has been granted <literal>pg_signal_backend</literal>,
         however only superusers can terminate superuser backends.
+       </para>
+       <para>
+        If <parameter>timeout</parameter> is not specified or zero, this
+        function returns <literal>true</literal> whether the process actually
+        terminates or not, indicating only that the sending of the signal was
+        successful.  If the <parameter>timeout</parameter> is specified (in
+        milliseconds) and greater than zero, the function waits until the
+        process is actually terminated or until the given time has passed. If
+        the process is terminated, the function
+        returns <literal>true</literal>.  On timeout a warning is emitted and
+        <literal>false</literal> is returned.
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_wait_for_backend_termination</primary>
+        </indexterm>
+        <function>pg_wait_for_backend_termination</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>timeout</parameter> <type>bigint</type> <literal>DEFAULT</literal> <literal>5000</literal> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Waits for the backend process with the specified Process ID to
+        terminate.  If the process terminates before
+        the <parameter>timeout</parameter> (in milliseconds)
+        expires, <literal>true</literal> is returned.  On timeout, a warning
+        is emitted and <literal>false</literal> is returned.
        </para></entry>
       </row>
      </tbody>
index 52958b4fd91d52962dabc5f556c27be10c480c34..da16c461f08d5f2077efc2f383d2da1c95722503 100644 (file)
@@ -1585,6 +1585,10 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       <entry>Waiting for subplan nodes of an <literal>Append</literal> plan
        node to be ready.</entry>
      </row>
+    <row>
+      <entry><literal>BackendTermination</literal></entry>
+      <entry>Waiting for the termination of another backend.</entry>
+     </row>
      <row>
       <entry><literal>BackupWaitWalArchive</literal></entry>
       <entry>Waiting for WAL files required for a backup to be successfully
index a47e102f36641e4f6b827263cf846b3b841b5e85..ff65b3edfa7d85af324a9bd3b94d6f0c30c692c1 100644 (file)
@@ -1347,6 +1347,16 @@ CREATE OR REPLACE FUNCTION
   RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_promote'
   PARALLEL SAFE;
 
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0)
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend'
+  PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_wait_for_backend_termination(pid integer, timeout int8 DEFAULT 5000)
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_wait_for_backend_termination'
+  PARALLEL SAFE;
+
 -- legacy definition for compatibility with 9.3
 CREATE OR REPLACE FUNCTION
   json_populate_record(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
index 8b55ff6e76b10401ee4909234acd212eabe876db..0337b00226ab6b35835b0de558cc84eeaaf3d4e5 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "catalog/pg_authid.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "postmaster/syslogger.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -126,15 +127,90 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 }
 
 /*
- * Signal to terminate a backend process.  This is allowed if you are a member
- * of the role whose process is being terminated.
+ * Wait until there is no backend process with the given PID and return true.
+ * On timeout, a warning is emitted and false is returned.
+ */
+static bool
+pg_wait_until_termination(int pid, int64 timeout)
+{
+       /*
+        * Wait in steps of waittime milliseconds until this function exits or
+        * timeout.
+        */
+       int64   waittime = 100;
+       /*
+        * Initially remaining time is the entire timeout specified by the user.
+        */
+       int64   remainingtime = timeout;
+
+       /*
+        * Check existence of the backend. If the backend still exists, then wait
+        * for waittime milliseconds, again check for the existence. Repeat this
+        * until timeout or an error occurs or a pending interrupt such as query
+        * cancel gets processed.
+        */
+       do
+       {
+               if (remainingtime < waittime)
+                       waittime = remainingtime;
+
+               if (kill(pid, 0) == -1)
+               {
+                       if (errno == ESRCH)
+                               return true;
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INTERNAL_ERROR),
+                                                errmsg("could not check the existence of the backend with PID %d: %m",
+                                                                pid)));
+               }
+
+               /* Process interrupts, if any, before waiting */
+               CHECK_FOR_INTERRUPTS();
+
+               (void) WaitLatch(MyLatch,
+                                                WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+                                                waittime,
+                                                WAIT_EVENT_BACKEND_TERMINATION);
+
+               ResetLatch(MyLatch);
+
+               remainingtime -= waittime;
+       } while (remainingtime > 0);
+
+       ereport(WARNING,
+                       (errmsg("backend with PID %d did not terminate within %lld milliseconds",
+                                       pid, (long long int) timeout)));
+
+       return false;
+}
+
+/*
+ * Signal to terminate a backend process. This is allowed if you are a member
+ * of the role whose process is being terminated. If timeout input argument is
+ * 0 (which is default), then this function just signals the backend and
+ * doesn't wait. Otherwise it waits until given the timeout milliseconds or no
+ * process has the given PID and returns true. On timeout, a warning is emitted
+ * and false is returned.
  *
  * Note that only superusers can signal superuser-owned processes.
  */
 Datum
 pg_terminate_backend(PG_FUNCTION_ARGS)
 {
-       int                     r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+       int      pid;
+       int      r;
+       int timeout;
+
+       pid = PG_GETARG_INT32(0);
+       timeout = PG_GETARG_INT64(1);
+
+       if (timeout < 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                       errmsg("\"timeout\" must not be negative")));
+
+       r = pg_signal_backend(pid, SIGTERM);
 
        if (r == SIGNAL_BACKEND_NOSUPERUSER)
                ereport(ERROR,
@@ -146,7 +222,47 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                                 errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend")));
 
-       PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+       /* Wait only on success and if actually requested */
+       if (r == SIGNAL_BACKEND_SUCCESS && timeout > 0)
+               PG_RETURN_BOOL(pg_wait_until_termination(pid, timeout));
+       else
+               PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * Wait for a backend process with the given PID to exit or until the given
+ * timeout milliseconds occurs. Returns true if the backend has exited. On
+ * timeout a warning is emitted and false is returned.
+ *
+ * We allow any user to call this function, consistent with any user being
+ * able to view the pid of the process in pg_stat_activity etc.
+ */
+Datum
+pg_wait_for_backend_termination(PG_FUNCTION_ARGS)
+{
+       int      pid;
+       int64   timeout;
+       PGPROC  *proc = NULL;
+
+       pid = PG_GETARG_INT32(0);
+       timeout = PG_GETARG_INT64(1);
+
+       if (timeout <= 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                               errmsg("\"timeout\" must not be negative or zero")));
+
+       proc = BackendPidGetProc(pid);
+
+       if (proc == NULL)
+       {
+               ereport(WARNING,
+                               (errmsg("PID %d is not a PostgreSQL server process", pid)));
+
+               PG_RETURN_BOOL(false);
+       }
+
+       PG_RETURN_BOOL(pg_wait_until_termination(pid, timeout));
 }
 
 /*
index accc1eb57764caecf9148c2ba810359828ab8a08..89b5b8b7b9d07f693d3ad52ae6d222c5c679be50 100644 (file)
@@ -313,6 +313,9 @@ pgstat_get_wait_ipc(WaitEventIPC w)
                case WAIT_EVENT_APPEND_READY:
                        event_name = "AppendReady";
                        break;
+               case WAIT_EVENT_BACKEND_TERMINATION:
+                       event_name = "BackendTermination";
+                       break;
                case WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE:
                        event_name = "BackupWaitWalArchive";
                        break;
index 6feaaa44597e5994b656f6fe72e4c57796b82a60..599dd10d10ecbb5aff110f8e2138e682095b1801 100644 (file)
 { oid => '2171', descr => 'cancel a server process\' current query',
   proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'int4', prosrc => 'pg_cancel_backend' },
-{ oid => '2096', descr => 'terminate a server process',
+{ oid => '2096', descr => 'terminate a backend process and if timeout is specified, wait for its exit or until timeout occurs',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4', prosrc => 'pg_terminate_backend' },
+  proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
+  prosrc => 'pg_terminate_backend' },
+{ oid => '2137', descr => 'wait for a backend process exit or timeout occurs',
+  proname => 'pg_wait_for_backend_termination', provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
+  prosrc => 'pg_wait_for_backend_termination' },
 { oid => '2172', descr => 'prepare for taking an online backup',
   proname => 'pg_start_backup', provolatile => 'v', proparallel => 'r',
   prorettype => 'pg_lsn', proargtypes => 'text bool bool',
index 44448b48ec0c55d166b1fc793156945aebd0f3ce..47accc5ffe22fdc13783e8a4335b26d41086c129 100644 (file)
@@ -80,6 +80,7 @@ typedef enum
 typedef enum
 {
        WAIT_EVENT_APPEND_READY = PG_WAIT_IPC,
+       WAIT_EVENT_BACKEND_TERMINATION,
        WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE,
        WAIT_EVENT_BGWORKER_SHUTDOWN,
        WAIT_EVENT_BGWORKER_STARTUP,