Optional message to user when terminating/cancelling backend
When terminating, or cancelling, a backend it’s currently not possible to let
the signalled session know *why* it was dropped. This has nagged me in the
past and now it happened to come up again, so I took a stab at this. The
attached patch implements the ability to pass an optional text message to the
signalled session which is included in the error message:
SELECT pg_terminate_backend(<pid> [, message]);
SELECT pg_cancel_backend(<pid> [, message]);
Right now the message is simply appended on the error message, not sure if
errdetail or errhint would be better? Calling:
select pg_terminate_backend(<pid>, 'server rebooting');
..leads to:
FATAL: terminating connection due to administrator command: "server rebooting"
Omitting the message invokes the command just like today.
The message is stored in a new shmem area which is checked when the session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs are
allocated can be done (but seemed rather complicated for little gain compared
to the quite moderate memory spend.)
cheers ./daniel
Attachments:
terminate_msg_v2.patchapplication/octet-stream; name=terminate_msg_v2.patchDownload
From f6692187a3c0f2e5d5472a92499c7443927a9a3b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 18 Jun 2017 09:00:52 +0200
Subject: [PATCH] Support optional message to pg_terminate_backend
This adds the ability for the caller of pg_terminate_backend() to
include an optional message to the process which is being killed.
The message will be appended to the error message returned to the
killed process. The new syntax is overloaded as:
SELECT pg_terminate_backend(<pid> [, msg]);
SELECT pg_cancel_backend(<pid> [, msg]);
---
doc/src/sgml/func.sgml | 6 +-
src/backend/storage/ipc/ipci.c | 2 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/backend/tcop/postgres.c | 38 +++++--
src/backend/utils/adt/misc.c | 50 +++++++--
src/backend/utils/init/postinit.c | 1 +
src/backend/utils/misc/Makefile | 6 +-
src/backend/utils/misc/backend_cancel.c | 167 +++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +
src/include/utils/backend_cancel.h | 25 +++++
10 files changed, 281 insertions(+), 19 deletions(-)
create mode 100644 src/backend/utils/misc/backend_cancel.c
create mode 100644 src/include/utils/backend_cancel.h
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e073f7b57c..4e4d0f1ad6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18330,7 +18330,7 @@ SELECT set_config('log_statement_stats', 'off', false);
<tbody>
<row>
<entry>
- <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
+ <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</> [, <parameter>message</parameter> <type>text</>])</function></literal>
</entry>
<entry><type>boolean</type></entry>
<entry>Cancel a backend's current query. This is also allowed if the
@@ -18355,7 +18355,7 @@ SELECT set_config('log_statement_stats', 'off', false);
</row>
<row>
<entry>
- <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
+ <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</> [, <parameter>message</parameter> <type>text</>])</function></literal>
</entry>
<entry><type>boolean</type></entry>
<entry>Terminate a backend. This is also allowed if the calling role
@@ -18386,6 +18386,8 @@ 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.
</para>
<para>
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2d1ed143e0..63ac4f0ab3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -150,6 +150,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 +271,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
SyncScanShmemInit();
AsyncShmemInit();
BackendRandomShmemInit();
+ BackendCancelShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index e6025ecedb..772ff518ee 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -50,3 +50,4 @@ OldSnapshotTimeMapLock 42
BackendRandomLock 43
LogicalRepWorkerLock 44
CLogTruncationLock 45
+BackendCancelLock 46
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index f99dd0a2d4..1c886426c0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -69,6 +69,7 @@
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
+#include "utils/backend_cancel.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
@@ -2876,9 +2877,22 @@ ProcessInterrupts(void)
errdetail_recovery_conflict()));
}
else
- ereport(FATAL,
- (errcode(ERRCODE_ADMIN_SHUTDOWN),
- errmsg("terminating connection due to administrator command")));
+ {
+ if (HasCancelMessage())
+ {
+ char *buffer = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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)
{
@@ -2991,9 +3005,21 @@ ProcessInterrupts(void)
if (!DoingCommandRead)
{
LockErrorCleanup();
- ereport(ERROR,
- (errcode(ERRCODE_QUERY_CANCELED),
- errmsg("canceling statement due to user request")));
+
+ if (HasCancelMessage())
+ {
+ char *buffer = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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/adt/misc.c b/src/backend/utils/adt/misc.c
index 9cc0b08e96..0abbe47c08 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -41,6 +41,7 @@
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -216,7 +217,7 @@ current_query(PG_FUNCTION_ARGS)
#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);
@@ -257,6 +258,9 @@ pg_signal_backend(int pid, int sig)
* too unlikely to worry about.
*/
+ if (msg != NULL)
+ SetBackendCancelMessage(pid, msg);
+
/* If we have setsid(), signal the backend's whole process group */
#ifdef HAVE_SETSID
if (kill(-pid, sig))
@@ -278,10 +282,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,
@@ -296,16 +300,31 @@ 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 = PG_GETARG_INT32(0);
+ char *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,
@@ -317,7 +336,22 @@ 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 = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b8b4a06350..cbf6cb4f60 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -740,6 +740,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
PerformAuthentication(MyProcPort);
InitializeSessionUserId(username, useroid);
am_superuser = superuser();
+ BackendCancelInit(MyBackendId);
}
/*
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index a53fcdf188..619c837e08 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -14,9 +14,9 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
-OBJS = backend_random.o guc.o help_config.o pg_config.o pg_controldata.o \
- pg_rusage.o ps_status.o queryenvironment.o rls.o sampling.o \
- superuser.o timeout.o tzparser.o
+OBJS = backend_cancel.o backend_random.o guc.o help_config.o pg_config.o \
+ pg_controldata.o pg_rusage.o ps_status.o queryenvironment.o rls.o \
+ sampling.o superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
# we can't substitute it into pg_config.h.
diff --git a/src/backend/utils/misc/backend_cancel.c b/src/backend/utils/misc/backend_cancel.c
new file mode 100644
index 0000000000..c1628fe574
--- /dev/null
+++ b/src/backend/utils/misc/backend_cancel.c
@@ -0,0 +1,167 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.c
+ * Backend cancellation messaging
+ *
+ *
+ * Module for supporting passing a user defined message to a cancelled,
+ * or terminated, backend from the user/administrator.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/backend_cancel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/backend_cancel.h"
+
+/*
+ * Each backend is registered per pid in the array which is indexed by
+ * Backend ID. Reading and writing the message is protected by the
+ * BackendCancelLock lwlock.
+ */
+typedef struct
+{
+ pid_t pid;
+ char message[MAX_CANCEL_MSG];
+ int len;
+} BackendCancelShmemStruct;
+
+static BackendCancelShmemStruct *BackendCancelSlots = NULL;
+static volatile BackendCancelShmemStruct *MyCancelSlot = NULL;
+
+static void CleanupCancelBackend(int status, Datum argument);
+
+Size
+CancelBackendMsgShmemSize(void)
+{
+ return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+void
+BackendCancelShmemInit(void)
+{
+ Size size = CancelBackendMsgShmemSize();
+ bool found;
+
+ BackendCancelSlots = (BackendCancelShmemStruct *)
+ ShmemInitStruct("BackendCancelSlots", size, &found);
+
+ if (!found)
+ MemSet(BackendCancelSlots, 0, size);
+}
+
+void
+BackendCancelInit(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));
+}
+
+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)
+ slot->message[0] = '\0';
+
+ slot->len = 0;
+ slot->pid = 0;
+}
+
+/*
+ * Sets a cancellation message for the backend with the specified pid and
+ * returns the length of message actually created. If the returned length
+ * is equal to the length of the message parameter, truncation may have
+ * occurred. If the backend wasn't found and no message was set, -1 is
+ * returned.
+ */
+int
+SetBackendCancelMessage(pid_t backend, char *message)
+{
+ BackendCancelShmemStruct *slot;
+ int i;
+
+ if (!message)
+ return 0;
+
+
+ for (i = 0; i < MaxBackends; i++)
+ {
+ slot = &BackendCancelSlots[i];
+
+ if (slot->pid != 0 && slot->pid == backend)
+ {
+ LWLockAcquire(BackendCancelLock, LW_EXCLUSIVE);
+
+ strlcpy(slot->message, message, sizeof(slot->message));
+ slot->len = strlen(message);
+
+ LWLockRelease(BackendCancelLock);
+ return slot->len;
+ }
+ }
+
+ elog(LOG, "Cancellation message requested for missing backend %d by %d",
+ (int) backend, MyProcPid);
+
+ return -1;
+}
+
+bool
+HasCancelMessage(void)
+{
+ volatile BackendCancelShmemStruct *slot = MyCancelSlot;
+
+ return (slot != NULL && slot->len > 0);
+}
+
+/*
+ * Return the configured cancellation message and its length. If the
+ * returned length is greater than, or equal to, the size of the passed
+ * buffer, truncation may have been performed. The message is cleared
+ * on reading.
+ */
+int
+GetCancelMessage(char **buffer, size_t buf_len)
+{
+ volatile BackendCancelShmemStruct *slot = MyCancelSlot;
+
+ if (slot != NULL && slot->len > 0)
+ {
+ LWLockAcquire(BackendCancelLock, LW_EXCLUSIVE);
+ strlcpy(*buffer, (const char *) slot->message, buf_len);
+ slot->len = 0;
+ slot->message[0] = '\0';
+ LWLockRelease(BackendCancelLock);
+
+ return slot->len;
+ }
+
+ return 0;
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 6c44def6e6..edd4a6cdb0 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3203,8 +3203,12 @@ 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 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 = 772 ( pg_cancel_backend PGNSP PGUID 12 1 0 0 0 f f f f t 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 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 = 972 ( pg_terminate_backend PGNSP PGUID 12 1 0 0 0 f f f f t 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 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 f t f v r 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
diff --git a/src/include/utils/backend_cancel.h b/src/include/utils/backend_cancel.h
new file mode 100644
index 0000000000..7f210553d6
--- /dev/null
+++ b/src/include/utils/backend_cancel.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.h
+ * Declarations for backend cancellation messaging
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ *
+ * src/include/utils/backend_cancel.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef BACKEND_CANCEL_H
+#define BACKEND_CANCEL_H
+
+#define MAX_CANCEL_MSG 128
+
+extern Size CancelBackendMsgShmemSize(void);
+extern void BackendCancelShmemInit(void);
+extern void BackendCancelInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int GetCancelMessage(char **msg, size_t len);
+
+#endif /* BACKEND_CANCEL_H */
--
2.13.0.rc0.45.ge2cb6ab.dirty
2017-06-19 20:24 GMT+02:00 Daniel Gustafsson <daniel@yesql.se>:
When terminating, or cancelling, a backend it’s currently not possible to
let
the signalled session know *why* it was dropped. This has nagged me in the
past and now it happened to come up again, so I took a stab at this. The
attached patch implements the ability to pass an optional text message to
the
signalled session which is included in the error message:SELECT pg_terminate_backend(<pid> [, message]);
SELECT pg_cancel_backend(<pid> [, message]);Right now the message is simply appended on the error message, not sure if
errdetail or errhint would be better? Calling:select pg_terminate_backend(<pid>, 'server rebooting');
..leads to:
FATAL: terminating connection due to administrator command: "server
rebooting"Omitting the message invokes the command just like today.
The message is stored in a new shmem area which is checked when the
session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs
are
allocated can be done (but seemed rather complicated for little gain
compared
to the quite moderate memory spend.)cheers ./daniel
+1
very good idea
Pavel
Show quoted text
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
+1.
This really helps PostgreSQL Azure service as well. When we are doing the upgrades to the service, instead of abruptly terminating the sessions we can provide this message.
Thanks,
Satya
From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Pavel Stehule
Sent: Monday, June 19, 2017 11:41 AM
To: Daniel Gustafsson <daniel@yesql.se>
Cc: PostgreSQL mailing lists <pgsql-hackers@postgresql.org>
Subject: Re: [HACKERS] Optional message to user when terminating/cancelling backend
2017-06-19 20:24 GMT+02:00 Daniel Gustafsson <daniel@yesql.se<mailto:daniel@yesql.se>>:
When terminating, or cancelling, a backend it’s currently not possible to let
the signalled session know *why* it was dropped. This has nagged me in the
past and now it happened to come up again, so I took a stab at this. The
attached patch implements the ability to pass an optional text message to the
signalled session which is included in the error message:
SELECT pg_terminate_backend(<pid> [, message]);
SELECT pg_cancel_backend(<pid> [, message]);
Right now the message is simply appended on the error message, not sure if
errdetail or errhint would be better? Calling:
select pg_terminate_backend(<pid>, 'server rebooting');
..leads to:
FATAL: terminating connection due to administrator command: "server rebooting"
Omitting the message invokes the command just like today.
The message is stored in a new shmem area which is checked when the session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs are
allocated can be done (but seemed rather complicated for little gain compared
to the quite moderate memory spend.)
cheers ./daniel
+1
very good idea
Pavel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org<mailto:pgsql-hackers@postgresql.org>)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers<https://na01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fwww.postgresql.org%2Fmailpref%2Fpgsql-hackers&data=02%7C01%7CSatyanarayana.Narlapuram%40microsoft.com%7C8f211f53d54c4d6308d308d4b742f824%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636334945639579811&sdata=VH8ljYHI%2BaxoNdM7pYLpfEqa%2FbXWf0dRptoNzxElbaA%3D&reserved=0>
Satyanarayana Narlapuram wrote:
+1.
This really helps PostgreSQL Azure service as well. When we are doing
the upgrades to the service, instead of abruptly terminating the
sessions we can provide this message.
I think you mean "in addition to" rather than "instead of".
Unless you have a lot of users running psql manually, I don't see how
this is actually very useful or actionable. What would the user do with
the information? Hopefully your users already trust that you'd keep the
downtime to the minimum possible.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jun 20, 2017 at 11:54 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Satyanarayana Narlapuram wrote:
Unless you have a lot of users running psql manually, I don't see how
this is actually very useful or actionable. What would the user do with
the information? Hopefully your users already trust that you'd keep the
downtime to the minimum possible.
Why wouldn't this be useful in application logs? Spurious dropped
connections during application execution would be alarming. Seeing a
message from the DBA when looking into those would be a painless and
quick way to alleviate stress.
pg_cancel_backend(<pid>, 'please try not to leave sessions in an "idle
in transaction" state...') would also seem like a useful message to
communicate; to user or application. Sure, some of this can, and
maybe would also need to, be done out-of-band but this communication
channel seems worthy enough to at least evaluate the provided
implementation.
David J.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Agree with David on the general usefulness of this channel. Not that Azure has this implementation or proposal today, but as a managed service this channel of communication is worth. For example, DBA / service can set a policy that if certain session exceeds the resource usage DBA can kill it and communicate the same. For example, too many locks, lot of IO activity, large open transactions etc. The messages will help application developer to tune their workloads appropriately.
Thanks,
Satya
-----Original Message-----
From: David G. Johnston [mailto:david.g.johnston@gmail.com]
Sent: Tuesday, June 20, 2017 12:44 PM
To: Alvaro Herrera <alvherre@2ndquadrant.com>
Cc: Satyanarayana Narlapuram <Satyanarayana.Narlapuram@microsoft.com>; Pavel Stehule <pavel.stehule@gmail.com>; Daniel Gustafsson <daniel@yesql.se>; PostgreSQL mailing lists <pgsql-hackers@postgresql.org>
Subject: Re: [HACKERS] Optional message to user when terminating/cancelling backend
On Tue, Jun 20, 2017 at 11:54 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Satyanarayana Narlapuram wrote:
Unless you have a lot of users running psql manually, I don't see how
this is actually very useful or actionable. What would the user do
with the information? Hopefully your users already trust that you'd
keep the downtime to the minimum possible.
Why wouldn't this be useful in application logs? Spurious dropped connections during application execution would be alarming. Seeing a message from the DBA when looking into those would be a painless and quick way to alleviate stress.
pg_cancel_backend(<pid>, 'please try not to leave sessions in an "idle in transaction" state...') would also seem like a useful message to communicate; to user or application. Sure, some of this can, and maybe would also need to, be done out-of-band but this communication channel seems worthy enough to at least evaluate the provided implementation.
David J.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 2017-06-19 20:24:43 +0200, Daniel Gustafsson wrote:
When terminating, or cancelling, a backend it’s currently not possible to let
the signalled session know *why* it was dropped. This has nagged me in the
past and now it happened to come up again, so I took a stab at this. The
attached patch implements the ability to pass an optional text message to the
signalled session which is included in the error message:SELECT pg_terminate_backend(<pid> [, message]);
SELECT pg_cancel_backend(<pid> [, message]);Right now the message is simply appended on the error message, not sure if
errdetail or errhint would be better? Calling:select pg_terminate_backend(<pid>, 'server rebooting');
..leads to:
FATAL: terminating connection due to administrator command: "server rebooting"
Omitting the message invokes the command just like today.
For extensions it'd also be useful if it'd be possible to overwrite the
error code. E.g. for citus there's a distributed deadlock detector,
running out of process because there's no way to interrupt lock waits
locally, and we've to do some ugly hacking to generate proper error
messages and code from another session.
- Andres
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jun 20, 2017 at 3:24 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
The message is stored in a new shmem area which is checked when the session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs are
allocated can be done (but seemed rather complicated for little gain compared
to the quite moderate memory spend.)
I think that you are right to take the approach with a per-backend
slot. This will avoid complications related to entry removals and
locking issues. There would be scaling issues as well if things get
very signaled for a lot of backends.
+#define MAX_CANCEL_MSG 128
That looks enough.
+ LWLockAcquire(BackendCancelLock, LW_EXCLUSIVE);
+
+ strlcpy(slot->message, message, sizeof(slot->message));
+ slot->len = strlen(message);
Why not using one spin lock per slot and save it in BackendCancelShmemStruct?
+ pid_t pid = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
It would be more solid to add some error handling for messages that
are too long, or at least truncate the message if too long.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
Here are some comments for the patch.
+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 = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_cancel_backend_internal(pid, msg));
+}
It would be better to insert a blank line between these functions.
+/*
+ * Sets a cancellation message for the backend with the specified pid and
+ * returns the length of message actually created. If the returned length
+ * is equal to the length of the message parameter, truncation may have
+ * occurred. If the backend wasn't found and no message was set, -1 is
+ * returned.
+ */
It seems to me that this comment is incorrect.
"If the returned length is not equal to the length of the message parameter,"
is right, isn't it?
In addition, the last statement would be
"If the backend wasn't found, -1 is returned. Otherwize, if no message was set,
0 is returned."
+ strlcpy(slot->message, message, sizeof(slot->message));
+ slot->len = strlen(message);
+
+ LWLockRelease(BackendCancelLock);
+ return slot->len;
If SetBackendCancelMessage() has to return the length of message actually created,
slot->len should be strlen(slot->message) instead of strlen(message).
In the current code, when the return value and slot->len is always set
to the length of the passed message parameter.
Regards,
On Mon, 19 Jun 2017 20:24:43 +0200
Daniel Gustafsson <daniel@yesql.se> wrote:
When terminating, or cancelling, a backend it’s currently not possible to let
the signalled session know *why* it was dropped. This has nagged me in the
past and now it happened to come up again, so I took a stab at this. The
attached patch implements the ability to pass an optional text message to the
signalled session which is included in the error message:SELECT pg_terminate_backend(<pid> [, message]);
SELECT pg_cancel_backend(<pid> [, message]);Right now the message is simply appended on the error message, not sure if
errdetail or errhint would be better? Calling:select pg_terminate_backend(<pid>, 'server rebooting');
..leads to:
FATAL: terminating connection due to administrator command: "server rebooting"
Omitting the message invokes the command just like today.
The message is stored in a new shmem area which is checked when the session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs are
allocated can be done (but seemed rather complicated for little gain compared
to the quite moderate memory spend.)cheers ./daniel
--
Yugo Nagata <nagata@sraoss.co.jp>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, 21 Jun 2017 12:06:33 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:
On Tue, Jun 20, 2017 at 3:24 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
The message is stored in a new shmem area which is checked when the session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs are
allocated can be done (but seemed rather complicated for little gain compared
to the quite moderate memory spend.)I think that you are right to take the approach with a per-backend
slot. This will avoid complications related to entry removals and
locking issues. There would be scaling issues as well if things get
very signaled for a lot of backends.+#define MAX_CANCEL_MSG 128
That looks enough.+ LWLockAcquire(BackendCancelLock, LW_EXCLUSIVE); + + strlcpy(slot->message, message, sizeof(slot->message)); + slot->len = strlen(message); Why not using one spin lock per slot and save it in BackendCancelShmemStruct?
+1
I found an example that a spin lock is used during strlcpy in WalReceiverMain().
+ pid_t pid = PG_GETARG_INT32(0); + char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg)); It would be more solid to add some error handling for messages that are too long, or at least truncate the message if too long.
I agree that error handling for too long messages is needed.
Although long messages are truncated in SetBackendCancelMessage(),
it is better to inform users that the message they can read was
truncated one. Or, maybe we should prohibit too long message
is passed in pg_teminate_backend()
--
Michael--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Yugo Nagata <nagata@sraoss.co.jp>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 21 Jun 2017, at 16:30, Yugo Nagata <nagata@sraoss.co.jp> wrote:
On Wed, 21 Jun 2017 12:06:33 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:On Tue, Jun 20, 2017 at 3:24 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
The message is stored in a new shmem area which is checked when the session is
aborted. To keep things simple a small buffer is kept per backend for the
message. If deemed too costly, keeping a central buffer from which slabs are
allocated can be done (but seemed rather complicated for little gain compared
to the quite moderate memory spend.)I think that you are right to take the approach with a per-backend
slot. This will avoid complications related to entry removals and
locking issues. There would be scaling issues as well if things get
very signaled for a lot of backends.+#define MAX_CANCEL_MSG 128
That looks enough.+ LWLockAcquire(BackendCancelLock, LW_EXCLUSIVE); + + strlcpy(slot->message, message, sizeof(slot->message)); + slot->len = strlen(message); Why not using one spin lock per slot and save it in BackendCancelShmemStruct?+1
I found an example that a spin lock is used during strlcpy in WalReceiverMain().
Yeah I agree as well, will fix.
+ pid_t pid = PG_GETARG_INT32(0); + char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg)); It would be more solid to add some error handling for messages that are too long, or at least truncate the message if too long.I agree that error handling for too long messages is needed.
Although long messages are truncated in SetBackendCancelMessage(),
it is better to inform users that the message they can read was
truncated one. Or, maybe we should prohibit too long message
is passed in pg_teminate_backend()
The message is truncated in SetBackendCancelMessage() for safety, but
pg_{cancel|terminate}_backend() could throw an error on too long message, or
warning truncation, to the caller as well. Personally I think a warning is the
appropriate response, but I don’t really have a strong opinion.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jun 21, 2017 at 11:42 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
The message is truncated in SetBackendCancelMessage() for safety, but
pg_{cancel|terminate}_backend() could throw an error on too long message, or
warning truncation, to the caller as well. Personally I think a warning is the
appropriate response, but I don’t really have a strong opinion.
And a NOTICE? That's what happens for relation name truncation. You
are right that having a check in SetBackendCancelMessage() makes the
most sense as bgworkers could just call the low level API. Isn't the
concept actually closer to just a backend message? This slot could be
used for other purposes than cancellation.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, 22 Jun 2017 09:24:54 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:
On Wed, Jun 21, 2017 at 11:42 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
The message is truncated in SetBackendCancelMessage() for safety, but
pg_{cancel|terminate}_backend() could throw an error on too long message, or
warning truncation, to the caller as well. Personally I think a warning is the
appropriate response, but I don’t really have a strong opinion.And a NOTICE? That's what happens for relation name truncation. You
are right that having a check in SetBackendCancelMessage() makes the
most sense as bgworkers could just call the low level API. Isn't the
concept actually closer to just a backend message? This slot could be
used for other purposes than cancellation.
+1 for NOTICE. The message truncation seems to be a kind of helpful
information rather than a likely problem as long as pg_terminated_backend
exits successfully.
https://www.postgresql.org/docs/10/static/runtime-config-logging.html#runtime-config-severity-levels
--
Michael
--
Yugo Nagata <nagata@sraoss.co.jp>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 22 Jun 2017, at 10:24, Yugo Nagata <nagata@sraoss.co.jp> wrote:
On Thu, 22 Jun 2017 09:24:54 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:On Wed, Jun 21, 2017 at 11:42 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
The message is truncated in SetBackendCancelMessage() for safety, but
pg_{cancel|terminate}_backend() could throw an error on too long message, or
warning truncation, to the caller as well. Personally I think a warning is the
appropriate response, but I don’t really have a strong opinion.And a NOTICE? That's what happens for relation name truncation. You
are right that having a check in SetBackendCancelMessage() makes the
most sense as bgworkers could just call the low level API. Isn't the
concept actually closer to just a backend message? This slot could be
used for other purposes than cancellation.+1 for NOTICE. The message truncation seems to be a kind of helpful
information rather than a likely problem as long as pg_terminated_backend
exits successfully.https://www.postgresql.org/docs/10/static/runtime-config-logging.html#runtime-config-severity-levels
Good point. I’ve attached a new version which issues a NOTICE on truncation
and also addresses all other comments so far in this thread. Thanks a lot for
the early patch reviews!
cheers ./daniel
Attachments:
terminate_msg_v3.patchapplication/octet-stream; name=terminate_msg_v3.patchDownload
From b937d85e470fd1490246f0db2890a3b8b08457c3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 18 Jun 2017 09:00:52 +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 process. The new syntax is overloaded
as:
SELECT pg_terminate_backend(<pid> [, msg]);
SELECT pg_cancel_backend(<pid> [, msg]);
---
doc/src/sgml/func.sgml | 6 +-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 38 ++++++-
src/backend/utils/adt/misc.c | 60 ++++++++--
src/backend/utils/init/postinit.c | 2 +
src/backend/utils/misc/Makefile | 6 +-
src/backend/utils/misc/backend_cancel.c | 189 ++++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +
src/include/utils/backend_cancel.h | 25 +++++
9 files changed, 314 insertions(+), 19 deletions(-)
create mode 100644 src/backend/utils/misc/backend_cancel.c
create mode 100644 src/include/utils/backend_cancel.h
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e073f7b57c..4e4d0f1ad6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18330,7 +18330,7 @@ SELECT set_config('log_statement_stats', 'off', false);
<tbody>
<row>
<entry>
- <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
+ <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</> [, <parameter>message</parameter> <type>text</>])</function></literal>
</entry>
<entry><type>boolean</type></entry>
<entry>Cancel a backend's current query. This is also allowed if the
@@ -18355,7 +18355,7 @@ SELECT set_config('log_statement_stats', 'off', false);
</row>
<row>
<entry>
- <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
+ <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</> [, <parameter>message</parameter> <type>text</>])</function></literal>
</entry>
<entry><type>boolean</type></entry>
<entry>Terminate a backend. This is also allowed if the calling role
@@ -18386,6 +18386,8 @@ 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.
</para>
<para>
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2d1ed143e0..f998ee82d3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -44,6 +44,7 @@
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
+#include "utils/backend_cancel.h"
#include "utils/backend_random.h"
#include "utils/snapmgr.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();
+ BackendCancelShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b8d860ebdb..b3135927d0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -69,6 +69,7 @@
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
+#include "utils/backend_cancel.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
@@ -2879,9 +2880,22 @@ ProcessInterrupts(void)
errdetail_recovery_conflict()));
}
else
- ereport(FATAL,
- (errcode(ERRCODE_ADMIN_SHUTDOWN),
- errmsg("terminating connection due to administrator command")));
+ {
+ if (HasCancelMessage())
+ {
+ char *buffer = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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)
{
@@ -2994,9 +3008,21 @@ ProcessInterrupts(void)
if (!DoingCommandRead)
{
LockErrorCleanup();
- ereport(ERROR,
- (errcode(ERRCODE_QUERY_CANCELED),
- errmsg("canceling statement due to user request")));
+
+ if (HasCancelMessage())
+ {
+ char *buffer = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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/adt/misc.c b/src/backend/utils/adt/misc.c
index 62341b84d1..38d4fd8ad9 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -41,6 +41,7 @@
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -216,7 +217,7 @@ current_query(PG_FUNCTION_ARGS)
#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);
@@ -248,6 +249,18 @@ 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)
+ {
+ int r;
+
+ r = SetBackendCancelMessage(pid, msg);
+
+ if (r != -1 && r != strlen(msg))
+ ereport(NOTICE,
+ (errmsg("message is too long and has been truncated")));
+ }
+
/*
* 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
@@ -278,10 +291,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,
@@ -296,16 +309,32 @@ 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 = PG_GETARG_INT32(0);
+ char *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,
@@ -317,7 +346,22 @@ 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 = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index eb6960d93f..c2abaf34ee 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -50,6 +50,7 @@
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/memutils.h"
@@ -740,6 +741,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
PerformAuthentication(MyProcPort);
InitializeSessionUserId(username, useroid);
am_superuser = superuser();
+ BackendCancelInit(MyBackendId);
}
/*
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index a53fcdf188..619c837e08 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -14,9 +14,9 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
-OBJS = backend_random.o guc.o help_config.o pg_config.o pg_controldata.o \
- pg_rusage.o ps_status.o queryenvironment.o rls.o sampling.o \
- superuser.o timeout.o tzparser.o
+OBJS = backend_cancel.o backend_random.o guc.o help_config.o pg_config.o \
+ pg_controldata.o pg_rusage.o ps_status.o queryenvironment.o rls.o \
+ sampling.o superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
# we can't substitute it into pg_config.h.
diff --git a/src/backend/utils/misc/backend_cancel.c b/src/backend/utils/misc/backend_cancel.c
new file mode 100644
index 0000000000..194554eab2
--- /dev/null
+++ b/src/backend/utils/misc/backend_cancel.c
@@ -0,0 +1,189 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.c
+ * Backend cancellation messaging
+ *
+ *
+ * Module for supporting passing a user defined message to a cancelled,
+ * or terminated, backend from the user/administrator.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/backend_cancel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/spin.h"
+#include "storage/shmem.h"
+#include "utils/backend_cancel.h"
+
+/*
+ * 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);
+
+Size
+CancelBackendMsgShmemSize(void)
+{
+ return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+void
+BackendCancelShmemInit(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));
+ }
+}
+
+void
+BackendCancelInit(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));
+}
+
+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)
+ slot->message[0] = '\0';
+
+ slot->len = 0;
+ slot->pid = 0;
+}
+
+/*
+ * Sets a cancellation message for the backend with the specified pid, and
+ * returns the length of message actually created. If the returned length
+ * is less than the length of the message parameter, truncation has occurred.
+ * If the backend wasn't found and no message was set, -1 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, char *message)
+{
+ BackendCancelShmemStruct *slot;
+ int i;
+ int message_len;
+
+ if (!message)
+ return 0;
+
+ for (i = 0; i < MaxBackends; i++)
+ {
+ slot = &BackendCancelSlots[i];
+
+ if (slot->pid != 0 && slot->pid == backend)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (slot->pid != backend)
+ {
+ SpinLockRelease(&slot->mutex);
+ goto error;
+ }
+
+ strlcpy(slot->message, message, sizeof(slot->message));
+ slot->len = strlen(slot->message);
+ message_len = slot->len;
+ SpinLockRelease(&slot->mutex);
+
+ return message_len;
+ }
+ }
+
+error:
+
+ elog(LOG, "Cancellation message requested for missing backend %d by %d",
+ (int) backend, MyProcPid);
+
+ return -1;
+}
+
+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
+GetCancelMessage(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/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 1191b4ab1b..1b2bc18ff7 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3203,8 +3203,12 @@ 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 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 = 772 ( pg_cancel_backend PGNSP PGUID 12 1 0 0 0 f f f f t 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 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 = 972 ( pg_terminate_backend PGNSP PGUID 12 1 0 0 0 f f f f t 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 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 f t f v r 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
diff --git a/src/include/utils/backend_cancel.h b/src/include/utils/backend_cancel.h
new file mode 100644
index 0000000000..7f210553d6
--- /dev/null
+++ b/src/include/utils/backend_cancel.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.h
+ * Declarations for backend cancellation messaging
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ *
+ * src/include/utils/backend_cancel.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef BACKEND_CANCEL_H
+#define BACKEND_CANCEL_H
+
+#define MAX_CANCEL_MSG 128
+
+extern Size CancelBackendMsgShmemSize(void);
+extern void BackendCancelShmemInit(void);
+extern void BackendCancelInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int GetCancelMessage(char **msg, size_t len);
+
+#endif /* BACKEND_CANCEL_H */
--
2.13.0.rc0.45.ge2cb6ab.dirty
On Thu, Jun 22, 2017 at 7:18 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
Good point. I’ve attached a new version which issues a NOTICE on truncation
and also addresses all other comments so far in this thread. Thanks a lot for
the early patch reviews!cheers ./daniel
I have done an initial review of the patch. I have some comments/suggestions.
+int
+GetCancelMessage(char **buffer, size_t buf_len)
+{
+ volatile BackendCancelShmemStruct *slot = MyCancelSlot;
+ int msg_length = 0;
+
Returned value of this function is never used, better to convert it to
just void.
-------
+bool
+HasCancelMessage(void)
+{
+ volatile BackendCancelShmemStruct *slot = MyCancelSlot;
+/*
+ * 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
+GetCancelMessage(char **buffer, size_t buf_len)
I think it will be good to merge these two function where
GetCancelMessage will first check whether it has the message or not
if it does then allocate the buffer of size slot->len and copy the
slot message to allocated buffer otherwise just return NULL.
So it will look like "char *GetCancelMessage()"
---------
+ SpinLockAcquire(&slot->mutex);
+ strlcpy(*buffer, (const char *) slot->message, buf_len);
strlcpy(*buffer, (const char *) slot->message, slot->len);
Isn't it better to copy only upto slot->len, seems like it will always
be <= buf_len, and if not then
we can do min(buf_len, slot->len)
----
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 22 Jun 2017, at 17:52, Dilip Kumar <dilipbalaut@gmail.com> wrote:
On Thu, Jun 22, 2017 at 7:18 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
Good point. I’ve attached a new version which issues a NOTICE on truncation
and also addresses all other comments so far in this thread. Thanks a lot for
the early patch reviews!cheers ./daniel
I have done an initial review of the patch. I have some comments/suggestions.
Thanks!
+int +GetCancelMessage(char **buffer, size_t buf_len) +{ + volatile BackendCancelShmemStruct *slot = MyCancelSlot; + int msg_length = 0; +Returned value of this function is never used, better to convert it to
just void.
You’re probably right, I was thinking that someone might be interested in
knowing about truncation when extracting the message but I can’t really think
of a callsite which wouldn’t just pass in a large enough buffer in the first
place.
+bool +HasCancelMessage(void) +{ + volatile BackendCancelShmemStruct *slot = MyCancelSlot;+/* + * 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 +GetCancelMessage(char **buffer, size_t buf_len)I think it will be good to merge these two function where
GetCancelMessage will first check whether it has the message or not
if it does then allocate the buffer of size slot->len and copy the
slot message to allocated buffer otherwise just return NULL.So it will look like "char *GetCancelMessage()”
It doesn’t seem like a good idea to perform memory allocation inside a spinlock
in a signalled backend, that would probably at least require an LWLock wouldn’t
it? It seems safer to leave memory management to the signalled backend but it
may be paranoia on my part.
+ SpinLockAcquire(&slot->mutex); + strlcpy(*buffer, (const char *) slot->message, buf_len);strlcpy(*buffer, (const char *) slot->message, slot->len);
Isn't it better to copy only upto slot->len, seems like it will always
be <= buf_len, and if not then
we can do min(buf_len, slot->len)
strlcpy(3) is defined as taking the size of the passed buffer and not the
copied string. Since we guarantee that slot->message is NUL terminated it
seems wise to stick to the API. Or did I misunderstand your comment?
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
+1
On Tue, Jun 20, 2017 at 8:54 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Unless you have a lot of users running psql manually, I don't see how
this is actually very useful or actionable. What would the user do with
the information? Hopefully your users already trust that you'd keep the
downtime to the minimum possible.
I think this feature would be useful for PgTerminator
(https://github.com/trustly/pgterminator)
a tool which automatically kills unprotected processes that could
potentially be the reason why
X number of protected important processes have been waiting for >Y seconds.
When I'm guilty of locking this in the production DB and get killed by
PgTerminator,
it would be nice to know the reason, e.g. that it was PgTerminator
that killed me
and what process I was blocking.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 06/26/2017 07:15 AM, Joel Jacobson wrote:
+1
On Tue, Jun 20, 2017 at 8:54 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Unless you have a lot of users running psql manually, I don't see how
this is actually very useful or actionable. What would the user do with
the information? Hopefully your users already trust that you'd keep the
downtime to the minimum possible.I think this feature would be useful for PgTerminator
(https://github.com/trustly/pgterminator)
a tool which automatically kills unprotected processes that could
potentially be the reason whyX number of protected important processes have been waiting for >Y seconds.
When I'm guilty of locking this in the production DB and get killed by
PgTerminator,
it would be nice to know the reason, e.g. that it was PgTerminator
that killed me
and what process I was blocking.
And not just the pid but literally "what".
jD
--
Command Prompt, Inc. || http://the.postgres.company/ || @cmdpromptinc
PostgreSQL Centered full stack support, consulting and development.
Advocate: @amplifypostgres || Learn: https://pgconf.us
***** Unless otherwise stated, opinions are my own. *****
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jun 23, 2017 at 1:48 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Good point. I’ve attached a new version which issues a NOTICE on truncation
and also addresses all other comments so far in this thread. Thanks a lot for
the early patch reviews!
FYI this no longer builds because commit 81c5e46c490e just stole your OIDs:
make[3]: Entering directory
`/home/travis/build/postgresql-cfbot/postgresql/src/backend/catalog'
cd ../../../src/include/catalog && '/usr/bin/perl' ./duplicate_oids
772
972
make[3]: *** [postgres.bki] Error 1
--
Thomas Munro
http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 02 Sep 2017, at 02:21, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Fri, Jun 23, 2017 at 1:48 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Good point. I’ve attached a new version which issues a NOTICE on truncation
and also addresses all other comments so far in this thread. Thanks a lot for
the early patch reviews!FYI this no longer builds because commit 81c5e46c490e just stole your OIDs:
make[3]: Entering directory
`/home/travis/build/postgresql-cfbot/postgresql/src/backend/catalog'
cd ../../../src/include/catalog && '/usr/bin/perl' ./duplicate_oids
772
972
make[3]: *** [postgres.bki] Error 1
Thanks, I hadn’t spotted that yet. Attached is an updated patch using new OIDs
to make it compile again.
cheers ./daniel
Attachments:
terminate_msg_v4.patchapplication/octet-stream; name=terminate_msg_v4.patchDownload
From ef476fe12f0d2c5255770f75bd7dd62831e37229 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 3 Sep 2017 22:43:16 +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
overloaded the existing as:
SELECT pg_terminate_backend(<pid> [, msg]);
SELECT pg_cancel_backend(<pid> [, msg]);
---
doc/src/sgml/func.sgml | 6 +-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 38 ++++++-
src/backend/utils/adt/misc.c | 60 ++++++++--
src/backend/utils/init/postinit.c | 2 +
src/backend/utils/misc/Makefile | 6 +-
src/backend/utils/misc/backend_cancel.c | 189 ++++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +
src/include/utils/backend_cancel.h | 25 +++++
9 files changed, 314 insertions(+), 19 deletions(-)
create mode 100644 src/backend/utils/misc/backend_cancel.c
create mode 100644 src/include/utils/backend_cancel.h
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 641b3b8f4e..ecde80aad2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18359,7 +18359,7 @@ SELECT set_config('log_statement_stats', 'off', false);
<tbody>
<row>
<entry>
- <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
+ <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</> [, <parameter>message</parameter> <type>text</>])</function></literal>
</entry>
<entry><type>boolean</type></entry>
<entry>Cancel a backend's current query. This is also allowed if the
@@ -18384,7 +18384,7 @@ SELECT set_config('log_statement_stats', 'off', false);
</row>
<row>
<entry>
- <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
+ <literal><function>pg_terminate_backend(<parameter>pid</parameter> <type>int</> [, <parameter>message</parameter> <type>text</>])</function></literal>
</entry>
<entry><type>boolean</type></entry>
<entry>Terminate a backend. This is also allowed if the calling role
@@ -18415,6 +18415,8 @@ 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.
</para>
<para>
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2d1ed143e0..f998ee82d3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -44,6 +44,7 @@
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
+#include "utils/backend_cancel.h"
#include "utils/backend_random.h"
#include "utils/snapmgr.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();
+ BackendCancelShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 8d3fecf6d6..248cd84ee5 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -69,6 +69,7 @@
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
+#include "utils/backend_cancel.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
@@ -2879,9 +2880,22 @@ ProcessInterrupts(void)
errdetail_recovery_conflict()));
}
else
- ereport(FATAL,
- (errcode(ERRCODE_ADMIN_SHUTDOWN),
- errmsg("terminating connection due to administrator command")));
+ {
+ if (HasCancelMessage())
+ {
+ char *buffer = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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)
{
@@ -2994,9 +3008,21 @@ ProcessInterrupts(void)
if (!DoingCommandRead)
{
LockErrorCleanup();
- ereport(ERROR,
- (errcode(ERRCODE_QUERY_CANCELED),
- errmsg("canceling statement due to user request")));
+
+ if (HasCancelMessage())
+ {
+ char *buffer = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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/adt/misc.c b/src/backend/utils/adt/misc.c
index 62341b84d1..38d4fd8ad9 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -41,6 +41,7 @@
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -216,7 +217,7 @@ current_query(PG_FUNCTION_ARGS)
#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);
@@ -248,6 +249,18 @@ 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)
+ {
+ int r;
+
+ r = SetBackendCancelMessage(pid, msg);
+
+ if (r != -1 && r != strlen(msg))
+ ereport(NOTICE,
+ (errmsg("message is too long and has been truncated")));
+ }
+
/*
* 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
@@ -278,10 +291,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,
@@ -296,16 +309,32 @@ 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 = PG_GETARG_INT32(0);
+ char *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,
@@ -317,7 +346,22 @@ 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 = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index eb6960d93f..c2abaf34ee 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -50,6 +50,7 @@
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/memutils.h"
@@ -740,6 +741,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
PerformAuthentication(MyProcPort);
InitializeSessionUserId(username, useroid);
am_superuser = superuser();
+ BackendCancelInit(MyBackendId);
}
/*
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index a53fcdf188..619c837e08 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -14,9 +14,9 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
-OBJS = backend_random.o guc.o help_config.o pg_config.o pg_controldata.o \
- pg_rusage.o ps_status.o queryenvironment.o rls.o sampling.o \
- superuser.o timeout.o tzparser.o
+OBJS = backend_cancel.o backend_random.o guc.o help_config.o pg_config.o \
+ pg_controldata.o pg_rusage.o ps_status.o queryenvironment.o rls.o \
+ sampling.o superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
# we can't substitute it into pg_config.h.
diff --git a/src/backend/utils/misc/backend_cancel.c b/src/backend/utils/misc/backend_cancel.c
new file mode 100644
index 0000000000..194554eab2
--- /dev/null
+++ b/src/backend/utils/misc/backend_cancel.c
@@ -0,0 +1,189 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.c
+ * Backend cancellation messaging
+ *
+ *
+ * Module for supporting passing a user defined message to a cancelled,
+ * or terminated, backend from the user/administrator.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/backend_cancel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/spin.h"
+#include "storage/shmem.h"
+#include "utils/backend_cancel.h"
+
+/*
+ * 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);
+
+Size
+CancelBackendMsgShmemSize(void)
+{
+ return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+void
+BackendCancelShmemInit(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));
+ }
+}
+
+void
+BackendCancelInit(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));
+}
+
+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)
+ slot->message[0] = '\0';
+
+ slot->len = 0;
+ slot->pid = 0;
+}
+
+/*
+ * Sets a cancellation message for the backend with the specified pid, and
+ * returns the length of message actually created. If the returned length
+ * is less than the length of the message parameter, truncation has occurred.
+ * If the backend wasn't found and no message was set, -1 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, char *message)
+{
+ BackendCancelShmemStruct *slot;
+ int i;
+ int message_len;
+
+ if (!message)
+ return 0;
+
+ for (i = 0; i < MaxBackends; i++)
+ {
+ slot = &BackendCancelSlots[i];
+
+ if (slot->pid != 0 && slot->pid == backend)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (slot->pid != backend)
+ {
+ SpinLockRelease(&slot->mutex);
+ goto error;
+ }
+
+ strlcpy(slot->message, message, sizeof(slot->message));
+ slot->len = strlen(slot->message);
+ message_len = slot->len;
+ SpinLockRelease(&slot->mutex);
+
+ return message_len;
+ }
+ }
+
+error:
+
+ elog(LOG, "Cancellation message requested for missing backend %d by %d",
+ (int) backend, MyProcPid);
+
+ return -1;
+}
+
+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
+GetCancelMessage(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/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d820b56aa1..ceeb85e13d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3247,8 +3247,12 @@ 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 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 t 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 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 t 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 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 f t f v r 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
diff --git a/src/include/utils/backend_cancel.h b/src/include/utils/backend_cancel.h
new file mode 100644
index 0000000000..7f210553d6
--- /dev/null
+++ b/src/include/utils/backend_cancel.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.h
+ * Declarations for backend cancellation messaging
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ *
+ * src/include/utils/backend_cancel.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef BACKEND_CANCEL_H
+#define BACKEND_CANCEL_H
+
+#define MAX_CANCEL_MSG 128
+
+extern Size CancelBackendMsgShmemSize(void);
+extern void BackendCancelShmemInit(void);
+extern void BackendCancelInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int GetCancelMessage(char **msg, size_t len);
+
+#endif /* BACKEND_CANCEL_H */
--
2.14.1.145.gb3622a4ee
On Sun, 3 Sep 2017 22:47:10 +0200
Daniel Gustafsson <daniel@yesql.se> wrote:
I have reviewed your latest patch.
I can apply this to the master branch and build this successfully,
and the behavior is as expected.
However, here are some comments and suggestions.
135 + char *buffer = palloc0(MAX_CANCEL_MSG);
136 +
137 + GetCancelMessage(&buffer, MAX_CANCEL_MSG);
138 + ereport(ERROR,
139 + (errcode(ERRCODE_QUERY_CANCELED),
140 + errmsg("canceling statement due to user request: \"%s\"",
141 + buffer)));
The memory for buffer is allocated here, but I think we can do this
in GetCancelMessage. Since the size of allocated memory is fixed
to MAX_CANCEL_MSG, it isn't neccesary to pass this to the function.
In addition, how about returning the message as the return value?
That is, we can define GetCancelMessage as following;
char * GetCancelMessage(void)
180 + r = SetBackendCancelMessage(pid, msg);
181 +
182 + if (r != -1 && r != strlen(msg))
183 + ereport(NOTICE,
184 + (errmsg("message is too long and has been truncated")));
185 + }
We can this error handling in SetBackendCancelMessage. I think this is better
because the truncation of message is done in this function. In addition,
we should use pg_mbcliplen for this truncation as done in truncate_identifier.
Else, multibyte character boundary is broken, and noises can slip in log
messages.
235 - int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
236 + int r = pg_signal_backend(pid, SIGTERM, msg);
This line includes an unnecessary indentation change.
411 + * returns the length of message actually created. If the returned length
"the length of message" might be "the length of the message"
413 + * If the backend wasn't found and no message was set, -1 is returned. If two
This comment is incorrect since this function returns 0 when no message was set.
440 + strlcpy(slot->message, message, sizeof(slot->message));
441 + slot->len = strlen(slot->message);
442 + message_len = slot->len;
443 + SpinLockRelease(&slot->mutex);
444 +
445 + return message_len;
This can return slot->len directly and the variable message_len is
unnecessary. However, if we handle the "too long message" error
in this function as suggested above, this does not have to
return anything.
Regards,
--
Yugo Nagata <nagata@sraoss.co.jp>
On 02 Sep 2017, at 02:21, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Fri, Jun 23, 2017 at 1:48 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Good point. I’ve attached a new version which issues a NOTICE on truncation
and also addresses all other comments so far in this thread. Thanks a lot for
the early patch reviews!"FYI this no longer builds because commit 81c5e46c490e just stole your OIDs:
make[3]: Entering directory
`/home/travis/build/postgresql-cfbot/postgresql/src/backend/catalog'
cd ../../../src/include/catalog && '/usr/bin/perl' ./duplicate_oids
772
972
make[3]: *** [postgres.bki] Error 1Thanks, I hadn’t spotted that yet. Attached is an updated patch using new OIDs
to make it compile again.cheers ./daniel
--
Yugo Nagata <nagata@sraoss.co.jp>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 Sep 2017, at 14:55, Yugo Nagata <nagata@sraoss.co.jp> wrote:
On Sun, 3 Sep 2017 22:47:10 +0200
Daniel Gustafsson <daniel@yesql.se> wrote:I have reviewed your latest patch.
I can apply this to the master branch and build this successfully,
and the behavior is as expected.However, here are some comments and suggestions.
Thanks for the review! As I haven’t had time to address the comments for a new
versin of this patch, I’m closing this as Returned with Feedback and will
re-submit for the next commitfest.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 Sep 2017, at 14:55, Yugo Nagata <nagata@sraoss.co.jp> wrote:
On Sun, 3 Sep 2017 22:47:10 +0200
Daniel Gustafsson <daniel@yesql.se> wrote:I have reviewed your latest patch.
Thanks!
I can apply this to the master branch and build this successfully,
and the behavior is as expected.However, here are some comments and suggestions.
135 + char *buffer = palloc0(MAX_CANCEL_MSG);
136 +
137 + GetCancelMessage(&buffer, MAX_CANCEL_MSG);
138 + ereport(ERROR,
139 + (errcode(ERRCODE_QUERY_CANCELED),
140 + errmsg("canceling statement due to user request: \"%s\"",
141 + buffer)));The memory for buffer is allocated here, but I think we can do this
in GetCancelMessage. Since the size of allocated memory is fixed
to MAX_CANCEL_MSG, it isn't neccesary to pass this to the function.
In addition, how about returning the message as the return value?
That is, we can define GetCancelMessage as following;char * GetCancelMessage(void)
I agree that it would be a much neater API, but it would mean pallocing inside
the spinlock wouldn’t it? That would be a no-no.
180 + r = SetBackendCancelMessage(pid, msg);
181 +
182 + if (r != -1 && r != strlen(msg))
183 + ereport(NOTICE,
184 + (errmsg("message is too long and has been truncated")));
185 + }We can this error handling in SetBackendCancelMessage. I think this is better
because the truncation of message is done in this function. In addition,
we should use pg_mbcliplen for this truncation as done in truncate_identifier.
Else, multibyte character boundary is broken, and noises can slip in log
messages.
Agreed on both points. I did however leave the returnvalue as before even
though it’s not read in this coding.
235 - int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
236 + int r = pg_signal_backend(pid, SIGTERM, msg);This line includes an unnecessary indentation change.
That’s embarrasing, fixed.
411 + * returns the length of message actually created. If the returned length
"the length of message" might be "the length of the message”
Fixed.
413 + * If the backend wasn't found and no message was set, -1 is returned. If two
This comment is incorrect since this function returns 0 when no message was set.
Fixed.
440 + strlcpy(slot->message, message, sizeof(slot->message));
441 + slot->len = strlen(slot->message);
442 + message_len = slot->len;
443 + SpinLockRelease(&slot->mutex);
444 +
445 + return message_len;This can return slot->len directly and the variable message_len is
unnecessary. However, if we handle the "too long message" error
in this function as suggested above, this does not have to
return anything.
That would mean returning a variable which was set while holding the lock, when
the lock has been released. That doesn’t seem like a good idea, even though it
may be of more theoretical importance here.
Attached patch is rebased over HEAD and addresses the above review comments.
Adding to the next commitfest as it was returned in a previous one.
cheers ./daniel
Attachments:
terminate_msg_v5.patchapplication/octet-stream; name=terminate_msg_v5.patchDownload
From d88202f36d66d8bfe88fa34967ed23a2d2eade4a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 24 Jan 2018 12:22:20 +0100
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
overloaded the existing as:
SELECT pg_terminate_backend(<pid> [, msg]);
SELECT pg_cancel_backend(<pid> [, msg]);
---
doc/src/sgml/func.sgml | 6 +-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 38 ++++++-
src/backend/utils/adt/misc.c | 52 +++++++--
src/backend/utils/init/postinit.c | 2 +
src/backend/utils/misc/Makefile | 6 +-
src/backend/utils/misc/backend_cancel.c | 195 ++++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +
src/include/utils/backend_cancel.h | 25 ++++
9 files changed, 312 insertions(+), 19 deletions(-)
create mode 100644 src/backend/utils/misc/backend_cancel.c
create mode 100644 src/include/utils/backend_cancel.h
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 487c7ff750..91f58eac55 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18381,7 +18381,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
@@ -18406,7 +18406,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
@@ -18437,6 +18437,8 @@ 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.
</para>
<para>
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..4e21d0f47b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -44,6 +44,7 @@
#include "storage/procsignal.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
+#include "utils/backend_cancel.h"
#include "utils/backend_random.h"
#include "utils/snapmgr.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();
+ BackendCancelShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ddc3ec860a..165ea0f906 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -69,6 +69,7 @@
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
+#include "utils/backend_cancel.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.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 = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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 = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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/adt/misc.c b/src/backend/utils/adt/misc.c
index 2e1e020c4b..30ac42fd99 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -42,6 +42,7 @@
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -217,7 +218,7 @@ current_query(PG_FUNCTION_ARGS)
#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);
@@ -249,6 +250,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
@@ -279,10 +284,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,
@@ -297,16 +302,32 @@ 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 = PG_GETARG_INT32(0);
+ char *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,
@@ -318,7 +339,22 @@ 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 = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
}
/*
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index f9b330998d..026419d23d 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -51,6 +51,7 @@
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
+#include "utils/backend_cancel.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/memutils.h"
@@ -741,6 +742,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
PerformAuthentication(MyProcPort);
InitializeSessionUserId(username, useroid);
am_superuser = superuser();
+ BackendCancelInit(MyBackendId);
}
/*
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index a53fcdf188..619c837e08 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -14,9 +14,9 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
-OBJS = backend_random.o guc.o help_config.o pg_config.o pg_controldata.o \
- pg_rusage.o ps_status.o queryenvironment.o rls.o sampling.o \
- superuser.o timeout.o tzparser.o
+OBJS = backend_cancel.o backend_random.o guc.o help_config.o pg_config.o \
+ pg_controldata.o pg_rusage.o ps_status.o queryenvironment.o rls.o \
+ sampling.o superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
# we can't substitute it into pg_config.h.
diff --git a/src/backend/utils/misc/backend_cancel.c b/src/backend/utils/misc/backend_cancel.c
new file mode 100644
index 0000000000..66261893cb
--- /dev/null
+++ b/src/backend/utils/misc/backend_cancel.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.c
+ * Backend cancellation messaging
+ *
+ *
+ * Module for supporting passing a user defined message to a cancelled,
+ * or terminated, backend from the user/administrator.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/backend_cancel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/spin.h"
+#include "storage/shmem.h"
+#include "utils/backend_cancel.h"
+
+/*
+ * 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);
+
+Size
+CancelBackendMsgShmemSize(void)
+{
+ return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+void
+BackendCancelShmemInit(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));
+ }
+}
+
+void
+BackendCancelInit(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));
+}
+
+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)
+ slot->message[0] = '\0';
+
+ 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, char *message)
+{
+ BackendCancelShmemStruct *slot;
+ int i;
+ int len;
+
+ if (!message)
+ return 0;
+
+ for (i = 0; i < MaxBackends; i++)
+ {
+ slot = &BackendCancelSlots[i];
+
+ if (slot->pid != 0 && slot->pid == backend)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (slot->pid != backend)
+ {
+ 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, MyProcPid);
+
+ return -1;
+}
+
+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
+GetCancelMessage(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/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f01648c961..9f6d4bda6b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3254,8 +3254,12 @@ 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 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 t 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 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 t 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 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 f t f v r 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_stop_backup _null_ _null_ _null_ ));
diff --git a/src/include/utils/backend_cancel.h b/src/include/utils/backend_cancel.h
new file mode 100644
index 0000000000..061f0fab18
--- /dev/null
+++ b/src/include/utils/backend_cancel.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_cancel.h
+ * Declarations for backend cancellation messaging
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/utils/backend_cancel.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef BACKEND_CANCEL_H
+#define BACKEND_CANCEL_H
+
+#define MAX_CANCEL_MSG 128
+
+extern Size CancelBackendMsgShmemSize(void);
+extern void BackendCancelShmemInit(void);
+extern void BackendCancelInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int GetCancelMessage(char **msg, size_t len);
+
+#endif /* BACKEND_CANCEL_H */
--
2.14.1.145.gb3622a4ee
A quick suggestion from a passer-by -- would you put the new code in
src/backend/storage/ipc/backend_signal.c instead? Sounds like a better
place than utils/misc/; and "signal" is more general in nature than just
"cancel". A bunch of stuff from utils/adt/misc.c (???) can migrate to
the new file -- everything from line 201 to 362 at least, that is:
pg_signal_backend
pg_cancel_backend
pg_terminate_backend
pg_reload_conf
pg_rotate_logfile
Maybe have two patches, 0001 creates the files moving the contents over,
then 0002 adds your new stuff on top.
/me wonders if there's anything that would suggest to make this
extensive to processes other than backends ...
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Jan 24, 2018 at 12:45:48PM -0300, Alvaro Herrera wrote:
/me wonders if there's anything that would suggest to make this
extensive to processes other than backends ...
That's a good thought. Now ProcessInterrupts() is not used by
non-backend processes. For example the WAL receiver has its own logic to
handle interrupts but I guess that we could have an interface which can
be plugged in for any processes, which is by default enabled for
bgworkers.
--
Michael
On 24 Jan 2018, at 16:45, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
A quick suggestion from a passer-by -- would you put the new code in
src/backend/storage/ipc/backend_signal.c instead? Sounds like a better
place than utils/misc/; and "signal" is more general in nature than just
"cancel". A bunch of stuff from utils/adt/misc.c (???) can migrate to
the new file -- everything from line 201 to 362 at least, that is:pg_signal_backend
pg_cancel_backend
pg_terminate_backend
pg_reload_conf
pg_rotate_logfile
+1, this makes a lot of sense. When writing this I didn’t find a good fit
anywhere so I modelled it after utils/misc/backend_random.c, not because it was
a good fit but it was the least bad one I could come up with. This seems a lot
cleaner.
Maybe have two patches, 0001 creates the files moving the contents over,
then 0002 adds your new stuff on top.
The two attached patches implements this.
/me wonders if there's anything that would suggest to make this
extensive to processes other than backends ...
Perhaps, do you have an API in mind?
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v6.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v6.patchDownload
From 9ab870d9c112315b15415732cc1a6aa0d77eacb6 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 25 Jan 2018 23:00:32 +0100
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 190 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 168 ---------------------------
3 files changed, 193 insertions(+), 171 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..603b229149
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 2e1e020c4b..5bfe789c0c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,9 +33,6 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
@@ -198,169 +193,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v6.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v6.patchDownload
From 770d5b785bb3b142507cedddd4aa61617b0195fa 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 | 6 +-
src/backend/storage/ipc/backend_signal.c | 237 +++++++++++++++++++++++++++++--
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 | 4 +
src/include/storage/backend_signal.h | 25 ++++
7 files changed, 299 insertions(+), 16 deletions(-)
create mode 100644 src/include/storage/backend_signal.h
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 487c7ff750..91f58eac55 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18381,7 +18381,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
@@ -18406,7 +18406,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
@@ -18437,6 +18437,8 @@ 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.
</para>
<para>
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 603b229149..e63af3eaac 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,13 +18,34 @@
#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.
*
@@ -44,7 +65,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 +97,14 @@ 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);
+
+ /* 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 +135,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 +153,32 @@ 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 = PG_GETARG_INT32(0);
+ char *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 +190,22 @@ 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 = PG_GETARG_INT32(0);
+ char *msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+ PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));
}
/*
@@ -188,3 +248,164 @@ 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.
+ */
+Size
+CancelBackendMsgShmemSize(void)
+{
+ return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+void
+BackendCancelShmemInit(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));
+ }
+}
+
+void
+BackendCancelInit(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));
+}
+
+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)
+ slot->message[0] = '\0';
+
+ 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, char *message)
+{
+ BackendCancelShmemStruct *slot;
+ int i;
+ int len;
+
+ if (!message)
+ return 0;
+
+ for (i = 0; i < MaxBackends; i++)
+ {
+ slot = &BackendCancelSlots[i];
+
+ if (slot->pid != 0 && slot->pid == backend)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (slot->pid != backend)
+ {
+ 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, MyProcPid);
+
+ return -1;
+}
+
+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
+GetCancelMessage(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..156df36234 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();
+ BackendCancelShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ddc3ec860a..0e36cb1a3f 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 = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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 = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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 f9b330998d..d6aca4d5d3 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"
@@ -741,6 +742,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
PerformAuthentication(MyProcPort);
InitializeSessionUserId(username, useroid);
am_superuser = superuser();
+ BackendCancelInit(MyBackendId);
}
/*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f01648c961..9f6d4bda6b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3254,8 +3254,12 @@ 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 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 t 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 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 t 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 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 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..f232813995
--- /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 BackendCancelShmemInit(void);
+extern void BackendCancelInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int GetCancelMessage(char **msg, size_t len);
+
+#endif /* BACKEND_SIGNAL_H */
--
2.14.1.145.gb3622a4ee
On 26 Jan 2018, at 00:05, Daniel Gustafsson <daniel@yesql.se> wrote:
On 24 Jan 2018, at 16:45, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:
Maybe have two patches, 0001 creates the files moving the contents over,
then 0002 adds your new stuff on top.The two attached patches implements this.
Attached are rebased patches to cope with the recent pgproc changes. I also
made the function cope with NULL messages, not because it’s a sensible value
but I can see this function being fed from a sub-SELECT which could return
NULL.
As per the previous mail, 0001 refactors the signal code according to Alvaros
suggestion and 0002 implements $subject on top of the refactoring.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v7.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v7.patchDownload
From 75fe95d36ff9e54f9e7f5a4c85b8c0e9e83ca7ed Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 5 Mar 2018 12:16:16 +0100
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 190 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 168 ---------------------------
3 files changed, 193 insertions(+), 171 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..603b229149
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 2e1e020c4b..5bfe789c0c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,9 +33,6 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
@@ -198,169 +193,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v7.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v7.patchDownload
From 0935706d606e807d8f2109e6807791dc7fa62292 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 | 6 +-
src/backend/storage/ipc/backend_signal.c | 251 ++++++++++++++++++++++++++++++-
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 +++
7 files changed, 314 insertions(+), 16 deletions(-)
create mode 100644 src/include/storage/backend_signal.h
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2f59af25a6..337d4de62e 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,8 @@ 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.
</para>
<para>
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 603b229149..e653bcfd30 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,13 +18,34 @@
#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.
*
@@ -44,7 +65,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 +97,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 +131,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 +149,41 @@ 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))
+ 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_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,164 @@ 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.
+ */
+Size
+CancelBackendMsgShmemSize(void)
+{
+ return MaxBackends * sizeof(BackendCancelShmemStruct);
+}
+
+void
+BackendCancelShmemInit(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));
+ }
+}
+
+void
+BackendCancelInit(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));
+}
+
+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)
+ slot->message[0] = '\0';
+
+ 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, char *message)
+{
+ BackendCancelShmemStruct *slot;
+ int i;
+ int len;
+
+ if (!message)
+ return 0;
+
+ for (i = 0; i < MaxBackends; i++)
+ {
+ slot = &BackendCancelSlots[i];
+
+ if (slot->pid != 0 && slot->pid == backend)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (slot->pid != backend)
+ {
+ 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, MyProcPid);
+
+ return -1;
+}
+
+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
+GetCancelMessage(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..156df36234 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();
+ BackendCancelShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 6dc2095b9a..bccea2f480 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 = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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 = palloc0(MAX_CANCEL_MSG);
+
+ GetCancelMessage(&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 484628987f..51cdb0d89b 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();
+ BackendCancelInit(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..f232813995
--- /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 BackendCancelShmemInit(void);
+extern void BackendCancelInit(int backend_id);
+
+extern int SetBackendCancelMessage(pid_t backend, char *message);
+extern bool HasCancelMessage(void);
+extern int GetCancelMessage(char **msg, size_t len);
+
+#endif /* BACKEND_SIGNAL_H */
--
2.14.1.145.gb3622a4ee
Hi,
I reviewed the patch. Here are my notes:
I can confirm that the patches apply and pass the tests as of 03/19/2018 @
11:00am (UTC).
I didn't test whether the docs compile or look good.
I have briefly tested and can confirm that the patch does what is intended.
Here are my comments about the patch:
The patch does not add new tests (maybe isolation tests) for the new
feature. Are there any opportunities to have tests for confirming the new
behavior? At least some regression tests like the following:
SELECT pg_cancel_backend(pg_backend_pid());
ERROR: 57014: canceling statement due to user request
SELECT pg_cancel_backend(pg_backend_pid(), 'message');
ERROR: 57014: canceling statement due to user request: "message"
Not introduced with this patch, but the spacing between the functions is
not consistent in src/backend/storage/ipc/backend_signal.c. There are 2
newlines after some functions (like pg_cancel_backend_msg) and 1 newline
after others (like pg_terminate_backend_msg). It would be nice to fix these
while refactoring.
I also thought about whether the patch should allow the message to be
completely overwritten, instead of appending to the existing one and I
think it is fine. Also, adding the admin message to the HINT or DETAIL part
of the error message would make sense but these are ignored by some clients
so it is fine this way.
Another thing is that, in a similar manner, we could allow changing the
error code which might be useful for extensions. For example, Citus could
use it to cancel remote backends when it detects a distributed deadlock and
changes the error code to something retryable while doing so. For
reference, see the hack that Citus is currently using:
https://github.com/citusdata/citus/blob/81cbb7c223f2eb9cfa8903f1d28869b6f972ded1/src/backend/distributed/shared_library_init.c#L237
+ If the optional <literal>message</literal> parameter is set, the text
+ will be appended to the error message returned to the signalled
backend.
I think providing more detail would be useful. For example we can provide
an example about how the error message looks like in its final form or what
will happen if the message is too long.
-pg_signal_backend(int pid, int sig)
+pg_signal_backend(int pid, int sig, char *msg)
The new parameter (msg) is not mentioned in the function header comment.
This applies to pg_signal_backend, pg_cancel_backend_internal,
pg_terminate_backend_internal functions.
+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))
+ 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_cancel_backend_internal(pid, msg));
+}
The first function seem redundant here because the second one covers all
the cases.
+ memset(slot->message, '\0', sizeof(slot->message));
SetBackendCancelMessage uses memset while BackendCancelShmemInit uses
MemSet. Not a big deal but it would be nice to be consistent and use
postgres macro versions for such calls.
+int
+SetBackendCancelMessage(pid_t backend, char *message)
+{
+ BackendCancelShmemStruct *slot;
The variable "slot" is declared outside of the foor loop but only used in
the inside, therefore it would be nicer to declare it inside the loop.
+ BackendCancelInit(MyBackendId);
Is the "normal multiuser case" the best place to initialize cancellation?
For example, can't we cancel background workers? If this is the right
place, maybe we should justify why this is the best place to initialize
backend cancellation memory part with a comment.
+ char *buffer = palloc0(MAX_CANCEL_MSG);
Why not use a static array like char[MAX_CANCEL_MSG], instead of pallocing?
+/*
+ * 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
+GetCancelMessage(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;
+}
We never change what the buffer points to, therefore is it necessary to
declare `buffer` as char** instead of char*?
+extern int SetBackendCancelMessage(pid_t backend, char *message);
Maybe rename backend to something like backend_pid.
+/*
+ * 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
+GetCancelMessage(char **buffer, size_t buf_len)
I think a function named GetStuff should not clear after getting the stuff.
Therefore either the function should be named something like
ConsumeCancelMessage or we should separate the "get" and "clear" concerns
to different functions.
+/*
+ * 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.
+ */
I don't think providing a single header comment for the functions below
this (CancelBackendMsgShmemSize, BackendCancelShmemInit, BackendCancelInit,
CleanupCancelBackend) is enough. More detailed comments about what each
function does would be useful.
+BackendCancelInit(int backend_id)
It took me some time to get used to the function names. The confusion was
about whether the function name tells "cancel the initialization process"
or "initialize backend cancellation memory".
--- /dev/null
+++ b/src/include/storage/backend_signal.h
First, I thought that this split should be done in the first patch, but I
realized that everything that has been moved from
src/backend/utils/adt/misc.c to src/backend/storage/ipc/backend_signal.c
has been declared in src/backend/utils/fmgrprotos.h
Best,
Eren
On Tue, Mar 6, 2018 at 12:20 AM Daniel Gustafsson <daniel@yesql.se> wrote:
Show quoted text
On 26 Jan 2018, at 00:05, Daniel Gustafsson <daniel@yesql.se> wrote:
On 24 Jan 2018, at 16:45, Alvaro Herrera <alvherre@alvh.no-ip.org>
wrote:
Maybe have two patches, 0001 creates the files moving the contents over,
then 0002 adds your new stuff on top.The two attached patches implements this.
Attached are rebased patches to cope with the recent pgproc changes. I
also
made the function cope with NULL messages, not because it’s a sensible
value
but I can see this function being fed from a sub-SELECT which could return
NULL.As per the previous mail, 0001 refactors the signal code according to
Alvaros
suggestion and 0002 implements $subject on top of the refactoring.cheers ./daniel
Re: Eren Başak 2018-03-20 <CAFNTstPcstV8Brqkg00a84V72b_FfnLinhu2C2Top+QssmwFhg@mail.gmail.com>
Another thing is that, in a similar manner, we could allow changing the
error code which might be useful for extensions. For example, Citus could
use it to cancel remote backends when it detects a distributed deadlock and
changes the error code to something retryable while doing so.
Another useful thing to do on top of this patch would be to include
messages when the termination comes from postgres itself, e.g. on a
server shutdown. Possibly, the message for pg_terminate_backend() itself could
say that someone invoke that, unless overridden.
FATAL: 57P01: terminating connection due to administrator command: server shutting down
FATAL: 57P01: terminating connection due to administrator command: restarting because of a crash of another server process
FATAL: 57P01: terminating connection due to administrator command: terminated by pg_terminate_backend()
Christoph
On 20 Mar 2018, at 12:12, Eren Başak <eren@citusdata.com> wrote:
I reviewed the patch. Here are my notes:
Thanks!
The patch does not add new tests (maybe isolation tests) for the new feature. Are there any opportunities to have tests for confirming the new behavior?
Good point, not sure why I’ve forgotten to add this. No existing suite seemed
to match well, so I added a new one for system administration functions like
this one.
Not introduced with this patch, but the spacing between the functions is not consistent in src/backend/storage/ipc/backend_signal.c. There are 2 newlines after some functions (like pg_cancel_backend_msg) and 1 newline after others (like pg_terminate_backend_msg). It would be nice to fix these while refactoring.
Fixed.
Another thing is that, in a similar manner, we could allow changing the error code which might be useful for extensions. For example, Citus could use it to cancel remote backends when it detects a distributed deadlock and changes the error code to something retryable while doing so. For reference, see the hack that Citus is currently using:
https://github.com/citusdata/citus/blob/81cbb7c223f2eb9cfa8903f1d28869b6f972ded1/src/backend/distributed/shared_library_init.c#L237
In 20170620200134.ubnv4sked5seolyk@alap3.anarazel.de, Andres suggested the same
thing. I don’t disagree, but I’m also not sure what the API would look like so
I’d prefer to address that in a follow-up patch should this one get accepted.
+ If the optional <literal>message</literal> parameter is set, the text + will be appended to the error message returned to the signalled backend.I think providing more detail would be useful. For example we can provide an example about how the error message looks like in its final form or what will happen if the message is too long.
Expanded the documentation a little and added a (contrived) example.
-pg_signal_backend(int pid, int sig) +pg_signal_backend(int pid, int sig, char *msg)The new parameter (msg) is not mentioned in the function header comment. This applies to pg_signal_backend, pg_cancel_backend_internal, pg_terminate_backend_internal functions.
Fixed.
+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)) + 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_cancel_backend_internal(pid, msg)); +}The first function seem redundant here because the second one covers all the cases.
pg_cancel_backend() is defined proisstrict, while pg_cancel_backend_msg() is
not. I think we must be able to perform the cancellation if the message is
NULL, so made it two functions.
Thinking more about this, I think pg_cancel_backend_msg() should handle a NULL
pid in the same way as pg_cancel_backend() so fixed that as well.
+ memset(slot->message, '\0', sizeof(slot->message));
SetBackendCancelMessage uses memset while BackendCancelShmemInit uses MemSet. Not a big deal but it would be nice to be consistent and use postgres macro versions for such calls.
Good point, moved to MemSet() for both.
+int +SetBackendCancelMessage(pid_t backend, char *message) +{ + BackendCancelShmemStruct *slot;The variable "slot" is declared outside of the foor loop but only used in the inside, therefore it would be nicer to declare it inside the loop.
Moved to inside the loop.
+ BackendCancelInit(MyBackendId);
Is the "normal multiuser case" the best place to initialize cancellation? For example, can't we cancel background workers? If this is the right place, maybe we should justify why this is the best place to initialize backend cancellation memory part with a comment.
I didn’t envision this being in another setting than the multiuser case, but
it’s been clear throughout the thread that others have had good ideas around
extended uses of this. Background workers can still be terminated/canceled,
this only affects setting the message, and that is to me a multiuser feature.
Renamed the functions BackendCancelMessage*() for clarity.
+ char *buffer = palloc0(MAX_CANCEL_MSG);
Why not use a static array like char[MAX_CANCEL_MSG], instead of pallocing?
No specific reason, changed.
+/* + * 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 +GetCancelMessage(char **buffer, size_t buf_len)
...
We never change what the buffer points to, therefore is it necessary to declare `buffer` as char** instead of char*?
Fixed
+extern int SetBackendCancelMessage(pid_t backend, char *message);
Maybe rename backend to something like backend_pid.
Makes sense, changed.
I think a function named GetStuff should not clear after getting the stuff. Therefore either the function should be named something like ConsumeCancelMessage or we should separate the "get" and "clear" concerns to different functions.
Good point, changed to ConsumeCancelMessage()
I don't think providing a single header comment for the functions below this (CancelBackendMsgShmemSize, BackendCancelShmemInit, BackendCancelInit, CleanupCancelBackend) is enough. More detailed comments about what each function does would be useful.
Comments expanded.
It took me some time to get used to the function names. The confusion was about whether the function name tells "cancel the initialization process" or "initialize backend cancellation memory”.
Naming is hard. I’m not wed to any name used, and am open to any suggestions
that can improve code clarity.
Attached patches are rebased over HEAD with all of the above addressed.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v8.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v8.patchDownload
From e96bd355fc747d174b6633d911cc781a7da32f8a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 5 Mar 2018 12:16:16 +0100
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 190 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 168 ---------------------------
3 files changed, 193 insertions(+), 171 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..603b229149
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 2e1e020c4b..5bfe789c0c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,9 +33,6 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
@@ -198,169 +193,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v8.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v8.patchDownload
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
Hi,
I have reviewed the v8 patches.
I can confirm that the patches apply and pass the tests as of 03/27/2018
11:00am (UTC).
I didn't test whether the docs compile but they look good.
I have briefly tested the patch again and can confirm that the patch does
what is intended.
The v8 patches address almost all of my review notes on v7 patch, thanks
Daniel for that.
I have some more questions, notes and views on the patch but have no strong
opinions at this moment. So I am fine with whatever decision is made:
I see you have removed extra newlines from backend_signal.c. However, I
realized that there is still one extra newline after pg_reload_conf.
In 20170620200134.ubnv4sked5seolyk@alap3.anarazel.de, Andres suggested
the same
thing. I don’t disagree, but I’m also not sure what the API would look
like so
I’d prefer to address that in a follow-up patch should this one get
accepted.
That's fine for me, although I would prefer to get the ability to specify
the error code sooner than later. My main question is that I am not sure
whether the community prefers to ship two similar (in use case) changes on
an API in a single patch or fine with two patches. Can that be a problem if
the subsequent patch is released in a different postgres version? I am not
sure.
pg_cancel_backend() is defined proisstrict, while pg_cancel_backend_msg()
is
not. I think we must be able to perform the cancellation if the message
is
NULL, so made it two functions.
I agree that we should preserve the old usage as well. What I don't
understand is that, can't we remove proisstrict from pg_cancel_backend and copy
the body of pg_cancel_backend_msg into pg_cancel_backend. This way, I think
we can accomplish the same thing without introducing two new functions.
+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")));
pg_terminate_backend_msg errors out if the pid is null but
pg_cancel_backend_msg returns null and I think we should have consistency
between these two in this regard.
--
Best,
Eren
On 27 Mar 2018, at 13:50, Eren Başak <eren@citusdata.com> wrote:
pg_cancel_backend() is defined proisstrict, while pg_cancel_backend_msg() is
not. I think we must be able to perform the cancellation if the message is
NULL, so made it two functions.I agree that we should preserve the old usage as well. What I don't understand is that, can't we remove proisstrict from pg_cancel_backend and copy the body of pg_cancel_backend_msg into pg_cancel_backend. This way, I think we can accomplish the same thing without introducing two new functions.
If we do that, wouldn’t SELECT pg_cancel_backend(NULL) fail unless NULL is
explicitly casted to integer? Also, I think that would cause make check to
fail the opr_sanity suite on the “multiple uses of the same internal function”
test. Maybe I’m misunderstanding your point here?
pg_terminate_backend_msg errors out if the pid is null but pg_cancel_backend_msg returns null and I think we should have consistency between these two in this regard.
Absolutely, that was an oversight in the v8 patch, they should both handle NULL
in the same way.
Whitespace and null handling fixed, as well as rebased over HEAD.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v9.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v9.patchDownload
From 86ea7bc27cbcdc111eea479a50704e941a82683a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 5 Mar 2018 12:16:16 +0100
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 190 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 168 ---------------------------
3 files changed, 193 insertions(+), 171 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..603b229149
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 2e1e020c4b..5bfe789c0c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,9 +33,6 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
@@ -198,169 +193,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v9.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v9.patchDownload
From 3bebd9717cfd18c7649084a89928dfa1c43b8a25 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, 377 insertions(+), 21 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 9d1772f349..7702049b5e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18461,7 +18461,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
@@ -18486,7 +18486,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
@@ -18517,6 +18517,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..895e1be775 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,30 @@ 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))
+ 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_terminate_backend_internal(pid, msg));
}
/*
@@ -167,7 +240,6 @@ pg_reload_conf(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(true);
}
-
/*
* Rotate log file
*
@@ -188,3 +260,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 6fc1cc272b..9aee117234 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -61,6 +61,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"
@@ -2919,9 +2920,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)
{
@@ -3032,9 +3046,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 ec50afcdf0..5a084e44db 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3302,8 +3302,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 d858a0e7db..ee644cc0b5 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..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
The documentation change suggests the error will be
ERROR: canceling statement due to user request: "Cancellation message text"
Why the quotes?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 05 Apr 2018, at 23:16, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
The documentation change suggests the error will be
ERROR: canceling statement due to user request: "Cancellation message text"
Why the quotes?
It seemed like a good idea at the time to indicate which part was submitted by
the user, but looking at it now the colon sign is a pretty clear indicator
already.
cheers ./daniel
On Thu, Apr 05, 2018 at 11:33:57PM +0200, Daniel Gustafsson wrote:
It seemed like a good idea at the time to indicate which part was submitted by
the user, but looking at it now the colon sign is a pretty clear indicator
already.
Dropping the quotes is more consistent with other error messages. We
don't bother about quoting things for example with system-related errors
generated by strerror().
--
Michael
On 06 Apr 2018, at 04:49, Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Apr 05, 2018 at 11:33:57PM +0200, Daniel Gustafsson wrote:
It seemed like a good idea at the time to indicate which part was submitted by
the user, but looking at it now the colon sign is a pretty clear indicator
already.Dropping the quotes is more consistent with other error messages. We
don't bother about quoting things for example with system-related errors
generated by strerror().
Yep, I completely agree. Attached are patches with the quotes removed and
rebased since Oids were taken etc.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v10.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v10.patchDownload
From b7f45fb74361e94e97991493951aad73897647db Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 5 Mar 2018 12:16:16 +0100
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 190 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 168 ---------------------------
3 files changed, 193 insertions(+), 171 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..603b229149
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,190 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 2e1e020c4b..5bfe789c0c 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,9 +33,6 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
@@ -198,169 +193,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v10.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v10.patchDownload
From a8519a37d0937091b8e179a75bc7867067ec4162 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, 377 insertions(+), 21 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 6257563eaa..0b7b49a635 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18512,7 +18512,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
@@ -18537,7 +18537,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
@@ -18568,6 +18568,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..895e1be775 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,30 @@ 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))
+ 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_terminate_backend_internal(pid, msg));
}
/*
@@ -167,7 +240,6 @@ pg_reload_conf(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(true);
}
-
/*
* Rotate log file
*
@@ -188,3 +260,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 853e1e472f..fa178c766b 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -34,6 +34,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"
@@ -151,6 +152,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
@@ -272,6 +274,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 7bdecc32ec..fca6d450ac 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -61,6 +61,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"
@@ -2919,9 +2920,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)
{
@@ -3032,9 +3046,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 09e0df290d..87948f4a19 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 02be8a5fbd..43aacaa299 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3307,8 +3307,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 = 6108 ( 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 = 6107 ( 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..4661cdbc75
--- /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 20d6745730..c4872e3d58 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 merge
+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 merge 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
On Fri, Apr 06, 2018 at 11:18:34AM +0200, Daniel Gustafsson wrote:
Yep, I completely agree. Attached are patches with the quotes removed and
rebased since Oids were taken etc.
I still find this idea interesting for plugin authors. However, as
feature freeze for v11 is in effect, I am marking the patch as returned
with feedback.
--
Michael
On 09 Apr 2018, at 02:47, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Apr 06, 2018 at 11:18:34AM +0200, Daniel Gustafsson wrote:
Yep, I completely agree. Attached are patches with the quotes removed and
rebased since Oids were taken etc.I still find this idea interesting for plugin authors. However, as
feature freeze for v11 is in effect, I am marking the patch as returned
with feedback.
Not sure what the feedback is though? The patch has been through thorough
review with all review comments addressed.
Will resubmit this for a v12 CF.
cheers ./daniel
Hi,
On 2017-06-20 13:01:35 -0700, Andres Freund wrote:
For extensions it'd also be useful if it'd be possible to overwrite the
error code. E.g. for citus there's a distributed deadlock detector,
running out of process because there's no way to interrupt lock waits
locally, and we've to do some ugly hacking to generate proper error
messages and code from another session.
What happened to this request? Seems we're out of the crunch mode and
could round the feature out a littlebit more...
Greetings,
Andres Freund
On 9 Apr 2018, at 23:55, Andres Freund <andres@anarazel.de> wrote:
On 2017-06-20 13:01:35 -0700, Andres Freund wrote:For extensions it'd also be useful if it'd be possible to overwrite the
error code. E.g. for citus there's a distributed deadlock detector,
running out of process because there's no way to interrupt lock waits
locally, and we've to do some ugly hacking to generate proper error
messages and code from another session.What happened to this request? Seems we're out of the crunch mode and
could round the feature out a littlebit more…
Revisiting old patches, I took a stab at this request.
Since I don’t really have a use case for altering the sqlerrcode other than the
on that Citus.. cited, I modelled the API around that. The slot now holds a
sqlerrcode as well as a message, with functions to just set the message keeping
the default sqlerrcode for when that is all one wants to do. There is no
function for just altering the sqlerrcode without a message as that seems not
useful to me.
The combination of sqlerrcode and message was dubbed SignalFeedback for lack of
a better term. With this I also addressed something that annoyed me; I had
called all the functions Cancel* which technically isn’t true since we also
support termination.
There are no new user facing changes in patch compared to the previous version.
This patchset still has the refactoring that Alvaro brought up upthread.
Parking this again the commitfest as it was returned with feedback from the
last one it was in (all review comments addressed, see upthread).
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v11.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v11.patch; x-unix-mode=0644Download
From 86159f2d40bf44bf4ae55a1cb71dbd986c440419 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:04 +0200
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 216 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 194 ---------------------------
3 files changed, 219 insertions(+), 197 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..2b81c161d9
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v11.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v11.patch; x-unix-mode=0644Download
From 67790ed15f6afeea8890f17407af87dd3d5347ef Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:07 +0200
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]);
The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend.
---
doc/src/sgml/func.sgml | 12 +-
src/backend/storage/ipc/backend_signal.c | 320 ++++++++++++++++++++++++++++--
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 55 ++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 6 +
src/include/storage/backend_signal.h | 27 +++
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, 442 insertions(+), 21 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 b851fe023a..ad9763698f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18540,7 +18540,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
@@ -18565,7 +18565,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
@@ -18596,6 +18596,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 2b81c161d9..69a7f106d4 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,20 +18,45 @@
#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/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 pid;
+ slock_t mutex;
+ char message[MAX_CANCEL_MSG];
+ int len;
+ int sqlerrcode;
+} 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 +70,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 +102,15 @@ 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)
+ {
+ 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
@@ -107,10 +141,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,
@@ -125,16 +159,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,
@@ -146,7 +203,30 @@ 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))
+ 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_terminate_backend_internal(pid, msg));
}
/*
@@ -168,7 +248,6 @@ pg_reload_conf(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(true);
}
-
/*
* Rotate log file
*
@@ -214,3 +293,220 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
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->len = 0;
+ slot->sqlerrcode = 0;
+ slot->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->len > 0)
+ MemSet(slot->message, '\0', sizeof(slot->message));
+
+ slot->len = 0;
+ slot->sqlerrcode = 0;
+ slot->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 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.
+ */
+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->pid != 0 && slot->pid == backend_pid)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (slot->pid != backend_pid)
+ {
+ SpinLockRelease(&slot->mutex);
+ goto error;
+ }
+
+ MemSet(slot->message, '\0', sizeof(slot->message));
+ memcpy(slot->message, message, len);
+ slot->len = len;
+ slot->sqlerrcode = sqlerrcode;
+ SpinLockRelease(&slot->mutex);
+
+ if (len != strlen(message))
+ ereport(NOTICE,
+ (errmsg("message is too long and has been truncated")));
+ return 0;
+ }
+ }
+
+error:
+
+ elog(LOG, "Cancellation message requested for missing backend %d by %d",
+ (int) backend_pid, MyProcPid);
+
+ return 1;
+}
+
+/*
+ * Test whether there is feedback registered for the current backend that can
+ * be consumed and presented to the user.
+ */
+bool
+HasBackendSignalFeedback(void)
+{
+ volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot;
+ bool has_message = false;
+
+ if (slot != NULL)
+ {
+ SpinLockAcquire(&slot->mutex);
+ has_message = ((slot->len > 0) && (slot->sqlerrcode != 0));
+ SpinLockRelease(&slot->mutex);
+ }
+
+ return has_message;
+}
+
+/*
+ * Return the configured signal feedback. The length of the message is returned
+ * in buf_len. The original length of the message is returned, or zero in case
+ * no message was found. If the returned length is greater than the size of the passed
+ * buffer, truncation has been performed. The feedback (message and errcode) is
+ * cleared on reading.
+ */
+int
+ConsumeBackendSignalFeedback(char *buffer, size_t buf_len, int *sqlerrcode)
+{
+ volatile BackendSignalFeedbackShmemStruct *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;
+ *sqlerrcode = slot->sqlerrcode;
+ slot->len = 0;
+ slot->message[0] = '\0';
+ slot->sqlerrcode = 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..5d91450887 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, 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/tcop/postgres.c b/src/backend/tcop/postgres.c
index f4133953be..e9e41c738b 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/backend_signal.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -2923,9 +2924,30 @@ 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;
+
+ len = ConsumeBackendSignalFeedback(buffer, MAX_CANCEL_MSG,
+ &sqlerrcode);
+ if (len == 0)
+ {
+ sqlerrcode = ERRCODE_ADMIN_SHUTDOWN;
+ buffer[0] = '\0';
+ }
+ ereport(FATAL,
+ (errcode(sqlerrcode),
+ errmsg("terminating connection due to administrator command: %s%s",
+ buffer, (len > sizeof(buffer) ? "..." : ""))));
+ }
+ else
+ ereport(FATAL,
+ (errcode(ERRCODE_ADMIN_SHUTDOWN),
+ errmsg("terminating connection due to administrator command")));
+ }
}
if (ClientConnectionLost)
{
@@ -3036,9 +3058,30 @@ 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;
+
+ len = ConsumeBackendSignalFeedback(buffer, MAX_CANCEL_MSG,
+ &sqlerrcode);
+ if (len == 0)
+ {
+ sqlerrcode = ERRCODE_QUERY_CANCELED;
+ buffer[0] = '\0';
+ }
+
+ ereport(ERROR,
+ (errcode(sqlerrcode),
+ errmsg("canceling statement due to user request: %s%s",
+ buffer, (len > sizeof(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 09e0df290d..65ae347f8d 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();
+ BackendSignalFeedbackInit(MyBackendId);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66c6c224a8..354fa302fd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5973,9 +5973,15 @@
{ oid => '2171', descr => 'cancel a server process\' current query',
proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
proargtypes => 'int4', prosrc => 'pg_cancel_backend' },
+{ oid => '3423', descr => 'cancel a server process\' current query and send message',
+ proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
+ proargtypes => 'int4 text', proisstrict => 'f', prosrc => 'pg_cancel_backend_msg' },
{ oid => '2096', descr => 'terminate a server process',
proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
proargtypes => 'int4', prosrc => 'pg_terminate_backend' },
+{ oid => '3424', descr => 'terminate a server process and send message',
+ proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
+ proargtypes => 'int4 text', proisstrict => 'f', prosrc => 'pg_terminate_backend_msg' },
{ 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/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..e6431eba9c
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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);
+
+#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..4661cdbc75
--- /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 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..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
Hi,
Onder, I CCed you because it seems worthwhile to ensure that the
relevant Citus code could use this instead of the gross hack you and I
committed...
On 2018-06-13 20:54:03 +0200, Daniel Gustafsson wrote:
On 9 Apr 2018, at 23:55, Andres Freund <andres@anarazel.de> wrote:
On 2017-06-20 13:01:35 -0700, Andres Freund wrote:For extensions it'd also be useful if it'd be possible to overwrite the
error code. E.g. for citus there's a distributed deadlock detector,
running out of process because there's no way to interrupt lock waits
locally, and we've to do some ugly hacking to generate proper error
messages and code from another session.What happened to this request? Seems we're out of the crunch mode and
could round the feature out a littlebit more…Revisiting old patches, I took a stab at this request.
Since I don’t really have a use case for altering the sqlerrcode other than the
on that Citus.. cited, I modelled the API around that.
Cool. Onder?
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b851fe023a..ad9763698f 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -18540,7 +18540,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 @@ -18565,7 +18565,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 @@ -18596,6 +18596,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>
I'm not sure I really like the appending bit. There's a security
argument to be made about doing so, but from a user POV that mostly
seems restrictive. I wonder if that aspect would be better handled by
adding an error context that contains information about which process
did the cancellation (or DETAIL?)?
+/* + * 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 pid;
This is the pid of the receiving process? If so, why do we need this in
here? That's just duplicated data, no?
+ slock_t mutex; + char message[MAX_CANCEL_MSG]; + int len; + int sqlerrcode;
I'd vote for including the pid of the process that did the cancelling in
here.
+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)); +}
Why isn't this just one function? Given that you already have a PG_NARGS
check, I don't quite see the point?
/* * 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,
@@ -146,7 +203,30 @@ 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)) + 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_terminate_backend_internal(pid, msg)); }/*
@@ -168,7 +248,6 @@ pg_reload_conf(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(true);
}
Same.
+/* + * 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); +}
I'm not quite seeing the point of these variants. What advantage are
they over just doing the same on the caller level?
+/* + * 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. + */ +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->pid != 0 && slot->pid == backend_pid) + { + SpinLockAcquire(&slot->mutex); + if (slot->pid != backend_pid) + { + SpinLockRelease(&slot->mutex); + goto error; + } + + MemSet(slot->message, '\0', sizeof(slot->message)); + memcpy(slot->message, message, len);
This seems unnecessarily expensive.
+ slot->len = len; + slot->sqlerrcode = sqlerrcode; + SpinLockRelease(&slot->mutex); + + if (len != strlen(message)) + ereport(NOTICE, + (errmsg("message is too long and has been truncated"))); + return 0; + } + }
So we made cancellation a O(N) proposition :/. Probably not too bad, but
still...
+error: + + elog(LOG, "Cancellation message requested for missing backend %d by %d", + (int) backend_pid, MyProcPid); + + return 1; +}
So we now do log spam if processes exit in the wrong moment?
+/* + * Test whether there is feedback registered for the current backend that can + * be consumed and presented to the user. + */ +bool +HasBackendSignalFeedback(void) +{ + volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot; + bool has_message = false; + + if (slot != NULL) + { + SpinLockAcquire(&slot->mutex); + has_message = ((slot->len > 0) && (slot->sqlerrcode != 0)); + SpinLockRelease(&slot->mutex); + } + + return has_message; +}
I'm somewhat uncomfortable with acquiring a mutex in an error path. We
used to prcoess at least some interrupts in signal handlers in some
cases - I think I removed all of them, but if not, we'd be in deep
trouble here. Wonder if we shouldn't try to get around needing that...
Greetings,
Andres Freund
On 13 Jun 2018, at 21:16, Andres Freund <andres@anarazel.de> wrote:
Thanks for the review!
I'm not sure I really like the appending bit. There's a security
argument to be made about doing so, but from a user POV that mostly
seems restrictive. I wonder if that aspect would be better handled by
adding an error context that contains information about which process
did the cancellation (or DETAIL?)?
That doesn’t sound like a bad idea at all, I took a stab at that in the
attached patch to see what it would look like. Is that along the lines of what
you had in mind?
It does however make testing harder as the .out file can’t have the matching
pid (commented out the test in the attached hoping that there is a way to test
it that I fail to see).
+/* + * 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 pid;This is the pid of the receiving process? If so, why do we need this in
here? That's just duplicated data, no?
Correct, we need that for finding the right slot in backend_feedback() unless
I’m missing something?
+ slock_t mutex; + char message[MAX_CANCEL_MSG]; + int len; + int sqlerrcode;I'd vote for including the pid of the process that did the cancelling in
here.
Did that for the above discussed log message change.
[ .. ]
Why isn't this just one function? Given that you already have a PG_NARGS
check, I don't quite see the point?
I was trying to retain STRICT on pg_cancel_backend(int4) for no food reason,
and I’m not entirely sure what the reason was anymore. Fixed in the attached
version.
[ .. ]
I'm not quite seeing the point of these variants. What advantage are
they over just doing the same on the caller level?
It seemed a cleaner API to provide these for when the caller just want to
provide messaging (which is where I started this a long time ago). I’m not wed
to the idea at all though, they are clearly just for convenience.
+ MemSet(slot->message, '\0', sizeof(slot->message)); + memcpy(slot->message, message, len);This seems unnecessarily expensive.
My goal was to safeguard against the risk of fragments from an undelivered
message being delivered to another backend, which might be overly cautious.
Using strlcpy() instead to guarantee termination is perhaps enough?
+ slot->len = len; + slot->sqlerrcode = sqlerrcode; + SpinLockRelease(&slot->mutex); + + if (len != strlen(message)) + ereport(NOTICE, + (errmsg("message is too long and has been truncated"))); + return 0; + } + }So we made cancellation a O(N) proposition :/. Probably not too bad, but
still…
When setting a message to be passed to the backend yes, with an upper bound of
MaxBackends.
[ .. ]
So we now do log spam if processes exit in the wrong moment?
Yeah, that was rather pointless. Removed in the attached version.
I'm somewhat uncomfortable with acquiring a mutex in an error path. We
used to prcoess at least some interrupts in signal handlers in some
cases - I think I removed all of them, but if not, we'd be in deep
trouble here. Wonder if we shouldn't try to get around needing that…
I’m all ears if you have a better idea, this was the only option I could come
up with.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v12.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v12.patch; x-unix-mode=0644Download
From 188089a36eeab66d390f2db16140b146e308d77b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:04 +0200
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 216 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 194 ---------------------------
3 files changed, 219 insertions(+), 197 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..2b81c161d9
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v12.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v12.patch; x-unix-mode=0644Download
From 287084f3800337ad8d29c1d6bbc7a63a9d131d63 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:07 +0200
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]);
The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend.
---
doc/src/sgml/func.sgml | 12 +-
src/backend/catalog/system_views.sql | 8 +
src/backend/storage/ipc/backend_signal.c | 287 +++++++++++++++++++++++++++++-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 61 ++++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/backend_signal.h | 27 +++
src/test/regress/expected/admin_funcs.out | 28 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 7 +
11 files changed, 423 insertions(+), 18 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 b851fe023a..ad9763698f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18540,7 +18540,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
@@ -18565,7 +18565,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
@@ -18596,6 +18596,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/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8cd8bf40ac..f0e5bf90b0 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/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 2b81c161d9..298cf1a390 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,20 +18,46 @@
#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/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 len; /* Length of the message */
+ 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 +71,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 +103,15 @@ 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)
+ {
+ 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 +145,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 +181,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 +205,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);
}
/*
@@ -214,3 +273,217 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
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->len = 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->len > 0)
+ MemSet(slot->message, '\0', sizeof(slot->message));
+
+ slot->len = 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.
+ */
+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;
+ }
+
+ MemSet(slot->message, '\0', sizeof(slot->message));
+ memcpy(slot->message, message, len);
+ slot->len = len;
+ 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;
+}
+
+/*
+ * Test whether there is feedback registered for the current backend that can
+ * be consumed and presented to the user.
+ */
+bool
+HasBackendSignalFeedback(void)
+{
+ volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot;
+ bool has_message = false;
+
+ if (slot != NULL)
+ {
+ SpinLockAcquire(&slot->mutex);
+ has_message = ((slot->len > 0) && (slot->sqlerrcode != 0));
+ SpinLockRelease(&slot->mutex);
+ }
+
+ return has_message;
+}
+
+/*
+ * Return the configured signal feedback. The length of the message is returned
+ * in buf_len. The original length of the message is returned, or zero in case
+ * no message was found. If the returned length is greater than the size of the
+ * passed buffer, truncation has been performed. The feedback (message and
+ * errcode) is cleared on reading.
+ */
+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->len > 0)
+ {
+ SpinLockAcquire(&slot->mutex);
+ strlcpy(buffer, (const char *) slot->message, buf_len);
+ msg_length = slot->len;
+ *sqlerrcode = slot->sqlerrcode;
+ *pid = slot->src_pid;
+ slot->len = 0;
+ slot->message[0] = '\0';
+ slot->sqlerrcode = 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..5d91450887 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, 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/tcop/postgres.c b/src/backend/tcop/postgres.c
index f4133953be..ebe3ac8b50 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/backend_signal.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -2923,9 +2924,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)
{
@@ -3036,9 +3061,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 09e0df290d..65ae347f8d 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();
+ BackendSignalFeedbackInit(MyBackendId);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66c6c224a8..53f6766f0f 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/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..86b46b3cc4
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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 /* 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..c6edab2537
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,28 @@
+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');
+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 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..9dc01ac49d
--- /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
On Fri, Jun 15, 2018 at 9:56 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
attached
Hi Daniel,
6118 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6119 select pg_cancel_backend(pg_backend_pid(), NULL);
6120! ERROR: canceling statement due to user request
6121--- 25,32 ----
6122
6123 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6124 select pg_cancel_backend(pg_backend_pid(), NULL);
6125! pg_cancel_backend
6126! -------------------
6127! t
Apparently Windows can take or leave it as it pleases.
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.4488
--
Thomas Munro
http://www.enterprisedb.com
On Fri, Jul 06, 2018 at 12:18:02PM +1200, Thomas Munro wrote:
6118 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6119 select pg_cancel_backend(pg_backend_pid(), NULL);
6120! ERROR: canceling statement due to user request
6121--- 25,32 ----
6122
6123 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6124 select pg_cancel_backend(pg_backend_pid(), NULL);
6125! pg_cancel_backend
6126! -------------------
6127! tApparently Windows can take or leave it as it pleases.
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.4488
The test coverage looks adapted if it is possible to catch such
failures, so that's nice.
+select pg_cancel_backend();
+ERROR: function pg_cancel_backend() does not exist
+LINE 1: select pg_cancel_backend();
This negative test is not really necessary.
--
Michael
Hi
I am testing last patch and looks so truncating message and appending "..."
doesn't work.
The slot->len hold trimmed length, not original length.
Regards
Pavel
On 6 Jul 2018, at 14:12, Pavel Stehule <pavel.stehule@gmail.com> wrote:
I am testing last patch and looks so truncating message and appending "..." doesn't work.
Thanks for testing, and sorry about the late response.
The slot->len hold trimmed length, not original length.
Fixed in the attached rebased patchset together with a few small touch-ups such
as fixed documentation.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v13.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v13.patch; x-unix-mode=0644Download
From 7cc3b81011e461a24463bc47c50f92a82ce51f78 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:04 +0200
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 216 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 194 ---------------------------
3 files changed, 219 insertions(+), 197 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..2b81c161d9
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v13.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v13.patch; x-unix-mode=0644Download
From 9621919433dffd006e2c216e915394be1cc011ee Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:07 +0200
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]);
The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend.
---
doc/src/sgml/func.sgml | 13 +-
src/backend/catalog/system_views.sql | 8 +
src/backend/storage/ipc/backend_signal.c | 303 +++++++++++++++++++++++++++++-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 61 +++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/backend_signal.h | 27 +++
src/test/regress/expected/admin_funcs.out | 22 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 5 +
11 files changed, 432 insertions(+), 18 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 edc9be92a6..5215e3eb60 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18554,7 +18554,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
@@ -18579,7 +18579,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
@@ -18610,6 +18610,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 bytes, 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 7251552419..21ab9592a2 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/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 2b81c161d9..4855ad8e32 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,20 +18,49 @@
#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/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 +74,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 +106,15 @@ 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)
+ {
+ 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 +148,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 +184,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 +208,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);
}
/*
@@ -214,3 +276,230 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
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/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..5d91450887 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, 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/tcop/postgres.c b/src/backend/tcop/postgres.c
index f4133953be..478c968338 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/backend_signal.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -2923,9 +2924,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)
{
@@ -3036,9 +3061,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 5ef6315d20..8bbfdde93c 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"
@@ -758,6 +759,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 a14651010f..bd4831f639 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/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..86b46b3cc4
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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 /* 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..e1489e7717
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,22 @@
+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(), NULL);
+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..8288ab0e11
--- /dev/null
+++ b/src/test/regress/sql/admin_funcs.sql
@@ -0,0 +1,5 @@
+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(), NULL);
--
2.14.1.145.gb3622a4ee
On 6 Jul 2018, at 03:47, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Jul 06, 2018 at 12:18:02PM +1200, Thomas Munro wrote:
6118 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6119 select pg_cancel_backend(pg_backend_pid(), NULL);
6120! ERROR: canceling statement due to user request
6121--- 25,32 ----
6122
6123 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6124 select pg_cancel_backend(pg_backend_pid(), NULL);
6125! pg_cancel_backend
6126! -------------------
6127! tApparently Windows can take or leave it as it pleases.
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.4488
The test coverage looks adapted if it is possible to catch such
failures, so that's nice.+select pg_cancel_backend(); +ERROR: function pg_cancel_backend() does not exist +LINE 1: select pg_cancel_backend(); This negative test is not really necessary.
I guess you’re right, it’s a bit superfluous. Removed in the last sent
patchset.
cheers ./daniel
On 6 Jul 2018, at 02:18, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Fri, Jun 15, 2018 at 9:56 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
attached
Hi Daniel,
6118 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6119 select pg_cancel_backend(pg_backend_pid(), NULL);
6120! ERROR: canceling statement due to user request
6121--- 25,32 ----
6122
6123 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6124 select pg_cancel_backend(pg_backend_pid(), NULL);
6125! pg_cancel_backend
6126! -------------------
6127! tApparently Windows can take or leave it as it pleases.
Well played =)
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.4488
That reads to me like it’s cancelling another backend than the current one,
which clearly isn’t right as we’re passing pg_backend_pid(). I can’t really
see what Windows specific bug was introduced by this patch though (or well, the
bug exhibits itself on Windows but it may well be generic of course).
Will continue to hunt.
cheers ./daniel
On 24 Jul 2018, at 22:57, Daniel Gustafsson <daniel@yesql.se> wrote:
On 6 Jul 2018, at 02:18, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Fri, Jun 15, 2018 at 9:56 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
attached
Hi Daniel,
6118 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6119 select pg_cancel_backend(pg_backend_pid(), NULL);
6120! ERROR: canceling statement due to user request
6121--- 25,32 ----
6122
6123 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6124 select pg_cancel_backend(pg_backend_pid(), NULL);
6125! pg_cancel_backend
6126! -------------------
6127! tApparently Windows can take or leave it as it pleases.
Well played =)
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.4488
That reads to me like it’s cancelling another backend than the current one,
which clearly isn’t right as we’re passing pg_backend_pid(). I can’t really
see what Windows specific bug was introduced by this patch though (or well, the
bug exhibits itself on Windows but it may well be generic of course).Will continue to hunt.
Seems the build of the updated patch built and tested Ok. Still have no idea
why the previous one didn’t.
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.6695
cheers ./daniel
Has there been any consideration to encodings? What happens if the
message contains non-ASCII characters, and the sending backend is
connected to database that uses a different encoding than the backend
being signaled?
- Heikki
On 6 Aug 2018, at 09:47, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Has there been any consideration to encodings?
Thats a good point, no =/
What happens if the message contains non-ASCII characters, and the sending backend is connected to database that uses a different encoding than the backend being signaled?
In the current state of the patch, instead of the message you get:
FATAL: character with byte sequence 0xe3 0x82 0xbd in encoding "UTF8" has
no equivalent in encoding “ISO_8859_5"
Thats clearly not good enough, but I’m not entirely sure what would be the best
way forward. Restrict messages to only be in SQL_ASCII? Store the encoding of
the message and check the encoding of the receiving backend before issuing it
for a valid conversion, falling back to no message in case there is none?
Neither seems terribly appealing, do you have any better suggestions?
cheers ./daniel
2018-08-12 0:17 GMT+02:00 Daniel Gustafsson <daniel@yesql.se>:
On 6 Aug 2018, at 09:47, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Has there been any consideration to encodings?
Thats a good point, no =/
What happens if the message contains non-ASCII characters, and the
sending backend is connected to database that uses a different encoding
than the backend being signaled?In the current state of the patch, instead of the message you get:
FATAL: character with byte sequence 0xe3 0x82 0xbd in encoding "UTF8"
has
no equivalent in encoding “ISO_8859_5"
Where this code fails? Isn't somewhere upper where string literals are
translated? Then this message is ok.
Thats clearly not good enough, but I’m not entirely sure what would be the
best
way forward. Restrict messages to only be in SQL_ASCII? Store the
encoding of
the message and check the encoding of the receiving backend before issuing
it
for a valid conversion, falling back to no message in case there is none?
Neither seems terribly appealing, do you have any better suggestions?
The client<->server encoding translation should do this work no?
Regards
Pavel
Show quoted text
cheers ./daniel
On 12 Aug 2018, at 07:42, Pavel Stehule <pavel.stehule@gmail.com> wrote:
2018-08-12 0:17 GMT+02:00 Daniel Gustafsson <daniel@yesql.se <mailto:daniel@yesql.se>>:On 6 Aug 2018, at 09:47, Heikki Linnakangas <hlinnaka@iki.fi <mailto:hlinnaka@iki.fi>> wrote:
What happens if the message contains non-ASCII characters, and the sending backend is connected to database that uses a different encoding than the backend being signaled?
In the current state of the patch, instead of the message you get:
FATAL: character with byte sequence 0xe3 0x82 0xbd in encoding "UTF8" has
no equivalent in encoding “ISO_8859_5"Where this code fails? Isn't somewhere upper where string literals are translated? Then this message is ok.
This happens for example when a UTF-8 backend sends a message with japanese
characters to a backend using ISO_8859_5. So the code works as expected, but
it’s not a very good user experience.
cheers ./daniel
2018-08-12 10:29 GMT+02:00 Daniel Gustafsson <daniel@yesql.se>:
On 12 Aug 2018, at 07:42, Pavel Stehule <pavel.stehule@gmail.com> wrote:
2018-08-12 0:17 GMT+02:00 Daniel Gustafsson <daniel@yesql.se <mailto:daniel@yesql.se>>:
On 6 Aug 2018, at 09:47, Heikki Linnakangas <hlinnaka@iki.fi <mailto:
hlinnaka@iki.fi>> wrote:
What happens if the message contains non-ASCII characters, and the
sending backend is connected to database that uses a different encoding
than the backend being signaled?In the current state of the patch, instead of the message you get:
FATAL: character with byte sequence 0xe3 0x82 0xbd in encoding
"UTF8" has
no equivalent in encoding “ISO_8859_5"
Where this code fails? Isn't somewhere upper where string literals are
translated? Then this message is ok.
This happens for example when a UTF-8 backend sends a message with japanese
characters to a backend using ISO_8859_5. So the code works as expected,
but
it’s not a very good user experience.
It is same like any other using of string literal.
Personally, I don't think so these functions should be a exception.
Pavel
Show quoted text
cheers ./daniel
On August 12, 2018 12:17:59 AM GMT+02:00, Daniel Gustafsson <daniel@yesql.se> wrote:
On 6 Aug 2018, at 09:47, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Has there been any consideration to encodings?
Thats a good point, no =/
What happens if the message contains non-ASCII characters, and the
sending backend is connected to database that uses a different encoding
than the backend being signaled?In the current state of the patch, instead of the message you get:
FATAL: character with byte sequence 0xe3 0x82 0xbd in encoding "UTF8"
has
no equivalent in encoding “ISO_8859_5"Thats clearly not good enough, but I’m not entirely sure what would be
the best
way forward. Restrict messages to only be in SQL_ASCII? Store the
encoding of
the message and check the encoding of the receiving backend before
issuing it
for a valid conversion, falling back to no message in case there is
none?
Neither seems terribly appealing, do you have any better suggestions?
Restricting to ASCII seems reasonable. But note that sqlascii isn't that (it's essentially just arbitrary null terminated data). Easier to relax later.
Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
On 12 Aug 2018, at 11:01, Andres Freund <andres@anarazel.de> wrote:
On August 12, 2018 12:17:59 AM GMT+02:00, Daniel Gustafsson <daniel@yesql.se> wrote:
On 6 Aug 2018, at 09:47, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Has there been any consideration to encodings?
Thats a good point, no =/
What happens if the message contains non-ASCII characters, and the
sending backend is connected to database that uses a different encoding
than the backend being signaled?In the current state of the patch, instead of the message you get:
FATAL: character with byte sequence 0xe3 0x82 0xbd in encoding "UTF8"
has
no equivalent in encoding “ISO_8859_5"Thats clearly not good enough, but I’m not entirely sure what would be
the best
way forward. Restrict messages to only be in SQL_ASCII? Store the
encoding of
the message and check the encoding of the receiving backend before
issuing it
for a valid conversion, falling back to no message in case there is
none?
Neither seems terribly appealing, do you have any better suggestions?Restricting to ASCII seems reasonable.
It’s quite restrictive, but it’s the safe option. I’ve hacked this into the
updated patch, but kept the backend_feedback() function using pg_mbstrlen() at
least for now since it seems the safe option should this be relaxed at some
point. Also added a small test by copying text from a ja.po file in the tree.
But note that sqlascii isn't that (it's essentially just arbitrary null terminated data). Easier to relax later.
Yeah, my fingers and brain were not in sync during typing, I meant to say ASCII
there. I blame a lack of coffee.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v14.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v14.patch; x-unix-mode=0644Download
From 81d82f7394c145745d0f20ab1f6cc25290ec757c Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:04 +0200
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 216 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 194 ---------------------------
3 files changed, 219 insertions(+), 197 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..2b81c161d9
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v14.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v14.patch; x-unix-mode=0644Download
From 0ebdcca3d545c2367d4f40407ccacab09e887e48 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:07 +0200
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]);
The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend.
---
doc/src/sgml/func.sgml | 13 +-
src/backend/catalog/system_views.sql | 8 +
src/backend/storage/ipc/backend_signal.c | 318 +++++++++++++++++++++++++++++-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 61 +++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/backend_signal.h | 27 +++
src/test/regress/expected/admin_funcs.out | 24 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 6 +
11 files changed, 450 insertions(+), 18 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 edc9be92a6..004359181e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18554,7 +18554,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
@@ -18579,7 +18579,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
@@ -18610,6 +18610,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 7251552419..21ab9592a2 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/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 2b81c161d9..c3c1198c45 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,20 +18,49 @@
#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/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 +74,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 +106,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 +163,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 +199,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 +223,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);
}
/*
@@ -214,3 +291,230 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
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/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..5d91450887 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, 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/tcop/postgres.c b/src/backend/tcop/postgres.c
index 07b956553a..d67f964985 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/backend_signal.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -2929,9 +2930,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)
{
@@ -3042,9 +3067,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 5ef6315d20..8bbfdde93c 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"
@@ -758,6 +759,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 a14651010f..bd4831f639 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/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..86b46b3cc4
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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 /* 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..99437561b1
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,24 @@
+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(), 'ソケットをブロッキングモードに設定できませんでした');
+ERROR: message is restricted to ASCII only
+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 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..d904036704
--- /dev/null
+++ b/src/test/regress/sql/admin_funcs.sql
@@ -0,0 +1,6 @@
+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(), 'ソケットをブロッキングモードに設定できませんでした');
+select pg_cancel_backend(pg_backend_pid(), NULL);
--
2.14.1.145.gb3622a4ee
On Wed, Jul 25, 2018 at 7:27 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
On 24 Jul 2018, at 22:57, Daniel Gustafsson <daniel@yesql.se> wrote:
On 6 Jul 2018, at 02:18, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Fri, Jun 15, 2018 at 9:56 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
attached
Hi Daniel,
6118 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6119 select pg_cancel_backend(pg_backend_pid(), NULL);
6120! ERROR: canceling statement due to user request
6121--- 25,32 ----
6122
6123 --select pg_cancel_backend(pg_backend_pid(), 'it brings on many changes');
6124 select pg_cancel_backend(pg_backend_pid(), NULL);
6125! pg_cancel_backend
6126! -------------------
6127! tApparently Windows can take or leave it as it pleases.
Well played =)
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.4488
That reads to me like it’s cancelling another backend than the current one,
which clearly isn’t right as we’re passing pg_backend_pid(). I can’t really
see what Windows specific bug was introduced by this patch though (or well, the
bug exhibits itself on Windows but it may well be generic of course).Will continue to hunt.
Seems the build of the updated patch built and tested Ok. Still have no idea
why the previous one didn’t.
That problem apparently didn't go away. cfbot tested it 7 times in
the past week, and it passed only once on Windows:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.9691
The other times all failed like this:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.9833
--
Thomas Munro
http://www.enterprisedb.com
Thomas Munro <thomas.munro@enterprisedb.com> writes:
On Wed, Jul 25, 2018 at 7:27 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
Seems the build of the updated patch built and tested Ok. Still have no idea
why the previous one didn’t.
That problem apparently didn't go away. cfbot tested it 7 times in
the past week, and it passed only once on Windows:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.9691
The other times all failed like this:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.9833
I think this is just a timing problem: the signal gets sent,
but it might or might not get received before the current statement ends.
It's possible that a signal-sent-to-self can be expected to be received
synchronously on all Unix platforms, but I wouldn't entirely bet on that
(in particular, the POSIX text for kill(2) does NOT guarantee it); and
our Windows signal implementation certainly doesn't guarantee anything
of the sort.
I don't think there's necessarily anything wrong with the code, but
you can't test it with such a simplistic test as this and expect
stable results. If you feel an urgent need to have a test case,
try building an isolationtester script.
regards, tom lane
On Fri, Aug 24, 2018 at 6:53 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Thomas Munro <thomas.munro@enterprisedb.com> writes:
On Wed, Jul 25, 2018 at 7:27 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
Seems the build of the updated patch built and tested Ok. Still have no idea
why the previous one didn’t.That problem apparently didn't go away. cfbot tested it 7 times in
the past week, and it passed only once on Windows:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.9691
The other times all failed like this:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.9833I think this is just a timing problem: the signal gets sent,
but it might or might not get received before the current statement ends.
It's possible that a signal-sent-to-self can be expected to be received
synchronously on all Unix platforms, but I wouldn't entirely bet on that
(in particular, the POSIX text for kill(2) does NOT guarantee it); and
our Windows signal implementation certainly doesn't guarantee anything
of the sort.
How about we just wait forever if the function manages to return?
select case when pg_cancel_backend(pg_backend_pid(), '...') then
pg_sleep('infinity') end;
--
Thomas Munro
http://www.enterprisedb.com
Thomas Munro <thomas.munro@enterprisedb.com> writes:
On Fri, Aug 24, 2018 at 6:53 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think this is just a timing problem: the signal gets sent,
but it might or might not get received before the current statement ends.
How about we just wait forever if the function manages to return?
select case when pg_cancel_backend(pg_backend_pid(), '...') then
pg_sleep('infinity') end;
Hm, that might work. I'd pick a long but not infinite timeout --- maybe
60 sec would be good.
regards, tom lane
On 24 Aug 2018, at 03:37, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Thomas Munro <thomas.munro@enterprisedb.com> writes:
On Fri, Aug 24, 2018 at 6:53 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think this is just a timing problem: the signal gets sent,
but it might or might not get received before the current statement ends.How about we just wait forever if the function manages to return?
select case when pg_cancel_backend(pg_backend_pid(), '...') then
pg_sleep('infinity') end;Hm, that might work. I'd pick a long but not infinite timeout --- maybe
60 sec would be good.
I like that idea, so I’ve updated the patch to see what the cfbot thinks of it.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v15.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v15.patch; x-unix-mode=0644Download
From 5f629a0342729cd82b8f5dc5187c531f87ad0643 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:04 +0200
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/backend_signal.c | 216 +++++++++++++++++++++++++++++++
src/backend/utils/adt/misc.c | 194 ---------------------------
3 files changed, 219 insertions(+), 197 deletions(-)
create mode 100644 src/backend/storage/ipc/backend_signal.c
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..ac35c197e0 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = backend_signal.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
new file mode 100644
index 0000000000..2b81c161d9
--- /dev/null
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+ *
+ * backend_signal.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/backend_signal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "postmaster/syslogger.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v15.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v15.patch; x-unix-mode=0644Download
From 81db4811c1e01d3c4a934d0b35ecb0a2b0573649 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:07 +0200
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]);
The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend.
---
doc/src/sgml/func.sgml | 13 +-
src/backend/catalog/system_views.sql | 8 +
src/backend/storage/ipc/backend_signal.c | 318 +++++++++++++++++++++++++++++-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/tcop/postgres.c | 61 +++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/backend_signal.h | 27 +++
src/test/regress/expected/admin_funcs.out | 30 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 12 ++
11 files changed, 462 insertions(+), 18 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 bb794e044f..245c207ae4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18554,7 +18554,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
@@ -18579,7 +18579,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
@@ -18610,6 +18610,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 7251552419..21ab9592a2 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/backend_signal.c b/src/backend/storage/ipc/backend_signal.c
index 2b81c161d9..c3c1198c45 100644
--- a/src/backend/storage/ipc/backend_signal.c
+++ b/src/backend/storage/ipc/backend_signal.c
@@ -18,20 +18,49 @@
#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/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 +74,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 +106,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 +163,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 +199,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 +223,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);
}
/*
@@ -214,3 +291,230 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
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/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..5d91450887 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, 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/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7a9ada2c71..a29d8bd9f6 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/backend_signal.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -2920,9 +2921,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)
{
@@ -3033,9 +3058,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 5ef6315d20..8bbfdde93c 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"
@@ -758,6 +759,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 a14651010f..bd4831f639 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/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..86b46b3cc4
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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 /* 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..d3723fddca
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,30 @@
+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 case
+ when pg_cancel_backend(pg_backend_pid(), 'ソケットをブロッキングモードに設定できませんでした')
+ then pg_sleep(60)
+end;
+ERROR: message is restricted to ASCII only
+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..36fde4cbf4
--- /dev/null
+++ b/src/test/regress/sql/admin_funcs.sql
@@ -0,0 +1,12 @@
+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 case
+ when pg_cancel_backend(pg_backend_pid(), 'ソケットをブロッキングモードに設定できませんでした')
+ then pg_sleep(60)
+end;
+select case
+ when pg_cancel_backend(pg_backend_pid(), NULL)
+ then pg_sleep(60)
+end;
--
2.14.1.145.gb3622a4ee
On 23 Aug 2018, at 20:53, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think this is just a timing problem: the signal gets sent,
but it might or might not get received before the current statement ends.
It's possible that a signal-sent-to-self can be expected to be received
synchronously on all Unix platforms, but I wouldn't entirely bet on that
(in particular, the POSIX text for kill(2) does NOT guarantee it); and
our Windows signal implementation certainly doesn't guarantee anything
of the sort.
That makes a lot of sense, I was thinking about it in too simplistic terms.
Thanks for the clarification.
I don't think there's necessarily anything wrong with the code, but
you can't test it with such a simplistic test as this and expect
stable results. If you feel an urgent need to have a test case,
try building an isolationtester script.
During hacking on it I preferred to have tests for it. Iff this makes it in,
the tests can be omitted per the judgement of the committers of course. Since
the printed pid will vary between runs it’s hard to test more interesting
scenarios so they are of questionable worth.
cheers ./daniel
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed
I tested this patch, and all looks well and functional. I reread a discussion and I don't see any unresolved objection against this patch.
There are not warning, crashes, all tests are passed. New behave is documented well.
I'll mark this patch as ready for commiters
The new status of this patch is: Ready for Committer
On Mon, Aug 27, 2018 at 12:06:18PM +0200, Daniel Gustafsson wrote:
I like that idea, so I’ve updated the patch to see what the cfbot
thinks of it.
+ when pg_cancel_backend(pg_backend_pid(), 'ソケットをブロッキングモードに設定できませんでした')
You could have chosen something less complicated, like "ホゲ", which is
the equivalent of "foo" in English. Anyway, non-ASCII characters should
not be included in the final patch.
--
Michael
On Sun, Sep 30, 2018 at 10:51:44PM +0900, Michael Paquier wrote:
You could have chosen something less complicated, like "ホゲ", which is
the equivalent of "foo" in English. Anyway, non-ASCII characters should
not be included in the final patch.
Looking at the refactoring patch 0001, wouldn't signalfuncs.c make a
better name for the new file? There are already multiple examples of
this type, like logicalfuncs.c, slotfuncs.c, etc.
I have moved this patch set to the next commit fest for now.
--
Michael
On 1 Oct 2018, at 01:19, Michael Paquier <michael@paquier.xyz> wrote:
On Sun, Sep 30, 2018 at 10:51:44PM +0900, Michael Paquier wrote:
You could have chosen something less complicated, like "ホゲ", which is
the equivalent of "foo" in English. Anyway, non-ASCII characters should
not be included in the final patch.
Fixed in the attached v16 revision.
Looking at the refactoring patch 0001, wouldn't signalfuncs.c make a
better name for the new file? There are already multiple examples of
this type, like logicalfuncs.c, slotfuncs.c, etc.
I have no strong feelings on this, I was merely using the name that Alvaro
suggested when he brought up the refactoring as an extension of this patch.
signalfuncs.c is fine by me, so I did this rename in the attached revision.
I have moved this patch set to the next commit fest for now.
Thanks.
cheers ./daniel
Attachments:
0001-Refactor-backend-signalling-code-v16.patchapplication/octet-stream; name=0001-Refactor-backend-signalling-code-v16.patch; x-unix-mode=0644Download
From 1f65beed14e976b95dfab00d55b2d0fde4f41df8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:04 +0200
Subject: [PATCH 1/2] Refactor backend signalling code
This moves the system administration functions for signalling backends
from backend/utils/adt/misc.c into a separate file dedicated to backend
signalling. No new functionality is introduced in this commit.
---
src/backend/storage/ipc/Makefile | 6 +-
src/backend/storage/ipc/ipci.c | 1 +
src/backend/storage/ipc/signalfuncs.c | 218 ++++++++++++++++++++++++++++++++++
src/backend/tcop/postgres.c | 1 +
src/backend/utils/adt/misc.c | 194 ------------------------------
src/backend/utils/init/postinit.c | 1 +
src/include/storage/signalfuncs.h | 27 +++++
7 files changed, 251 insertions(+), 197 deletions(-)
create mode 100644 src/backend/storage/ipc/signalfuncs.c
create mode 100644 src/include/storage/signalfuncs.h
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..bf4619d5fd 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = signalfuncs.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..f1c460a761 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -42,6 +42,7 @@
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
+#include "storage/signalfuncs.h"
#include "storage/sinvaladt.h"
#include "storage/spin.h"
#include "utils/backend_random.h"
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
new file mode 100644
index 0000000000..249358e9c9
--- /dev/null
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -0,0 +1,218 @@
+/*-------------------------------------------------------------------------
+ *
+ * signalfuncs.c
+ * Routines for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/signalfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.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 "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e4c6e3d406..b17531b2cd 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -66,6 +66,7 @@
#include "storage/ipc.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
+#include "storage/signalfuncs.h"
#include "storage/sinval.h"
#include "tcop/fastpath.h"
#include "tcop/pquery.h"
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4f1d2a0d28..5f61913120 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -47,6 +47,7 @@
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/proc.h"
+#include "storage/signalfuncs.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
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 */
--
2.14.1.145.gb3622a4ee
0002-Support-optional-message-in-backend-cancel-terminate-v16.patchapplication/octet-stream; name=0002-Support-optional-message-in-backend-cancel-terminate-v16.patch; x-unix-mode=0644Download
From 5a4de9a130ce4e664dd35cf9dbcbcaee3caec2ab Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 13 Jun 2018 10:23:07 +0200
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]);
The backend API also expose functionality for altering the errcode
used when terminating or canceling the backend.
---
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 | 316 +++++++++++++++++++++++++++++-
src/backend/tcop/postgres.c | 61 +++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/backend_signal.h | 27 +++
src/test/regress/expected/admin_funcs.out | 25 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 8 +
11 files changed, 451 insertions(+), 18 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 9a7f683658..e61a9b0d00 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 7251552419..21ab9592a2 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 f1c460a761..04127ead9d 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"
@@ -151,6 +152,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
@@ -271,6 +273,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 249358e9c9..5809fffa45 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 "funcapi.h"
+#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/ipc.h"
@@ -28,12 +29,38 @@
#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.
@@ -47,7 +74,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);
@@ -79,6 +106,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
@@ -112,7 +163,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,
@@ -136,7 +199,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,
@@ -148,7 +223,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);
}
/*
@@ -216,3 +291,230 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
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 b17531b2cd..d53a44f708 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/backend_signal.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -2991,9 +2992,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)
{
@@ -3104,9 +3129,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 5f61913120..f0265624f4 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"
@@ -781,6 +782,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 8e4145f42b..ae5d45685e 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/backend_signal.h b/src/include/storage/backend_signal.h
new file mode 100644
index 0000000000..86b46b3cc4
--- /dev/null
+++ b/src/include/storage/backend_signal.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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 /* 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..d99bd56532
--- /dev/null
+++ b/src/test/regress/expected/admin_funcs.out
@@ -0,0 +1,25 @@
+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 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..570c552347
--- /dev/null
+++ b/src/test/regress/sql/admin_funcs.sql
@@ -0,0 +1,8 @@
+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 case
+ when pg_cancel_backend(pg_backend_pid(), NULL)
+ then pg_sleep(60)
+end;
--
2.14.1.145.gb3622a4ee
On Mon, Oct 01, 2018 at 02:37:42PM +0200, Daniel Gustafsson wrote:
On 1 Oct 2018, at 01:19, Michael Paquier <michael@paquier.xyz> wrote:
Looking at the refactoring patch 0001, wouldn't signalfuncs.c make a
better name for the new file? There are already multiple examples of
this type, like logicalfuncs.c, slotfuncs.c, etc.I have no strong feelings on this, I was merely using the name that Alvaro
suggested when he brought up the refactoring as an extension of this patch.
signalfuncs.c is fine by me, so I did this rename in the attached revision.
Indeed, I missed the previous part posted here:
/messages/by-id/20180124154548.gmpyvkzlsijren7u@alvherre.pgsql
Alvaro, do you have a strong preference over one or the other?
--
Michael
On Tue, Oct 2, 2018 at 1:37 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 1 Oct 2018, at 01:19, Michael Paquier <michael@paquier.xyz> wrote:
On Sun, Sep 30, 2018 at 10:51:44PM +0900, Michael Paquier wrote:You could have chosen something less complicated, like "ホゲ", which is
the equivalent of "foo" in English. Anyway, non-ASCII characters should
not be included in the final patch.Fixed in the attached v16 revision.
Hi Daniel,
It looks like you missed another case that needs tolerance for late
signal delivery on Windows:
+select pg_cancel_backend(pg_backend_pid());
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.15263
--
Thomas Munro
http://www.enterprisedb.com
On Wed, Oct 03, 2018 at 12:09:54PM +1300, Thomas Munro wrote:
It looks like you missed another case that needs tolerance for late
signal delivery on Windows:+select pg_cancel_backend(pg_backend_pid());
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.15263
Looking at 0001, why are the declarations needed in patch 0002 part of
0001 (see signalfuncs.h)? I think that something like instead the
attached is enough for this part. Daniel, could you confirm?
--
Michael
Attachments:
signalfuncs-refactor.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9dbdc26c9b..bf4619d5fd 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -8,8 +8,8 @@ subdir = src/backend/storage/ipc
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
- procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o sinval.o \
- sinvaladt.o standby.o
+OBJS = signalfuncs.o barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o \
+ pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o shm_mq.o \
+ shm_toc.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
new file mode 100644
index 0000000000..c09a047127
--- /dev/null
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -0,0 +1,216 @@
+/*-------------------------------------------------------------------------
+ *
+ * signalfuncs.c
+ * Functions for signalling backends
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/signalfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "catalog/pg_authid.h"
+#include "funcapi.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 "utils/acl.h"
+#include "utils/builtins.h"
+
+/*
+ * 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.
+ *
+ * Returns 0 on success, 1 on general failure, 2 on normal permission error
+ * and 3 if the caller needs to be a superuser.
+ *
+ * In the event of a general failure (return code 1), a warning message will
+ * be emitted. For permission errors, doing that is the responsibility of
+ * the caller.
+ */
+#define SIGNAL_BACKEND_SUCCESS 0
+#define SIGNAL_BACKEND_ERROR 1
+#define SIGNAL_BACKEND_NOPERMISSION 2
+#define SIGNAL_BACKEND_NOSUPERUSER 3
+static int
+pg_signal_backend(int pid, int sig)
+{
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ /*
+ * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
+ * we reach kill(), a process for which we get a valid proc here might
+ * have terminated on its own. There's no way to acquire a lock on an
+ * arbitrary process to prevent that. But since so far all the callers of
+ * this mechanism involve some request for ending the process anyway, that
+ * it might end on its own first is not a problem.
+ */
+ if (proc == NULL)
+ {
+ /*
+ * This is just a warning so a loop-through-resultset will not abort
+ * if one backend terminated on its own during the run.
+ */
+ ereport(WARNING,
+ (errmsg("PID %d is not a PostgreSQL server process", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ return SIGNAL_BACKEND_NOSUPERUSER;
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ return SIGNAL_BACKEND_NOPERMISSION;
+
+ /*
+ * 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
+ * to kill the wrong thing. Seems near impossible when sequential pid
+ * assignment and wraparound is used. Perhaps it could happen on a system
+ * where pid re-use is randomized. That race condition possibility seems
+ * too unlikely to worry about.
+ */
+
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ if (kill(-pid, sig))
+#else
+ if (kill(pid, sig))
+#endif
+ {
+ /* Again, just a warning to allow loops */
+ ereport(WARNING,
+ (errmsg("could not send signal to process %d: %m", pid)));
+ return SIGNAL_BACKEND_ERROR;
+ }
+ return SIGNAL_BACKEND_SUCCESS;
+}
+
+/*
+ * Signal to cancel a backend process. This is allowed if you are a member of
+ * the role whose process is being canceled.
+ *
+ * Note that only superusers can signal superuser-owned processes.
+ */
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to cancel superuser query"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
+
+ PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
+}
+
+/*
+ * 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)
+{
+ int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
+
+ if (r == SIGNAL_BACKEND_NOSUPERUSER)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ if (r == SIGNAL_BACKEND_NOPERMISSION)
+ ereport(ERROR,
+ (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);
+}
+
+/*
+ * Signal to reload the database configuration
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+ if (kill(PostmasterPid, SIGHUP))
+ {
+ ereport(WARNING,
+ (errmsg("failed to send signal to postmaster: %m")));
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+
+/*
+ * Rotate log file
+ *
+ * This function is kept to support adminpack 1.0.
+ */
+Datum
+pg_rotate_logfile(PG_FUNCTION_ARGS)
+{
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to rotate log files with adminpack 1.0"),
+ errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
+
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
+
+/*
+ * Rotate log file
+ *
+ * Permission checking for this function is managed through the normal
+ * GRANT system.
+ */
+Datum
+pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
+{
+ if (!Logging_collector)
+ {
+ ereport(WARNING,
+ (errmsg("rotation not possible because log collection not active")));
+ PG_RETURN_BOOL(false);
+ }
+
+ SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
+ PG_RETURN_BOOL(true);
+}
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index b24dece23f..6ea3679835 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -15,13 +15,11 @@
#include "postgres.h"
#include <sys/file.h>
-#include <signal.h>
#include <dirent.h>
#include <math.h>
#include <unistd.h>
#include "access/sysattr.h"
-#include "catalog/pg_authid.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -35,13 +33,9 @@
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
-#include "storage/pmsignal.h"
-#include "storage/proc.h"
-#include "storage/procarray.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "tcop/tcopprot.h"
-#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"
@@ -198,194 +192,6 @@ current_query(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
-/*
- * 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.
- *
- * Returns 0 on success, 1 on general failure, 2 on normal permission error
- * and 3 if the caller needs to be a superuser.
- *
- * In the event of a general failure (return code 1), a warning message will
- * be emitted. For permission errors, doing that is the responsibility of
- * the caller.
- */
-#define SIGNAL_BACKEND_SUCCESS 0
-#define SIGNAL_BACKEND_ERROR 1
-#define SIGNAL_BACKEND_NOPERMISSION 2
-#define SIGNAL_BACKEND_NOSUPERUSER 3
-static int
-pg_signal_backend(int pid, int sig)
-{
- PGPROC *proc = BackendPidGetProc(pid);
-
- /*
- * BackendPidGetProc returns NULL if the pid isn't valid; but by the time
- * we reach kill(), a process for which we get a valid proc here might
- * have terminated on its own. There's no way to acquire a lock on an
- * arbitrary process to prevent that. But since so far all the callers of
- * this mechanism involve some request for ending the process anyway, that
- * it might end on its own first is not a problem.
- */
- if (proc == NULL)
- {
- /*
- * This is just a warning so a loop-through-resultset will not abort
- * if one backend terminated on its own during the run.
- */
- ereport(WARNING,
- (errmsg("PID %d is not a PostgreSQL server process", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
-
- /* Only allow superusers to signal superuser-owned backends. */
- if (superuser_arg(proc->roleId) && !superuser())
- return SIGNAL_BACKEND_NOSUPERUSER;
-
- /* Users can signal backends they have role membership in. */
- if (!has_privs_of_role(GetUserId(), proc->roleId) &&
- !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
- return SIGNAL_BACKEND_NOPERMISSION;
-
- /*
- * 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
- * to kill the wrong thing. Seems near impossible when sequential pid
- * assignment and wraparound is used. Perhaps it could happen on a system
- * where pid re-use is randomized. That race condition possibility seems
- * too unlikely to worry about.
- */
-
- /* If we have setsid(), signal the backend's whole process group */
-#ifdef HAVE_SETSID
- if (kill(-pid, sig))
-#else
- if (kill(pid, sig))
-#endif
- {
- /* Again, just a warning to allow loops */
- ereport(WARNING,
- (errmsg("could not send signal to process %d: %m", pid)));
- return SIGNAL_BACKEND_ERROR;
- }
- return SIGNAL_BACKEND_SUCCESS;
-}
-
-/*
- * Signal to cancel a backend process. This is allowed if you are a member of
- * the role whose process is being canceled.
- *
- * Note that only superusers can signal superuser-owned processes.
- */
-Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to cancel superuser query"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a member of the role whose query is being canceled or member of pg_signal_backend"))));
-
- PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
-}
-
-/*
- * 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)
-{
- int r = pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);
-
- if (r == SIGNAL_BACKEND_NOSUPERUSER)
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be a superuser to terminate superuser process"))));
-
- if (r == SIGNAL_BACKEND_NOPERMISSION)
- ereport(ERROR,
- (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);
-}
-
-/*
- * Signal to reload the database configuration
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_reload_conf(PG_FUNCTION_ARGS)
-{
- if (kill(PostmasterPid, SIGHUP))
- {
- ereport(WARNING,
- (errmsg("failed to send signal to postmaster: %m")));
- PG_RETURN_BOOL(false);
- }
-
- PG_RETURN_BOOL(true);
-}
-
-
-/*
- * Rotate log file
- *
- * This function is kept to support adminpack 1.0.
- */
-Datum
-pg_rotate_logfile(PG_FUNCTION_ARGS)
-{
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to rotate log files with adminpack 1.0"),
- errhint("Consider using pg_logfile_rotate(), which is part of core, instead."))));
-
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
-/*
- * Rotate log file
- *
- * Permission checking for this function is managed through the normal
- * GRANT system.
- */
-Datum
-pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
-{
- if (!Logging_collector)
- {
- ereport(WARNING,
- (errmsg("rotation not possible because log collection not active")));
- PG_RETURN_BOOL(false);
- }
-
- SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);
- PG_RETURN_BOOL(true);
-}
-
/* Function to find out which databases make use of a tablespace */
typedef struct
On 4 Oct 2018, at 09:59, Michael Paquier <michael@paquier.xyz> wrote:
On Wed, Oct 03, 2018 at 12:09:54PM +1300, Thomas Munro wrote:
It looks like you missed another case that needs tolerance for late
signal delivery on Windows:+select pg_cancel_backend(pg_backend_pid());
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.15263
Looking at 0001, why are the declarations needed in patch 0002 part of
0001 (see signalfuncs.h)? I think that something like instead the
attached is enough for this part. Daniel, could you confirm?
Yes, you are correct, the signalfuncs.h includes in 0001 are a rebase error
from when I renamed the file. They are not present in the v15 patch but got
introduced in v16 when I clearly wasn’t caffeinated enough to rebase.
cheers ./daniel
On Thu, Oct 04, 2018 at 10:06:06AM +0200, Daniel Gustafsson wrote:
Yes, you are correct, the signalfuncs.h includes in 0001 are a rebase error
from when I renamed the file. They are not present in the v15 patch but got
introduced in v16 when I clearly wasn’t caffeinated enough to rebase.
No problem, thanks for confirming. I have worked a bit more on the
thing, reducing the headers of the new file to a bare minimum, and I
have committed 0001.
--
Michael
On 4 Oct 2018, at 13:00, Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Oct 04, 2018 at 10:06:06AM +0200, Daniel Gustafsson wrote:
Yes, you are correct, the signalfuncs.h includes in 0001 are a rebase error
from when I renamed the file. They are not present in the v15 patch but got
introduced in v16 when I clearly wasn’t caffeinated enough to rebase.No problem, thanks for confirming. I have worked a bit more on the
thing, reducing the headers of the new file to a bare minimum, and I
have committed 0001.
Thanks! Attached is a v17 which rebases the former 0002 patch on top of
current master, along with the test fix for Windows that Thomas reported
upthread (no other changes introduced over earlier versions).
cheers ./daniel
Attachments:
terminate_msg_v17.patchapplication/octet-stream; name=terminate_msg_v17.patch; x-unix-mode=0644Download
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
On Fri, Oct 05, 2018 at 10:11:45AM +0200, Daniel Gustafsson wrote:
Thanks! Attached is a v17 which rebases the former 0002 patch on top of
current master, along with the test fix for Windows that Thomas reported
upthread (no other changes introduced over earlier versions).
Thanks for the new version.
In order to make a test with non-ASCII characters in the error message,
we could use a trick similar to xml.sql: use a function which ignores
the test case if server_encoding is not UTF-8 and use something like
chr() to pass down a messages with non-ASCII characters. Then if the
function catches the error we are good.
+ *pid = slot->src_pid;
+ slot->orig_length = 0;
+ slot->message[0] = '\0';
Message consumption should be the part using memset(0), no? system
calls are avoided within a spinlock section, but memset and strlcpy
should be fast enough. Those are used in WalReceiverMain() for
example.
The facility to store messages should be in its own file, and
signalfuncs.c should depend on it, something like signal_message.c?
+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
A couple of thoughts here:
- More information could make this facility more generic: an elevel to
be able to fully manipulate the custom error message, and a breakpoint
definition. As far as I can see you have two of them in the patch which
are the callers of ConsumeBackendSignalFeedback(). One event cancels
the query, and another terminates it. In both cases, the breakpoint
implies the elevel. So could we have more possible breakpoints possible
and should we try to make this API more pluggable from the start?
- Is orig_length really useful in the data stored? Why not just
warning/noticing the caller defining the message and just store the
message.
So, looking at your patch, I am wondering also about splitting it into
a couple of pieces for clarity:
- The base facility to be able to register and consume messages which
can be attached to a backend slot, and then be accessed by other things.
Let's think about it as a base facility which is useful for extensions
and module developers. If coupled with a hook, it would be actually
possible to scan a backend's slot for some information which could be
consumed afterwards for custom error messages...
- The set of breakpoints which can then be used to define if a given
code path should show up a custom error message. I can think of three
of them: cancel, termination and extension, which is a custom path that
extensions can use. The extension breakpoint would make sense as
something included from the start, could be stored as an integer and I
guess should not be an enum but a set of defined tables (see pgstat.h
for wait event categories for example).
- The set of SQL functions making use of it in-core, in your case these
are the SQL functions for cancellation and termination.
--
Michael
On 9 Oct 2018, at 07:38, Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Oct 05, 2018 at 10:11:45AM +0200, Daniel Gustafsson wrote:
Thanks! Attached is a v17 which rebases the former 0002 patch on top of
current master, along with the test fix for Windows that Thomas reported
upthread (no other changes introduced over earlier versions).Thanks for the new version.
Thanks for reviewing!
In order to make a test with non-ASCII characters in the error message,
we could use a trick similar to xml.sql: use a function which ignores
the test case if server_encoding is not UTF-8 and use something like
chr() to pass down a messages with non-ASCII characters. Then if the
function catches the error we are good.
Thats one approach, do you think it’s worth adding to ensure clipping during
truncation?
+ *pid = slot->src_pid;
+ slot->orig_length = 0;
+ slot->message[0] = '\0';
Message consumption should be the part using memset(0), no? system
calls are avoided within a spinlock section, but memset and strlcpy
should be fast enough. Those are used in WalReceiverMain() for
example.
Good point. IIRC I added it in the setting codepath to avoid memsetting in
case there were no more messages set. That’s an incorrect optimization, so
fixed.
The facility to store messages should be in its own file, and
signalfuncs.c should depend on it, something like signal_message.c?
It was originally in backend_signal.c, and was moved into (what is now)
signalfuncs.c in the refactoring that Alvaro suggested. Re-reading his email
I’m not sure he actually proposed merging this code with the signal code.
Moved the new functionality to signal_message.{ch}.
+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 */ +} BackendSignalFeedbackShmemStructA couple of thoughts here:
- More information could make this facility more generic: an elevel to
be able to fully manipulate the custom error message, and a breakpoint
definition. As far as I can see you have two of them in the patch which
are the callers of ConsumeBackendSignalFeedback(). One event cancels
the query, and another terminates it. In both cases, the breakpoint
implies the elevel. So could we have more possible breakpoints possible
and should we try to make this API more pluggable from the start?
I’m not sure I follow, can you elaborate on this?
- Is orig_length really useful in the data stored? Why not just
warning/noticing the caller defining the message and just store the
message.
The caller is being notified, but the original length is returned such that the
consumer can format the message with the truncation in mind. In postgres.c we
currently do:
ereport(FATAL,
(errcode(sqlerrcode),
errmsg("%s%s",
buffer, (len > sizeof(buffer) ? "..." : "")),
errdetail("terminating connection due to administrator command by process %d",
pid)));
If that’s not deemed of value to keep, then orig_length can be dropped.
So, looking at your patch, I am wondering also about splitting it into
a couple of pieces for clarity:
- The base facility to be able to register and consume messages which
can be attached to a backend slot, and then be accessed by other things.
Let's think about it as a base facility which is useful for extensions
and module developers. If coupled with a hook, it would be actually
possible to scan a backend's slot for some information which could be
consumed afterwards for custom error messages...
- The set of breakpoints which can then be used to define if a given
code path should show up a custom error message. I can think of three
of them: cancel, termination and extension, which is a custom path that
extensions can use. The extension breakpoint would make sense as
something included from the start, could be stored as an integer and I
guess should not be an enum but a set of defined tables (see pgstat.h
for wait event categories for example).
- The set of SQL functions making use of it in-core, in your case these
are the SQL functions for cancellation and termination.
This does not sound like a bad idea to me. Is that something you are planning
to do or do you want me to cut the patch up into pieces? Just want to avoid
stepping on each others toes if you are already thinking/poking at this.
Attached is a v18 with the above changes.
cheers ./daniel
Attachments:
terminate_msg_v18.patchapplication/octet-stream; name=terminate_msg_v18.patch; x-unix-mode=0644Download
From 45102679e15becbf28186b1e59d79aab40b30ee7 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/Makefile | 2 +-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/signal_message.c | 272 ++++++++++++++++++++++++++++++
src/backend/storage/ipc/signalfuncs.c | 66 +++++++-
src/backend/tcop/postgres.c | 61 ++++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/signal_message.h | 27 +++
src/test/regress/expected/admin_funcs.out | 28 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 11 ++
13 files changed, 480 insertions(+), 19 deletions(-)
create mode 100644 src/backend/storage/ipc/signal_message.c
create mode 100644 src/include/storage/signal_message.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 5193df3366..b0b85289d6 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 0c1bcebb0d..44a5fc4afd 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/Makefile b/src/backend/storage/ipc/Makefile
index 49e7c9f15e..fd628af59f 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -10,6 +10,6 @@ include $(top_builddir)/src/Makefile.global
OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o signalfuncs.o \
- sinval.o sinvaladt.o standby.o
+ signal_message.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..fb7c1a0b49 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/signal_message.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/signal_message.c b/src/backend/storage/ipc/signal_message.c
new file mode 100644
index 0000000000..01f8ebcb17
--- /dev/null
+++ b/src/backend/storage/ipc/signal_message.c
@@ -0,0 +1,272 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_message.c
+ * Functions for sending a message to a signalled backend
+ *
+ * This file contains routines to 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.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/signal_message.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
+#include "storage/signal_message.h"
+#include "storage/spin.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);
+
+/*
+ * 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;
+ }
+
+ 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 and 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;
+ /* Avoid risking to leak any part of a previously set message */
+ MemSet(slot->message, '\0', sizeof(slot->message));
+ slot->sqlerrcode = 0;
+ SpinLockRelease(&slot->mutex);
+ }
+
+ return msg_length;
+}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 878665b045..f0c02c7008 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -19,9 +19,11 @@
#include "catalog/pg_authid.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/signal_message.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -29,9 +31,11 @@
/*
* 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 +49,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 +81,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 +138,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 +174,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 +198,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);
}
/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e4c6e3d406..fad63f024a 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/signal_message.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..4382691420 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/signal_message.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 038a11d6cd..587fbd8529 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5973,10 +5973,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/signal_message.h b/src/include/storage/signal_message.h
new file mode 100644
index 0000000000..11ca34d24b
--- /dev/null
+++ b/src/include/storage/signal_message.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_message.h
+ * Declarations for sending a message to a signalled backend
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/storage/signal_message.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SIGNAL_MESSAGE_H
+#define SIGNAL_MESSAGE_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 /* SIGNAL_MESSAGE_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
On Wed, Oct 10, 2018 at 02:20:53PM +0200, Daniel Gustafsson wrote:
On 9 Oct 2018, at 07:38, Michael Paquier <michael@paquier.xyz> wrote:
In order to make a test with non-ASCII characters in the error message,
we could use a trick similar to xml.sql: use a function which ignores
the test case if server_encoding is not UTF-8 and use something like
chr() to pass down a messages with non-ASCII characters. Then if the
function catches the error we are good.Thats one approach, do you think it’s worth adding to ensure clipping during
truncation?
I am not exactly sure what you mean here.
The facility to store messages should be in its own file, and
signalfuncs.c should depend on it, something like signal_message.c?It was originally in backend_signal.c, and was moved into (what is now)
signalfuncs.c in the refactoring that Alvaro suggested. Re-reading
his email I’m not sure he actually proposed merging this code with the
signal code.
Indeed, you could understand the past suggestion both ways. From what I
can see, there is merit in keeping the SQL-callable functions working on
signals into their own file. The message capacity that you are
proposing here should on their own, and...
Moved the new functionality to signal_message.{ch}.
That's actually a much better name than anything I could think of.
- More information could make this facility more generic: an elevel to
be able to fully manipulate the custom error message, and a breakpoint
definition. As far as I can see you have two of them in the patch which
are the callers of ConsumeBackendSignalFeedback(). One event cancels
the query, and another terminates it. In both cases, the breakpoint
implies the elevel. So could we have more possible breakpoints possible
and should we try to make this API more pluggable from the start?I’m not sure I follow, can you elaborate on this?
Sure. From what I can see in your patch is that you want to generate in
some code paths an ereport() call with a given message string. As far
as I can see, you can decide three things with what's available:
- errcode
- errstring
- the moment you want the ereport() to be generated.
What I am suggesting here is to let callers extend the possibilities a
bit more with:
- elevel
- errdetail
This way, you can override code paths with a more custom experience.
The thing could break easily Postgres, as you should not really use a
non-FATAL elevel when terminating a session, but having a flexible API
will allow to not come back to it in the future. We may also want to
have ConsumeBackendSignalFeedback() call ereport() by itself with all
the details available in the data registered.
The main take on your feature is that it is more flexible than the elog
hook, as you can enforce custom strings at function call, and don't need
to do some weird hacks in plugins in need of manipulating the error.
- Is orig_length really useful in the data stored? Why not just
warning/noticing the caller defining the message and just store the
message.The caller is being notified, but the original length is returned such that the
consumer can format the message with the truncation in mind. In postgres.c we
currently do:ereport(FATAL,
(errcode(sqlerrcode),
errmsg("%s%s",
buffer, (len > sizeof(buffer) ? "..." : "")),
errdetail("terminating connection due to administrator command by process %d",
pid)));
The goal is to generate an elog() at the end, so I can see your point,
not sure if that's worth the complication though..
So, looking at your patch, I am wondering also about splitting it into
a couple of pieces for clarity:
- The base facility to be able to register and consume messages which
can be attached to a backend slot, and then be accessed by other things.
Let's think about it as a base facility which is useful for extensions
and module developers. If coupled with a hook, it would be actually
possible to scan a backend's slot for some information which could be
consumed afterwards for custom error messages...
- The set of breakpoints which can then be used to define if a given
code path should show up a custom error message. I can think of three
of them: cancel, termination and extension, which is a custom path that
extensions can use. The extension breakpoint would make sense as
something included from the start, could be stored as an integer and I
guess should not be an enum but a set of defined tables (see pgstat.h
for wait event categories for example).
- The set of SQL functions making use of it in-core, in your case these
are the SQL functions for cancellation and termination.This does not sound like a bad idea to me. Is that something you are
planning to do or do you want me to cut the patch up into pieces?
Just want to avoid stepping on each others toes if you are already
thinking/poking at this.
Those are my first impressions about the patch after looking at the
code so I have not done any code diving. The more pieces we are able to
break this stuff into, the more likely it is easy to review and to get
committed. From what I can see the main idea can be split into more
sub-concepts.
--
Michael
On 11 Oct 2018, at 03:29, Michael Paquier <michael@paquier.xyz> wrote:
Hi!,
Thanks for reviewing this patch, and sorry for having been slow lately.
On Wed, Oct 10, 2018 at 02:20:53PM +0200, Daniel Gustafsson wrote:
On 9 Oct 2018, at 07:38, Michael Paquier <michael@paquier.xyz> wrote:
In order to make a test with non-ASCII characters in the error message,
we could use a trick similar to xml.sql: use a function which ignores
the test case if server_encoding is not UTF-8 and use something like
chr() to pass down a messages with non-ASCII characters. Then if the
function catches the error we are good.Thats one approach, do you think it’s worth adding to ensure clipping during
truncation?I am not exactly sure what you mean here.
The test was added to the patch as a reviewer found a bug in an early version
where it didn’t treat mb characters properly, it was using strlen() and prone
to clipping an mb char in half. The test was added to the patch to show the
fix to the patch review process, but that doesn’t necessarily mean it’s deemed
an interesting enough case to have a test in check/installcheck for. I don’t
have strong opinions, other than trying to not add uninteresting tests as they
do carry a cost.
The facility to store messages should be in its own file, and
signalfuncs.c should depend on it, something like signal_message.c?It was originally in backend_signal.c, and was moved into (what is now)
signalfuncs.c in the refactoring that Alvaro suggested. Re-reading
his email I’m not sure he actually proposed merging this code with the
signal code.Indeed, you could understand the past suggestion both ways. From what I
can see, there is merit in keeping the SQL-callable functions working on
signals into their own file. The message capacity that you are
proposing here should on their own, and...Moved the new functionality to signal_message.{ch}.
That's actually a much better name than anything I could think of.
Actually it isn’t, it was your suggestion =)
- More information could make this facility more generic: an elevel to
be able to fully manipulate the custom error message, and a breakpoint
definition. As far as I can see you have two of them in the patch which
are the callers of ConsumeBackendSignalFeedback(). One event cancels
the query, and another terminates it. In both cases, the breakpoint
implies the elevel. So could we have more possible breakpoints possible
and should we try to make this API more pluggable from the start?I’m not sure I follow, can you elaborate on this?
Sure. From what I can see in your patch is that you want to generate in
some code paths an ereport() call with a given message string. As far
as I can see, you can decide three things with what's available:
- errcode
- errstring
- the moment you want the ereport() to be generated.What I am suggesting here is to let callers extend the possibilities a
bit more with:
- elevel
- errdetail
This way, you can override code paths with a more custom experience.
The thing could break easily Postgres, as you should not really use a
non-FATAL elevel when terminating a session, but having a flexible API
will allow to not come back to it in the future. We may also want to
have ConsumeBackendSignalFeedback() call ereport() by itself with all
the details available in the data registered.
I’ve added elevel to the API, but I’m not sure if keeping an additional buffer
for errdetail is worth it since it probably wouldn’t be used that often?
The main take on your feature is that it is more flexible than the elog
hook, as you can enforce custom strings at function call, and don't need
to do some weird hacks in plugins in need of manipulating the error.- Is orig_length really useful in the data stored? Why not just
warning/noticing the caller defining the message and just store the
message.The caller is being notified, but the original length is returned such that the
consumer can format the message with the truncation in mind. In postgres.c we
currently do:ereport(FATAL,
(errcode(sqlerrcode),
errmsg("%s%s",
buffer, (len > sizeof(buffer) ? "..." : "")),
errdetail("terminating connection due to administrator command by process %d",
pid)));The goal is to generate an elog() at the end, so I can see your point,
not sure if that's worth the complication though..
Since I wrote the code, I think it’s worth it as a nice-to-have rather than as
a pure necessity. If noone else sees the value then we’ll rip it out of the
patch.
So, looking at your patch, I am wondering also about splitting it into
a couple of pieces for clarity:
- The base facility to be able to register and consume messages which
can be attached to a backend slot, and then be accessed by other things.
Let's think about it as a base facility which is useful for extensions
and module developers. If coupled with a hook, it would be actually
possible to scan a backend's slot for some information which could be
consumed afterwards for custom error messages...
- The set of breakpoints which can then be used to define if a given
code path should show up a custom error message. I can think of three
of them: cancel, termination and extension, which is a custom path that
extensions can use. The extension breakpoint would make sense as
something included from the start, could be stored as an integer and I
guess should not be an enum but a set of defined tables (see pgstat.h
for wait event categories for example).
- The set of SQL functions making use of it in-core, in your case these
are the SQL functions for cancellation and termination.
Is a hook for extensions needed, as they too can drain the message via the
Consume function? Where if so do you reckon it should go?
This does not sound like a bad idea to me. Is that something you are
planning to do or do you want me to cut the patch up into pieces?
Just want to avoid stepping on each others toes if you are already
thinking/poking at this.Those are my first impressions about the patch after looking at the
code so I have not done any code diving. The more pieces we are able to
break this stuff into, the more likely it is easy to review and to get
committed. From what I can see the main idea can be split into more
sub-concepts.
I’ve split the patch into two logical parts, the signalling functionality and
the userfacing terminate/cancel part. For extra clarity I’ve also included the
full v19 patch, in case you prefer that instead when seeing the two.
cheers ./daniel
Attachments:
0002-Allow-pg_-cancel-terminate-_backend-to-pass-v19.patchapplication/octet-stream; name=0002-Allow-pg_-cancel-terminate-_backend-to-pass-v19.patch; x-unix-mode=0644Download
From 70065fb5caeba8592f5de711b4155abbc86f2ed8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 31 Oct 2018 00:19:10 +0100
Subject: [PATCH 2/2] Allow pg_{cancel|terminate}_backend to pass message
This adds an optional message parameter to pg_cancel_backend() and
pg_terminate_backend(), whereby the administrator can inform the
user why the query was cancelled or connection terminated. A small
testcase is added to excercise the new codepath.
---
doc/src/sgml/func.sgml | 13 +++++-
src/backend/catalog/system_views.sql | 8 ++++
src/backend/storage/ipc/signalfuncs.c | 66 +++++++++++++++++++++++++++----
src/backend/tcop/postgres.c | 61 +++++++++++++++++++++++++---
src/include/catalog/pg_proc.dat | 4 +-
src/test/regress/expected/admin_funcs.out | 28 +++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 11 ++++++
8 files changed, 175 insertions(+), 18 deletions(-)
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 10816f556b..e334e77ae7 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 53ddc593a8..d164535beb 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1032,6 +1032,14 @@ CREATE OR REPLACE FUNCTION
RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_promote'
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/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 878665b045..f0c02c7008 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -19,9 +19,11 @@
#include "catalog/pg_authid.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/signal_message.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -29,9 +31,11 @@
/*
* 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 +49,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 +81,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 +138,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 +174,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 +198,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);
}
/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a3b9757565..50c56e0618 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/signal_message.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -3013,9 +3014,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, NULL);
+ 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)
{
@@ -3126,9 +3151,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, NULL);
+ 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/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4026018ba9..11b8830217 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5799,10 +5799,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/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 b5e15501dd..5034cc8c20 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
0001-Add-infrastructure-for-signalling-backends-with-v19.patchapplication/octet-stream; name=0001-Add-infrastructure-for-signalling-backends-with-v19.patch; x-unix-mode=0644Download
From 119d7ed341477b1c5f644ae289bf8cb01ad5bc5a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 31 Oct 2018 00:12:39 +0100
Subject: [PATCH 1/2] Add infrastructure for signalling backends with payload
This adds an API to include a payload consisting of a message, an
elevel and a custom sqlerrcode, when signalling a backend. The main
usecase is to be able to include context to the user as to why the
connection is terminated or query cancelled.
---
src/backend/storage/ipc/Makefile | 2 +-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/signal_message.c | 286 +++++++++++++++++++++++++++++++
src/backend/utils/init/postinit.c | 2 +
src/include/storage/signal_message.h | 29 ++++
5 files changed, 321 insertions(+), 1 deletion(-)
create mode 100644 src/backend/storage/ipc/signal_message.c
create mode 100644 src/include/storage/signal_message.h
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 49e7c9f15e..fd628af59f 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -10,6 +10,6 @@ include $(top_builddir)/src/Makefile.global
OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o signalfuncs.o \
- sinval.o sinvaladt.o standby.o
+ signal_message.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..fb7c1a0b49 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/signal_message.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/signal_message.c b/src/backend/storage/ipc/signal_message.c
new file mode 100644
index 0000000000..596f9584cc
--- /dev/null
+++ b/src/backend/storage/ipc/signal_message.c
@@ -0,0 +1,286 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_message.c
+ * Functions for sending a message to a signalled backend
+ *
+ * This file contains routines to 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.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/signal_message.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
+#include "storage/signal_message.h"
+#include "storage/spin.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 */
+ int elevel; /* elevel 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,
+ int elevel);
+
+/*
+ * 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->elevel = 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->elevel = 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,
+ ERROR);
+}
+
+/*
+ * 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,
+ FATAL);
+}
+
+/*
+ * Set both a message, elevel and a sqlerrcode for use when signalling the
+ * backend with the specified pid.
+ */
+int
+SetBackendSignalFeedback(pid_t backend_pid, char *message, int sqlerrcode,
+ int elevel)
+{
+ return backend_feedback(backend_pid, message, sqlerrcode, elevel);
+}
+
+/*
+ * 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 elevel)
+{
+ 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;
+ }
+
+ memcpy(slot->message, message, len);
+ slot->orig_length = pg_mbstrlen(message);
+ slot->sqlerrcode = sqlerrcode;
+ slot->elevel = elevel;
+ 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 and 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_MSG 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, int *elevel)
+{
+ volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot;
+ int msg_length = 0;
+
+ if (slot != NULL && slot->orig_length > 0)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (buffer && buf_len)
+ strlcpy(buffer, (const char *) slot->message, buf_len);
+ msg_length = slot->orig_length;
+ if (sqlerrcode)
+ *sqlerrcode = slot->sqlerrcode;
+ if (pid)
+ *pid = slot->src_pid;
+ if (elevel)
+ *elevel = slot->elevel;
+
+ slot->orig_length = 0;
+ /* Avoid risking to leak any part of a previously set message */
+ MemSet(slot->message, '\0', sizeof(slot->message));
+ slot->sqlerrcode = 0;
+ slot->elevel = 0;
+ SpinLockRelease(&slot->mutex);
+ }
+
+ return msg_length;
+}
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 4f1d2a0d28..4382691420 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/signal_message.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/storage/signal_message.h b/src/include/storage/signal_message.h
new file mode 100644
index 0000000000..e0f834f2f5
--- /dev/null
+++ b/src/include/storage/signal_message.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_message.h
+ * Declarations for sending a message to a signalled backend
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/storage/signal_message.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SIGNAL_MESSAGE_H
+#define SIGNAL_MESSAGE_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, int elevel);
+extern bool HasBackendSignalFeedback(void);
+extern int ConsumeBackendSignalFeedback(char *msg, size_t len, int *sqlerrcode,
+ pid_t *pid, int *elevel);
+
+#endif /* SIGNAL_MESSAGE_H */
--
2.14.1.145.gb3622a4ee
terminate_msg_v19.patchapplication/octet-stream; name=terminate_msg_v19.patch; x-unix-mode=0644Download
From 9af3133898984acee754f71537f8a59e0addc253 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/Makefile | 2 +-
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/signal_message.c | 286 ++++++++++++++++++++++++++++++
src/backend/storage/ipc/signalfuncs.c | 66 ++++++-
src/backend/tcop/postgres.c | 61 ++++++-
src/backend/utils/init/postinit.c | 2 +
src/include/catalog/pg_proc.dat | 4 +-
src/include/storage/signal_message.h | 29 +++
src/test/regress/expected/admin_funcs.out | 28 +++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/admin_funcs.sql | 11 ++
13 files changed, 496 insertions(+), 19 deletions(-)
create mode 100644 src/backend/storage/ipc/signal_message.c
create mode 100644 src/include/storage/signal_message.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 10816f556b..e334e77ae7 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 53ddc593a8..d164535beb 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1032,6 +1032,14 @@ CREATE OR REPLACE FUNCTION
RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_promote'
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/Makefile b/src/backend/storage/ipc/Makefile
index 49e7c9f15e..fd628af59f 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -10,6 +10,6 @@ include $(top_builddir)/src/Makefile.global
OBJS = barrier.o dsm_impl.o dsm.o ipc.o ipci.o latch.o pmsignal.o procarray.o \
procsignal.o shmem.o shmqueue.o shm_mq.o shm_toc.o signalfuncs.o \
- sinval.o sinvaladt.o standby.o
+ signal_message.o sinval.o sinvaladt.o standby.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 0c86a581c0..fb7c1a0b49 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/signal_message.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/signal_message.c b/src/backend/storage/ipc/signal_message.c
new file mode 100644
index 0000000000..596f9584cc
--- /dev/null
+++ b/src/backend/storage/ipc/signal_message.c
@@ -0,0 +1,286 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_message.c
+ * Functions for sending a message to a signalled backend
+ *
+ * This file contains routines to 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.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/signal_message.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
+#include "storage/signal_message.h"
+#include "storage/spin.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 */
+ int elevel; /* elevel 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,
+ int elevel);
+
+/*
+ * 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->elevel = 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->elevel = 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,
+ ERROR);
+}
+
+/*
+ * 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,
+ FATAL);
+}
+
+/*
+ * Set both a message, elevel and a sqlerrcode for use when signalling the
+ * backend with the specified pid.
+ */
+int
+SetBackendSignalFeedback(pid_t backend_pid, char *message, int sqlerrcode,
+ int elevel)
+{
+ return backend_feedback(backend_pid, message, sqlerrcode, elevel);
+}
+
+/*
+ * 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 elevel)
+{
+ 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;
+ }
+
+ memcpy(slot->message, message, len);
+ slot->orig_length = pg_mbstrlen(message);
+ slot->sqlerrcode = sqlerrcode;
+ slot->elevel = elevel;
+ 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 and 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_MSG 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, int *elevel)
+{
+ volatile BackendSignalFeedbackShmemStruct *slot = MyCancelSlot;
+ int msg_length = 0;
+
+ if (slot != NULL && slot->orig_length > 0)
+ {
+ SpinLockAcquire(&slot->mutex);
+ if (buffer && buf_len)
+ strlcpy(buffer, (const char *) slot->message, buf_len);
+ msg_length = slot->orig_length;
+ if (sqlerrcode)
+ *sqlerrcode = slot->sqlerrcode;
+ if (pid)
+ *pid = slot->src_pid;
+ if (elevel)
+ *elevel = slot->elevel;
+
+ slot->orig_length = 0;
+ /* Avoid risking to leak any part of a previously set message */
+ MemSet(slot->message, '\0', sizeof(slot->message));
+ slot->sqlerrcode = 0;
+ slot->elevel = 0;
+ SpinLockRelease(&slot->mutex);
+ }
+
+ return msg_length;
+}
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 878665b045..f0c02c7008 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -19,9 +19,11 @@
#include "catalog/pg_authid.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/signal_message.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -29,9 +31,11 @@
/*
* 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 +49,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 +81,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 +138,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 +174,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 +198,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);
}
/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a3b9757565..50c56e0618 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/signal_message.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -3013,9 +3014,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, NULL);
+ 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)
{
@@ -3126,9 +3151,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, NULL);
+ 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..4382691420 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/signal_message.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 4026018ba9..11b8830217 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5799,10 +5799,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/signal_message.h b/src/include/storage/signal_message.h
new file mode 100644
index 0000000000..e0f834f2f5
--- /dev/null
+++ b/src/include/storage/signal_message.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_message.h
+ * Declarations for sending a message to a signalled backend
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ *
+ * src/include/storage/signal_message.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SIGNAL_MESSAGE_H
+#define SIGNAL_MESSAGE_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, int elevel);
+extern bool HasBackendSignalFeedback(void);
+extern int ConsumeBackendSignalFeedback(char *msg, size_t len, int *sqlerrcode,
+ pid_t *pid, int *elevel);
+
+#endif /* SIGNAL_MESSAGE_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 b5e15501dd..5034cc8c20 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
Daniel Gustafsson <daniel@yesql.se> writes:
I’ve split the patch into two logical parts, the signalling functionality and
the userfacing terminate/cancel part. For extra clarity I’ve also included the
full v19 patch, in case you prefer that instead when seeing the two.
I'm a bit befuddled by this patch, or at least the proposed test cases.
Those show no proof that the feature actually works, ie, delivers a
message to the target backend. Also, what am I to make of the test cases
involving NULL arguments?
Personally, rather than messing around with defaulted arguments and
detecting nulls at runtime, I'd make two C functions that are both strict.
The signal_message stuff seems both overly complicated and overly fragile.
You have, for example, largely ignored the coding rule that says to have
only straight-line code within a spinlock hold. I am also not very
enamored of setting up Yet Another per-backend data structure in shared
memory, especially one that can so easily get out of sync with the
ProcArray. If we're going to expend dedicated shared memory on this
feature, let's just add the necessary fields to the PGPROC structs.
That would also provide some opportunity to interlock things in a way that
would fix the race conditions, ie, once we've found the relevant PGPROC
entry, hold a lock on it till we've inserted the message and sent the
signal.
I don't especially like orig_length: that information is of no use
to the recipient backend, and what the field is actually being used
as, ie a boolean "slot is full" indicator, is nowhere to be guessed
from the field's documentation.
The current patch fails to apply against parallel_schedule, which
reminds me that you forgot to include an update to serial_schedule.
regards, tom lane
On Mon, Nov 12, 2018 at 8:36 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Daniel Gustafsson <daniel@yesql.se> writes:
I’ve split the patch into two logical parts, the signalling functionality and
the userfacing terminate/cancel part. For extra clarity I’ve also included the
full v19 patch, in case you prefer that instead when seeing the two.
Just for the records, cfbot complains during the compilation:
option.c: In function ‘parseCommandLine’:
option.c:265:8: error: ignoring return value of ‘getcwd’, declared
with attribute warn_unused_result [-Werror=unused-result]
getcwd(default_sockdir, MAXPGPATH);
^
On Fri, Nov 30, 2018 at 06:02:15PM +0100, Dmitry Dolgov wrote:
Just for the records, cfbot complains during the compilation:
option.c: In function ‘parseCommandLine’:
option.c:265:8: error: ignoring return value of ‘getcwd’, declared
with attribute warn_unused_result [-Werror=unused-result]
getcwd(default_sockdir, MAXPGPATH);
^
The patch has been marked as returned with feedback.
--
Michael