diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index c6a45d9e55c..0606b6a9aa4 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -24977,7 +24977,7 @@ SELECT collation for ('foo' COLLATE "de_DE"); pg_terminate_backend - pg_terminate_backend ( pid integer ) + pg_terminate_backend ( pid integer, timeout bigint DEFAULT 0 ) boolean @@ -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 pg_signal_backend, however only superusers can terminate superuser backends. + + + If timeout is not specified or zero, this + function returns true whether the process actually + terminates or not, indicating only that the sending of the signal was + successful. If the timeout 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 true. On timeout a warning is emitted and + false is returned. + + + + + + + pg_wait_for_backend_termination + + pg_wait_for_backend_termination ( pid integer, timeout bigint DEFAULT 5000 ) + boolean + + + Waits for the backend process with the specified Process ID to + terminate. If the process terminates before + the timeout (in milliseconds) + expires, true is returned. On timeout, a warning + is emitted and false is returned. diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 52958b4fd91..da16c461f08 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -1585,6 +1585,10 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser Waiting for subplan nodes of an Append plan node to be ready. + + BackendTermination + Waiting for the termination of another backend. + BackupWaitWalArchive Waiting for WAL files required for a backup to be successfully diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index a47e102f366..ff65b3edfa7 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -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) diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c index 8b55ff6e76b..0337b00226a 100644 --- a/src/backend/storage/ipc/signalfuncs.c +++ b/src/backend/storage/ipc/signalfuncs.c @@ -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)); } /* diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index accc1eb5776..89b5b8b7b9d 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -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; diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6feaaa44597..599dd10d10e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -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', diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 44448b48ec0..47accc5ffe2 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -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,