From 7085f0d5948ce7d51b7fb9533f5383fdaeb715a6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Fri, 5 Oct 2018 09:59:25 +0200
Subject: [PATCH] Support optional message in backend cancel/terminate

This adds the ability for the caller of pg_terminate_backend(), or
pg_cancel_backend(), to include an optional message to the process
which is being signalled. The message will be appended to the error
message returned to the killed or cancelled process. The new syntax
is overloading the existing as:

    SELECT pg_terminate_backend(<pid> [, msg]);
    SELECT pg_cancel_backend(<pid> [, msg]);

The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend. This is however not
exposed in SQL in the above mentioned functions.

Also adds a new test suite for administrative functions.
---
 doc/src/sgml/func.sgml                    |  13 +-
 src/backend/catalog/system_views.sql      |   8 +
 src/backend/storage/ipc/ipci.c            |   3 +
 src/backend/storage/ipc/signalfuncs.c     | 320 +++++++++++++++++++++++++++++-
 src/backend/tcop/postgres.c               |  61 +++++-
 src/backend/utils/init/postinit.c         |   2 +
 src/include/catalog/pg_proc.dat           |   4 +-
 src/include/storage/signalfuncs.h         |  27 +++
 src/test/regress/expected/admin_funcs.out |  28 +++
 src/test/regress/parallel_schedule        |   2 +-
 src/test/regress/sql/admin_funcs.sql      |  11 +
 11 files changed, 461 insertions(+), 18 deletions(-)
 create mode 100644 src/include/storage/signalfuncs.h
 create mode 100644 src/test/regress/expected/admin_funcs.out
 create mode 100644 src/test/regress/sql/admin_funcs.sql

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f984d069e1..5c1b302c78 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18756,7 +18756,7 @@ SELECT set_config('log_statement_stats', 'off', false);
      <tbody>
       <row>
        <entry>
-        <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</type>)</function></literal>
+        <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</type> [, <parameter>message</parameter> <type>text</type>])</function></literal>
         </entry>
        <entry><type>boolean</type></entry>
        <entry>Cancel a backend's current query.  This is also allowed if the
@@ -18781,7 +18781,7 @@ SELECT set_config('log_statement_stats', 'off', false);
       </row>
       <row>
        <entry>
-        <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</type>)</function></literal>
+        <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</type> [, <parameter>message</parameter> <type>text</type>])</function></literal>
         </entry>
        <entry><type>boolean</type></entry>
        <entry>Terminate a backend.  This is also allowed if the calling role
@@ -18812,6 +18812,15 @@ SELECT set_config('log_statement_stats', 'off', false);
     The role of an active backend can be found from the
     <structfield>usename</structfield> column of the
     <structname>pg_stat_activity</structname> view.
+    If the optional <literal>message</literal> parameter is set, it will
+    replace the default error message, which in turn will be the error
+    detail. <literal>message</literal> is limited to 128 ASCII characters, a
+    longer text will be truncated. An example where we cancel our own backend:
+<programlisting>
+postgres=# SELECT pg_cancel_backend(pg_backend_pid(), 'cancellation message text');
+ERROR:  cancellation message text
+DETAIL:  canceling statement due to user request by process 12345
+</programlisting>
    </para>
 
    <para>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 020f28cbf6..145348e2ef 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1027,6 +1027,14 @@ CREATE OR REPLACE FUNCTION pg_stop_backup (
   RETURNS SETOF record STRICT VOLATILE LANGUAGE internal as 'pg_stop_backup_v2'
   PARALLEL RESTRICTED;
 
+CREATE OR REPLACE FUNCTION
+  pg_cancel_backend(pid int4, message text DEFAULT NULL)
+  RETURNS bool VOLATILE LANGUAGE internal AS 'pg_cancel_backend' PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid int4, message text DEFAULT NULL)
+  RETURNS bool VOLATILE LANGUAGE internal AS 'pg_terminate_backend' 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/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..0486e08493 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -33,6 +33,7 @@
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "replication/origin.h"
+#include "storage/signalfuncs.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm.h"
 #include "storage/ipc.h"
@@ -150,6 +151,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
 		size = add_size(size, BackendRandomShmemSize());
+		size = add_size(size, BackendSignalFeedbackShmemSize());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -270,6 +272,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	SyncScanShmemInit();
 	AsyncShmemInit();
 	BackendRandomShmemInit();
+	BackendSignalFeedbackShmemInit();
 
 #ifdef EXEC_BACKEND
 
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 878665b045..2dc1877ce3 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,21 +17,51 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "postmaster/syslogger.h"
+#include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/signalfuncs.h"
+#include "storage/spin.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 
 
+/*
+ * Structure for registering a feedback payload to be sent to a cancelled, or
+ * terminated backend. Each backend is registered per pid in the array which is
+ * indexed by Backend ID. Reading and writing the message is protected by a
+ * per-slot spinlock.
+ */
+typedef struct
+{
+	pid_t	dest_pid;		/* The pid of the process being signalled */
+	pid_t	src_pid;		/* The pid of the processing signalling */
+	slock_t	mutex;			/* Per-slot protection */
+	char	message[MAX_CANCEL_MSG]; /* Message to send to signalled backend */
+	int		orig_length;	/* Length of the message as passed by the user,
+							 * if this length exceeds MAX_CANCEL_MSG it will
+							 * be truncated but we store the original length
+							 * in order to be able to convey truncation */
+	int		sqlerrcode;		/* errcode to use when signalling backend */
+} BackendSignalFeedbackShmemStruct;
+
+static BackendSignalFeedbackShmemStruct	*BackendSignalFeedbackSlots = NULL;
+static volatile BackendSignalFeedbackShmemStruct *MyCancelSlot = NULL;
+static void CleanupBackendSignalFeedback(int status, Datum argument);
+static int backend_feedback(pid_t backend_pid, char *message, int sqlerrcode);
+
 /*
  * Send a signal to another backend.
  *
- * The signal is delivered if the user is either a superuser or the same
- * role as the backend being signaled. For "dangerous" signals, an explicit
- * check for superuser needs to be done prior to calling this function.
+ * The signal is delivered if the user is either a superuser or the same role
+ * as the backend being signaled. For "dangerous" signals, an explicit check
+ * for superuser needs to be done prior to calling this function. If msg is
+ * set, the contents will be passed as a message to the backend in the error
+ * message.
  *
  * Returns 0 on success, 1 on general failure, 2 on normal permission error
  * and 3 if the caller needs to be a superuser.
@@ -45,7 +75,7 @@
 #define SIGNAL_BACKEND_NOPERMISSION 2
 #define SIGNAL_BACKEND_NOSUPERUSER 3
 static int
-pg_signal_backend(int pid, int sig)
+pg_signal_backend(int pid, int sig, char *msg)
 {
 	PGPROC	   *proc = BackendPidGetProc(pid);
 
@@ -77,6 +107,30 @@ pg_signal_backend(int pid, int sig)
 		!has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
 		return SIGNAL_BACKEND_NOPERMISSION;
 
+	/* If the user supplied a message to the signalled backend */
+	if (msg != NULL)
+	{
+		char *tmp = msg;
+
+		/*
+		 * The message to pass to the signalled backend is currently restricted
+		 * to ASCII only, since the sending backend might use an encoding which
+		 * is incompatible with the receiving with regards to conversion.
+		 */
+		while (*tmp != '\0')
+		{
+			if (!isascii(*tmp))
+				ereport(ERROR,
+						(errmsg("message is restricted to ASCII only")));
+			tmp++;
+		}
+
+		if (sig == SIGINT)
+			SetBackendCancelMessage(pid, msg);
+		else
+			SetBackendTerminationMessage(pid, msg);
+	}
+
 	/*
 	 * Can the process we just validated above end, followed by the pid being
 	 * recycled for a new process, before reaching here?  Then we'd be trying
@@ -110,7 +164,19 @@ pg_signal_backend(int pid, int sig)
 Datum
 pg_cancel_backend(PG_FUNCTION_ARGS)
 {
-	int			r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+	int			r;
+	pid_t		pid;
+	char 	   *msg = NULL;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	pid = PG_GETARG_INT32(0);
+
+	if (PG_NARGS() == 2 && !PG_ARGISNULL(1))
+		msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	r = pg_signal_backend(pid, SIGINT, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -134,7 +200,19 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 Datum
 pg_terminate_backend(PG_FUNCTION_ARGS)
 {
-	int			r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+	int			r;
+	pid_t		pid;
+	char 	   *msg = NULL;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	pid = PG_GETARG_INT32(0);
+
+	if (PG_NARGS() == 2 && !PG_ARGISNULL(1))
+		msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	r = pg_signal_backend(pid, SIGTERM, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -146,7 +224,7 @@ 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);
+	return (r == SIGNAL_BACKEND_SUCCESS);
 }
 
 /*
@@ -213,3 +291,231 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
 	SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
 	PG_RETURN_BOOL(true);
 }
+
+/*
+ * The following routines handle registering an optional message when
+ * cancelling, or terminating, a backend as well changing the sqlerrcode used.
+ * The combined payload of message/errcode is referred to as feedback.  The
+ * message will be stored in shared memory and is limited to MAX_CANCEL_MSG
+ * characters including the NULL terminator.
+ *
+ * Access to the feedback slots is protected by spinlocks.
+ */
+
+/*
+ * Return the required size for the cancelation feedback Shmem area.
+ */
+Size
+BackendSignalFeedbackShmemSize(void)
+{
+	return MaxBackends * sizeof(BackendSignalFeedbackShmemStruct);
+}
+
+/*
+ * Create and initialize the Shmem structure for holding the feedback, the
+ * bookkeeping for them and the spinlocks associated.
+ */
+void
+BackendSignalFeedbackShmemInit(void)
+{
+	Size	size = BackendSignalFeedbackShmemSize();
+	bool	found;
+	int		i;
+
+	BackendSignalFeedbackSlots = (BackendSignalFeedbackShmemStruct *)
+		ShmemInitStruct("BackendSignalFeedbackSlots", size, &found);
+
+	if (!found)
+	{
+		MemSet(BackendSignalFeedbackSlots, 0, size);
+
+		for (i = 0; i < MaxBackends; i++)
+			SpinLockInit(&(BackendSignalFeedbackSlots[i].mutex));
+	}
+}
+
+/*
+ * Set up the slot for the current backend_id
+ */
+void
+BackendSignalFeedbackInit(int backend_id)
+{
+	volatile BackendSignalFeedbackShmemStruct *slot;
+
+	slot = &BackendSignalFeedbackSlots[backend_id - 1];
+
+	slot->message[0] = '\0';
+	slot->orig_length = 0;
+	slot->sqlerrcode = 0;
+	slot->dest_pid = MyProcPid;
+
+	MyCancelSlot = slot;
+
+	on_shmem_exit(CleanupBackendSignalFeedback, Int32GetDatum(backend_id));
+}
+
+/*
+ * Ensure that the slot is purged and emptied at exit. Any message gets
+ * overwritten with null chars to avoid risking exposing a message intended for
+ * another backend to a new backend.
+ */
+static void
+CleanupBackendSignalFeedback(int status, Datum argument)
+{
+	int backend_id = DatumGetInt32(argument);
+	volatile BackendSignalFeedbackShmemStruct *slot;
+
+	slot = &BackendSignalFeedbackSlots[backend_id - 1];
+
+	Assert(slot == MyCancelSlot);
+
+	MyCancelSlot = NULL;
+
+	if (slot->orig_length > 0)
+		MemSet(slot->message, '\0', sizeof(slot->message));
+
+	slot->orig_length = 0;
+	slot->sqlerrcode = 0;
+	slot->dest_pid = 0;
+	slot->src_pid = 0;
+}
+
+/*
+ * Set a message for the cancellation of the backend with the specified pid,
+ * using the default sqlerrcode.
+ */
+int
+SetBackendCancelMessage(pid_t backend_pid, char *message)
+{
+	return backend_feedback(backend_pid, message, ERRCODE_QUERY_CANCELED);
+}
+
+/*
+ * Set a message for the termination of the backend with the specified pid,
+ * using the default sqlerrcode.
+ */
+int
+SetBackendTerminationMessage(pid_t backend_pid, char *message)
+{
+	return backend_feedback(backend_pid, message, ERRCODE_ADMIN_SHUTDOWN);
+}
+
+/*
+ * Set both a message and a sqlerrcode for use when signalling the backend
+ * with the specified pid.
+ */
+int
+SetBackendSignalFeedback(pid_t backend_pid, char *message, int sqlerrcode)
+{
+	return backend_feedback(backend_pid, message, sqlerrcode);
+}
+
+/*
+ * Sets a cancellation message for the backend with the specified pid, and
+ * returns zero on success. If the backend isn't found, or no message is
+ * passed, 1 is returned.  If two backends collide in setting a message, the
+ * existing message will be overwritten by the last one in. The message will
+ * be truncated to fit within MAX_CANCEL_MSG bytes.
+ */
+static int
+backend_feedback(pid_t backend_pid, char *message, int sqlerrcode)
+{
+	int		i;
+	int		len;
+
+	if (!message)
+		return 1;
+
+	len = pg_mbcliplen(message, strlen(message), MAX_CANCEL_MSG - 1);
+
+	for (i = 0; i < MaxBackends; i++)
+	{
+		BackendSignalFeedbackShmemStruct *slot = &BackendSignalFeedbackSlots[i];
+
+		if (slot->dest_pid != 0 && slot->dest_pid == backend_pid)
+		{
+			SpinLockAcquire(&slot->mutex);
+			if (slot->dest_pid != backend_pid)
+			{
+				SpinLockRelease(&slot->mutex);
+				return 1;
+			}
+
+			/* Avoid risking to leak any part of a previously set message */
+			MemSet(slot->message, '\0', sizeof(slot->message));
+
+			memcpy(slot->message, message, len);
+			slot->orig_length = pg_mbstrlen(message);
+			slot->sqlerrcode = sqlerrcode;
+			slot->src_pid = MyProcPid;
+			SpinLockRelease(&slot->mutex);
+
+			if (len != strlen(message))
+				ereport(NOTICE,
+						(errmsg("message is too long and has been truncated")));
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/*
+ * HasBackendSignalFeedback
+ *		Test if there is a backend signalling feedback to consume
+ *
+ * Test whether there is feedback registered for the current backend that can
+ * be consumed and presented to the user. It isn't strictly required to call
+ * this function prior to consuming a potential message, but since consuming it
+ * will clear it there can be cases where one would like to peek first.
+ */
+bool
+HasBackendSignalFeedback(void)
+{
+	volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot;
+	bool 	has_message = false;
+
+	if (slot != NULL)
+	{
+		SpinLockAcquire(&slot->mutex);
+		has_message = ((slot->orig_length > 0) && (slot->sqlerrcode != 0));
+		SpinLockRelease(&slot->mutex);
+	}
+
+	return has_message;
+}
+
+/*
+ * ConsumeBackendSignalFeedback
+ *		Read anc clear backend signalling feedback
+ *
+ * Return the configured signal feedback in buffer, which is buf_len bytes in
+ * size.  The original length of the message is returned, or zero in case no
+ * message was found. If the returned length exceeds that of Min(buf_len,
+ * MAX_CANCEL_MSG), then truncation has been performed. The feedback (message
+ * and errcode) is cleared on consumption. There is no point in passing a
+ * buffer larger than MAX_CANCEL_NSG as that is the upper bound on what will be
+ * stored in the slot.
+ */
+int
+ConsumeBackendSignalFeedback(char *buffer, size_t buf_len, int *sqlerrcode,
+							 pid_t *pid)
+{
+	volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot;
+	int		msg_length = 0;
+
+	if (slot != NULL && slot->orig_length > 0)
+	{
+		SpinLockAcquire(&slot->mutex);
+		strlcpy(buffer, (const char *) slot->message, buf_len);
+		msg_length = slot->orig_length;
+		*sqlerrcode = slot->sqlerrcode;
+		*pid = slot->src_pid;
+		slot->orig_length = 0;
+		slot->message[0] = '\0';
+		slot->sqlerrcode = 0;
+		SpinLockRelease(&slot->mutex);
+	}
+
+	return msg_length;
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e4c6e3d406..1db8ea1bda 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -62,6 +62,7 @@
 #include "replication/slot.h"
 #include "replication/walsender.h"
 #include "rewrite/rewriteHandler.h"
+#include "storage/signalfuncs.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -2990,9 +2991,33 @@ ProcessInterrupts(void)
 					 errdetail_recovery_conflict()));
 		}
 		else
-			ereport(FATAL,
-					(errcode(ERRCODE_ADMIN_SHUTDOWN),
-					 errmsg("terminating connection due to administrator command")));
+		{
+			if (HasBackendSignalFeedback())
+			{
+				char	buffer[MAX_CANCEL_MSG];
+				int		len;
+				int		sqlerrcode = 0;
+				pid_t	pid = 0;
+
+				len = ConsumeBackendSignalFeedback(buffer, MAX_CANCEL_MSG,
+												   &sqlerrcode, &pid);
+				if (len == 0)
+				{
+					sqlerrcode = ERRCODE_ADMIN_SHUTDOWN;
+					buffer[0] = '\0';
+				}
+				ereport(FATAL,
+						(errcode(sqlerrcode),
+						 errmsg("%s%s",
+								buffer, (len > sizeof(buffer) ? "..." : "")),
+						 errdetail("terminating connection due to administrator command by process %d",
+								   pid)));
+			}
+			else
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						 errmsg("terminating connection due to administrator command")));
+		}
 	}
 	if (ClientConnectionLost)
 	{
@@ -3103,9 +3128,33 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+
+			if (HasBackendSignalFeedback())
+			{
+				char	buffer[MAX_CANCEL_MSG];
+				int		len;
+				int		sqlerrcode = 0;
+				pid_t	pid = 0;
+
+				len = ConsumeBackendSignalFeedback(buffer, MAX_CANCEL_MSG,
+												   &sqlerrcode, &pid);
+				if (len == 0)
+				{
+					sqlerrcode = ERRCODE_QUERY_CANCELED;
+					buffer[0] = '\0';
+				}
+
+				ereport(ERROR,
+						(errcode(sqlerrcode),
+						 errmsg("%s%s",
+								buffer, (len > sizeof(buffer) ? "..." : "")),
+						 errdetail("canceling statement due to user request by process %d",
+								   pid)));
+			}
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request")));
 		}
 	}
 
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4f1d2a0d28..1c0a2eb1fe 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -40,6 +40,7 @@
 #include "postmaster/autovacuum.h"
 #include "postmaster/postmaster.h"
 #include "replication/walsender.h"
+#include "storage/signalfuncs.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -780,6 +781,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
 		am_superuser = superuser();
+		BackendSignalFeedbackInit(MyBackendId);
 	}
 
 	/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8579822bcd..b8926c19dc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5972,10 +5972,10 @@
 
 { oid => '2171', descr => 'cancel a server process\' current query',
   proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4', prosrc => 'pg_cancel_backend' },
+  proargtypes => 'int4 text', proisstrict => 'f', prosrc => 'pg_cancel_backend' },
 { oid => '2096', descr => 'terminate a server process',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4', prosrc => 'pg_terminate_backend' },
+  proargtypes => 'int4 text', proisstrict => 'f', prosrc => 'pg_terminate_backend' },
 { 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/storage/signalfuncs.h b/src/include/storage/signalfuncs.h
new file mode 100644
index 0000000000..73927cfa34
--- /dev/null
+++ b/src/include/storage/signalfuncs.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * signalfuncs.h
+ *		Declarations for backend signalling
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ *	  src/include/storage/signalfuncs.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SIGNALFUNCS_H
+#define SIGNALFUNCS_H
+
+#define MAX_CANCEL_MSG 128
+
+extern Size BackendSignalFeedbackShmemSize(void);
+extern void BackendSignalFeedbackShmemInit(void);
+extern void BackendSignalFeedbackInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern int SetBackendTerminationMessage(pid_t backend, char *message);
+extern int SetBackendSignalFeedback(pid_t backend, char *message, int sqlerrcode);
+extern bool HasBackendSignalFeedback(void);
+extern int ConsumeBackendSignalFeedback(char *msg, size_t len, int *sqlerrcode, pid_t *pid);
+
+#endif /* SIGNALFUNCS_H */
diff --git a/src/test/regress/expected/admin_funcs.out b/src/test/regress/expected/admin_funcs.out
new file mode 100644
index 0000000000..84e1835fa6
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,28 @@
+select pg_cancel_backend(NULL);
+ pg_cancel_backend 
+-------------------
+ 
+(1 row)
+
+select case
+	when pg_cancel_backend(pg_backend_pid())
+	then pg_sleep(60)
+end;
+ERROR:  canceling statement due to user request
+select pg_cancel_backend(NULL, NULL);
+ pg_cancel_backend 
+-------------------
+ 
+(1 row)
+
+select pg_cancel_backend(NULL, 'suicide is painless');
+ pg_cancel_backend 
+-------------------
+ 
+(1 row)
+
+select case
+	when pg_cancel_backend(pg_backend_pid(), NULL)
+	then pg_sleep(60)
+end;
+ERROR:  canceling statement due to user request
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c8d9..cdba9235b6 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index admin_funcs
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/admin_funcs.sql b/src/test/regress/sql/admin_funcs.sql
new file mode 100644
index 0000000000..73a4994535
--- /dev/null
+++ b/src/test/regress/sql/admin_funcs.sql
@@ -0,0 +1,11 @@
+select pg_cancel_backend(NULL);
+select case
+	when pg_cancel_backend(pg_backend_pid())
+	then pg_sleep(60)
+end;
+select pg_cancel_backend(NULL, NULL);
+select pg_cancel_backend(NULL, 'suicide is painless');
+select case
+	when pg_cancel_backend(pg_backend_pid(), NULL)
+	then pg_sleep(60)
+end;
-- 
2.14.1.145.gb3622a4ee

