Add functions to wait for backend termination

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
This commit is contained in:
Magnus Hagander 2021-04-08 11:32:14 +02:00
parent fb310f1781
commit aaf0432572
7 changed files with 174 additions and 7 deletions

View 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>

View 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

View 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)

View 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));
}
/*

View 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;

View File

@ -6190,9 +6190,14 @@
{ 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',

View 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,