From f0797c1c52781de98e6d1aa69005a006e2be076b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 25 Jan 2018 23:49:37 +0100
Subject: [PATCH 2/2] 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
overloaded the existing as:

    SELECT pg_terminate_backend(<pid> [, msg]);
    SELECT pg_cancel_backend(<pid> [, msg]);
---
 doc/src/sgml/func.sgml                    |  12 +-
 src/backend/storage/ipc/backend_signal.c  | 275 ++++++++++++++++++++++++++++--
 src/backend/storage/ipc/ipci.c            |   3 +
 src/backend/tcop/postgres.c               |  38 ++++-
 src/backend/utils/init/postinit.c         |   2 +
 src/include/catalog/pg_proc.h             |   5 +
 src/include/storage/backend_signal.h      |  25 +++
 src/test/regress/expected/admin_funcs.out |  29 ++++
 src/test/regress/parallel_schedule        |   2 +-
 src/test/regress/sql/admin_funcs.sql      |   7 +
 10 files changed, 378 insertions(+), 20 deletions(-)
 create mode 100644 src/include/storage/backend_signal.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 2f59af25a6..bb7dbe264f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18453,7 +18453,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
@@ -18478,7 +18478,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
@@ -18509,6 +18509,14 @@ 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, the text
+    will be appended to the error message returned to the signalled backend.
+    <literal>message</literal> is limited to 128 bytes, any 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:  canceling statement due to user request: "Cancellation message text"
+</programlisting>
    </para>
 
    <para>
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 603b229149..1728fe22df 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,19 +18,42 @@
 
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "postmaster/syslogger.h"
+#include "storage/backend_signal.h"
+#include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
 
+/*
+ * Structure for registering a message 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	pid;
+	slock_t	mutex;
+	char	message[MAX_CANCEL_MSG];
+	int		len;
+} BackendCancelShmemStruct;
+
+static BackendCancelShmemStruct	*BackendCancelSlots = NULL;
+static volatile BackendCancelShmemStruct *MyCancelSlot = NULL;
+static void CleanupCancelBackend(int status, Datum argument);
+
 /*
  * 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.
@@ -44,7 +67,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);
 
@@ -76,6 +99,10 @@ 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)
+		SetBackendCancelMessage(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
@@ -106,10 +133,10 @@ pg_signal_backend(int pid, int sig)
  *
  * Note that only superusers can signal superuser-owned processes.
  */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
+static bool
+pg_cancel_backend_internal(pid_t pid, char *msg)
 {
-	int			r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+	int			r = pg_signal_backend(pid, SIGINT, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -124,16 +151,39 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(pg_cancel_backend_internal(PG_GETARG_INT32(0), NULL));
+}
+
+Datum
+pg_cancel_backend_msg(PG_FUNCTION_ARGS)
+{
+	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));
+
+	PG_RETURN_BOOL(pg_cancel_backend_internal(pid, msg));
+}
+
 /*
  * Signal to terminate a backend process.  This is allowed if you are a member
  * of the role whose process is being terminated.
  *
  * Note that only superusers can signal superuser-owned processes.
  */
-Datum
-pg_terminate_backend(PG_FUNCTION_ARGS)
+static bool
+pg_terminate_backend_internal(pid_t pid, char *msg)
 {
-	int			r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+	int		r = pg_signal_backend(pid, SIGTERM, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -145,7 +195,31 @@ 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);
+}
+
+Datum
+pg_terminate_backend(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(pg_terminate_backend_internal(PG_GETARG_INT32(0), NULL));
+}
+
+Datum
+pg_terminate_backend_msg(PG_FUNCTION_ARGS)
+{
+	pid_t		pid;
+	char 	   *msg = NULL;
+	
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errmsg("pid cannot be NULL")));
+
+	pid = PG_GETARG_INT32(0);
+
+	if (PG_NARGS() == 2 && !PG_ARGISNULL(1))
+		msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
 }
 
 /*
@@ -188,3 +262,182 @@ pg_rotate_logfile(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * The following routines handle registering an optional message when
+ * cancelling, or terminating a backend. The message will be stored in
+ * shared memory and is limited to MAX_CANCEL_MSG characters including
+ * the NULL terminator.
+ *
+ * Access to the message slots is protected by spinlocks.
+ */
+
+/*
+ * Return the required size for the cancelation message Shmem area.
+ */
+Size
+CancelBackendMsgShmemSize(void)
+{
+	return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+/*
+ * Create and initialize the Shmem structure for holding the messages, the
+ * bookkeeping for them and the spinlocks associated.
+ */
+void
+BackendCancelMessageShmemInit(void)
+{
+	Size	size = CancelBackendMsgShmemSize();
+	bool	found;
+	int		i;
+
+	BackendCancelSlots = (BackendCancelShmemStruct *)
+		ShmemInitStruct("BackendCancelSlots", size, &found);
+
+	if (!found)
+	{
+		MemSet(BackendCancelSlots, 0, size);
+
+		for (i = 0; i < MaxBackends; i++)
+			SpinLockInit(&(BackendCancelSlots[i].mutex));
+	}
+}
+
+/*
+ * Set up the slot for the current backend_id
+ */
+void
+BackendCancelMessageInit(int backend_id)
+{
+	volatile BackendCancelShmemStruct *slot;
+
+	slot = &BackendCancelSlots[backend_id - 1];
+
+	slot->message[0] = '\0';
+	slot->len = 0;
+	slot->pid = MyProcPid;
+
+	MyCancelSlot = slot;
+
+	on_shmem_exit(CleanupCancelBackend, 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
+CleanupCancelBackend(int status, Datum argument)
+{
+	int backend_id = DatumGetInt32(argument);
+	volatile BackendCancelShmemStruct *slot;
+
+	slot = &BackendCancelSlots[backend_id - 1];
+
+	Assert(slot == MyCancelSlot);
+
+	MyCancelSlot = NULL;
+
+	if (slot->len > 0)
+		MemSet(slot->message, '\0', sizeof(slot->message));
+
+	slot->len = 0;
+	slot->pid = 0;
+}
+
+/*
+ * Sets a cancellation message for the backend with the specified pid, and
+ * returns the length of the message actually created. If the returned length
+ * is less than the length of the message parameter, truncation has occurred.
+ * If the backend isn't found, -1 is returned. If no message is passed, zero is
+ * returned. If two backends collide in setting a message, the existing message
+ * will be overwritten by the last one in.
+ */
+int
+SetBackendCancelMessage(pid_t backend_pid, char *message)
+{
+	int		i;
+	int		len;
+
+	if (!message)
+		return 0;
+
+	for (i = 0; i < MaxBackends; i++)
+	{
+		BackendCancelShmemStruct *slot = &BackendCancelSlots[i];
+
+		if (slot->pid != 0 && slot->pid == backend_pid)
+		{
+			SpinLockAcquire(&slot->mutex);
+			if (slot->pid != backend_pid)
+			{
+				SpinLockRelease(&slot->mutex);
+				goto error;
+			}
+
+			len = pg_mbcliplen(message, strlen(message),
+							   sizeof(slot->message) - 1);
+			MemSet(slot->message, '\0', sizeof(slot->message));
+			memcpy(slot->message, message, len);
+			slot->len = len;
+			SpinLockRelease(&slot->mutex);
+
+			if (len != strlen(message))
+				ereport(NOTICE,
+						(errmsg("message is too long and has been truncated")));
+			return len;
+		}
+	}
+
+error:
+
+	elog(LOG, "Cancellation message requested for missing backend %d by %d",
+		 (int) backend_pid, MyProcPid);
+
+	return -1;
+}
+
+/*
+ * Test whether there is a cancelation message for the current backend that
+ * can be consumed and presented to the user.
+ */
+bool
+HasCancelMessage(void)
+{
+	volatile BackendCancelShmemStruct *slot = MyCancelSlot;
+	bool 	has_message = false;
+
+	if (slot != NULL)
+	{
+		SpinLockAcquire(&slot->mutex);
+		has_message = (slot->len > 0);
+		SpinLockRelease(&slot->mutex);
+	}
+
+	return has_message;
+}
+
+/*
+ * Return the configured cancellation message and its length. If the returned
+ * length is greater than the size of the passed buffer, truncation has been
+ * performed. The message is cleared on reading.
+ */
+int
+ConsumeCancelMessage(char *buffer, size_t buf_len)
+{
+	volatile BackendCancelShmemStruct *slot = MyCancelSlot;
+	int		msg_length = 0;
+
+	if (slot != NULL && slot->len > 0)
+	{
+		SpinLockAcquire(&slot->mutex);
+		strlcpy(buffer, (const char *) slot->message, buf_len);
+		msg_length = slot->len;
+		slot->len = 0;
+		slot->message[0] = '\0';
+		SpinLockRelease(&slot->mutex);
+	}
+
+	return msg_length;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..e4b5d3a45b 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/backend_signal.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, CancelBackendMsgShmemSize());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -270,6 +272,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	SyncScanShmemInit();
 	AsyncShmemInit();
 	BackendRandomShmemInit();
+	BackendCancelMessageShmemInit();
 
 #ifdef EXEC_BACKEND
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 6dc2095b9a..d43fa2d66e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -60,6 +60,7 @@
 #include "replication/slot.h"
 #include "replication/walsender.h"
 #include "rewrite/rewriteHandler.h"
+#include "storage/backend_signal.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -2918,9 +2919,22 @@ ProcessInterrupts(void)
 					 errdetail_recovery_conflict()));
 		}
 		else
-			ereport(FATAL,
-					(errcode(ERRCODE_ADMIN_SHUTDOWN),
-					 errmsg("terminating connection due to administrator command")));
+		{
+			if (HasCancelMessage())
+			{
+				char	buffer[MAX_CANCEL_MSG];
+
+				ConsumeCancelMessage(buffer, MAX_CANCEL_MSG);
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						 errmsg("terminating connection due to administrator command: \"%s\"",
+						 buffer)));
+			}
+			else
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						 errmsg("terminating connection due to administrator command")));
+		}
 	}
 	if (ClientConnectionLost)
 	{
@@ -3031,9 +3045,21 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+
+			if (HasCancelMessage())
+			{
+				char	buffer[MAX_CANCEL_MSG];
+
+				ConsumeCancelMessage(buffer, MAX_CANCEL_MSG);
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request: \"%s\"",
+								buffer)));
+			}
+			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 d8f45b3c43..616580be71 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/backend_signal.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -747,6 +748,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
 		am_superuser = superuser();
+		BackendCancelMessageInit(MyBackendId);
 	}
 
 	/*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 0fdb42f639..4223cdfd8e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3286,8 +3286,13 @@ DESCR("is schema another session's temp schema?");
 
 DATA(insert OID = 2171 ( pg_cancel_backend		PGNSP PGUID 12 1 0 0 0 f f f t f v s 1 0 16 "23" _null_ _null_ _null_ _null_ _null_ pg_cancel_backend _null_ _null_ _null_ ));
 DESCR("cancel a server process' current query");
+DATA(insert OID = 3438 ( pg_cancel_backend		PGNSP PGUID 12 1 0 0 0 f f f f f v s 2 0 16 "23 25" _null_ _null_ _null_ _null_ _null_ pg_cancel_backend_msg _null_ _null_ _null_ ));
+DESCR("cancel a server process' current query");
+
 DATA(insert OID = 2096 ( pg_terminate_backend		PGNSP PGUID 12 1 0 0 0 f f f t f v s 1 0 16 "23" _null_ _null_ _null_ _null_ _null_ pg_terminate_backend _null_ _null_ _null_ ));
 DESCR("terminate a server process");
+DATA(insert OID = 3437 ( pg_terminate_backend		PGNSP PGUID 12 1 0 0 0 f f f f f v s 2 0 16 "23 25" _null_ _null_ _null_ _null_ _null_ pg_terminate_backend_msg _null_ _null_ _null_ ));
+DESCR("terminate a server process");
 DATA(insert OID = 2172 ( pg_start_backup		PGNSP PGUID 12 1 0 0 0 f f f t f v r 3 0 3220 "25 16 16" _null_ _null_ _null_ _null_ _null_ pg_start_backup _null_ _null_ _null_ ));
 DESCR("prepare for taking an online backup");
 DATA(insert OID = 2173 ( pg_stop_backup			PGNSP PGUID 12 1 0 0 0 f f f t f v r 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
diff --git a/src/include/storage/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..5bb04615fd
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.h
+ *		Declarations for backend signalling
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ *	  src/include/storage/backend_signal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef BACKEND_SIGNAL_H
+#define BACKEND_SIGNAL_H
+
+#define MAX_CANCEL_MSG 128
+
+extern Size CancelBackendMsgShmemSize(void);
+extern void BackendCancelMessageShmemInit(void);
+extern void BackendCancelMessageInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int ConsumeCancelMessage(char *msg, size_t len);
+
+#endif /* BACKEND_SIGNAL_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..5bf5b6bc1e
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,29 @@
+select pg_cancel_backend();
+ERROR:  function pg_cancel_backend() does not exist
+LINE 1: select pg_cancel_backend();
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select pg_cancel_backend(NULL);
+ pg_cancel_backend 
+-------------------
+ 
+(1 row)
+
+select pg_cancel_backend(pg_backend_pid());
+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 pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
+ERROR:  canceling statement due to user request: "it brings on many changes"
+select pg_cancel_backend(pg_backend_pid(), NULL);
+ERROR:  canceling statement due to user request
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ad9434fb87..f1f9eef56d 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
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password 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..420d782b80
--- /dev/null
+++ b/src/test/regress/sql/admin_funcs.sql
@@ -0,0 +1,7 @@
+select pg_cancel_backend();
+select pg_cancel_backend(NULL);
+select pg_cancel_backend(pg_backend_pid());
+select pg_cancel_backend(NULL, NULL);
+select pg_cancel_backend(NULL, 'suicide is painless');
+select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
+select pg_cancel_backend(pg_backend_pid(), NULL);
-- 
2.14.1.145.gb3622a4ee

