public inbox for [email protected]  
help / color / mirror / Atom feed
Additional message in pg_terminate_backend
15+ messages / 6 participants
[nested] [flat]

* Additional message in pg_terminate_backend
@ 2025-12-13 07:44  Roman Khapov <[email protected]>
  0 siblings, 2 replies; 15+ messages in thread

From: Roman Khapov @ 2025-12-13 07:44 UTC (permalink / raw)
  To: [email protected]

Hi hackers!

Recently I started working on patch for adding additional message from admin 
in pg_terminate_backend for one of our greenplum fork. The main idea is that there must be 
done little investigation every time you see 'FATAL:  terminating connection due to administrator command’ to understand the reason, especially in cases where connection was terminated by another
user. So it was decided to create some new functions, that allows to terminate connection with
additional message.

I did POC patches with the next main ideas:
 - lets add termReasonStr field in every PGPROC, that field can be used in ProcessInterrupts() 
 - implementation of pg_terminate_backend/pg_cancel_backend should be accessible from extensions, so lets move it in pg_terminate_backend_impl/pg_cancel_backend_impl and add definitions for it somewhere
 - write simple extensions, which defines functions like pg_terminate_backend_msg, that sets termReasonStr and calls pg_terminate_backend_impl

After patch and added extension, it is possible to do smth like:
postgres=# select pg_terminate_backend_msg(pg_backend_pid(), 0, ’the message');
FATAL:  terminating connection due to administrator command: the message

The general question I want to ask: can this patches be useful for vanilla PostgreSQL?

If so, there are some questions about patch improvements:
 - maybe the message can be delivered to backend by some other way than the field in struct PGPROC?
termReasonStr field consumes some shared memory and are used in rare cases
 - names of all new funcs/fields/etc should be changed to some better names
 - new file signalfuncs.h seems like too complicated solutions to define *_impl functions, it can be done in
some other files? 

-----
Best regards,
Roman Khapov



Attachments:

  [application/octet-stream] 0001-termination-msg-in-PGPROC.patch (5.6K, 2-0001-termination-msg-in-PGPROC.patch)
  download | inline diff:
From 9ddd96f2da2332ef019c6da5825ae38cc820f2ac Mon Sep 17 00:00:00 2001
From: roman khapov <[email protected]>
Date: Sat, 13 Dec 2025 06:55:28 +0000
Subject: [PATCH 1/2] termination msg in PGPROC

This commit adds message in PGPROC, that can be set
before terminating/cancelling connection, in order to add
this message when backend is responding to client with
'FATAL: terminating connection..' or 'ERROR: cancelling statement..'

For now there is no separated functions to perform termination with msg,
but that functions can be simply added in extension. This requires
implementation of pg_terminate_backend/pg_cancel_backend to be accessible
from extenions, so pg_terminate_backend_impl and pg_cancel_backend_impl was
introduced as public functions.

Signed-off-by: roman khapov <[email protected]>
---
 src/backend/storage/ipc/signalfuncs.c | 32 +++++++++++++++++++--------
 src/backend/tcop/postgres.c           | 26 ++++++++++++++++++----
 src/include/storage/proc.h            |  6 +++++
 src/include/storage/signalfuncs.h     | 13 +++++++++++
 4 files changed, 64 insertions(+), 13 deletions(-)
 create mode 100644 src/include/storage/signalfuncs.h

diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index a3a670ba24..cd7eeeb9e3 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -22,6 +22,7 @@
 #include "postmaster/syslogger.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
+#include "storage/signalfuncs.h"
 #include "storage/procarray.h"
 #include "utils/acl.h"
 #include "utils/fmgrprotos.h"
@@ -133,9 +134,9 @@ pg_signal_backend(int pid, int sig)
  * Note that only superusers can signal superuser-owned processes.
  */
 Datum
-pg_cancel_backend(PG_FUNCTION_ARGS)
+pg_cancel_backend_impl(int pid)
 {
-	int			r = pg_signal_backend(PG_GETARG_INT32(0), SIGINT);
+	int			r = pg_signal_backend(pid, SIGINT);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -161,6 +162,12 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+	return pg_cancel_backend_impl(PG_GETARG_INT32(0));
+}
+
 /*
  * Wait until there is no backend process with the given PID and return true.
  * On timeout, a warning is emitted and false is returned.
@@ -234,14 +241,9 @@ pg_wait_until_termination(int pid, int64 timeout)
  * Note that only superusers can signal superuser-owned processes.
  */
 Datum
-pg_terminate_backend(PG_FUNCTION_ARGS)
+pg_terminate_backend_impl(int pid, int timeout)
 {
-	int			pid;
-	int			r;
-	int			timeout;		/* milliseconds */
-
-	pid = PG_GETARG_INT32(0);
-	timeout = PG_GETARG_INT64(1);
+	int r;
 
 	if (timeout < 0)
 		ereport(ERROR,
@@ -278,6 +280,18 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum
+pg_terminate_backend(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	int			timeout;		/* milliseconds */
+
+	pid = PG_GETARG_INT32(0);
+	timeout = PG_GETARG_INT64(1);
+
+	return pg_terminate_backend_impl(pid, timeout);
+}
+
 /*
  * Signal to reload the database configuration
  *
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7dd75a490a..bb43d2db7e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3356,9 +3356,17 @@ ProcessInterrupts(void)
 			proc_exit(0);
 		}
 		else
-			ereport(FATAL,
+		{
+			if (MyProc->termReasonStr[0] == '\0')
+				ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
+			else
+				ereport(FATAL,
+					(errcode(ERRCODE_ADMIN_SHUTDOWN),
+					 errmsg("terminating connection due to administrator command: %s",
+							MyProc->termReasonStr)));
+		}
 	}
 
 	if (CheckClientConnectionPending)
@@ -3466,9 +3474,19 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+			if (MyProc->termReasonStr[0] == '\0')
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request")));
+			else
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request: %s",
+							MyProc->termReasonStr)));
+
+				memset(MyProc->termReasonStr, 0, sizeof(MyProc->termReasonStr));
+			}
 		}
 	}
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index c6f5ebceef..f0f2bc4b93 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -77,6 +77,9 @@ struct XidCache
  */
 #define		PROC_XMIN_FLAGS (PROC_IN_VACUUM | PROC_IN_SAFE_IC)
 
+/* including the termination null byte */
+#define		PROC_TERM_REASON_MAX_LEN 32
+
 /*
  * We allow a limited number of "weak" relation locks (AccessShareLock,
  * RowShareLock, RowExclusiveLock) to be recorded in the PGPROC structure
@@ -301,6 +304,9 @@ struct PGPROC
 
 	uint32		wait_event_info;	/* proc's wait information */
 
+	/* additional info when termination signal sending */
+	char		termReasonStr[PROC_TERM_REASON_MAX_LEN];
+
 	/* Support for group transaction status update. */
 	bool		clogGroupMember;	/* true, if member of clog group */
 	pg_atomic_uint32 clogGroupNext; /* next clog group member */
diff --git a/src/include/storage/signalfuncs.h b/src/include/storage/signalfuncs.h
new file mode 100644
index 0000000000..359b3be67b
--- /dev/null
+++ b/src/include/storage/signalfuncs.h
@@ -0,0 +1,13 @@
+
+#ifndef SIGNALFUNCS_H
+#define SIGNALFUNCS_H
+
+#include <postgres.h>
+
+Datum
+pg_terminate_backend_impl(int pid, int timeout);
+
+Datum
+pg_cancel_backend_impl(int pid);
+
+#endif /* SIGNALFUNCS_H */
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] 0002-pg_term_reason-POC-for-pg_terminate_backend_msg.patch (8.1K, 3-0002-pg_term_reason-POC-for-pg_terminate_backend_msg.patch)
  download | inline diff:
From 5b15c5938e8641d0103d225246dbe03911e0ea3f Mon Sep 17 00:00:00 2001
From: roman khapov <[email protected]>
Date: Sat, 13 Dec 2025 07:06:00 +0000
Subject: [PATCH 2/2] pg_term_reason: POC for pg_terminate_backend_msg

This commits adds proof-of-concept implementation
for extensions, that defines some function to terminate
connections with additional message from admin.

Ex.:
postgres=# create extension pg_term_reason;
CREATE EXTENSION

postgres=# select pg_terminate_backend_msg(pg_backend_pid(), 0, 'Have you seen my coffee cup?');
FATAL:  terminating connection due to administrator command: Have you seen my coffee cup?

Signed-off-by: roman khapov <[email protected]>
---
 contrib/Makefile                              |  1 +
 contrib/meson.build                           |  1 +
 contrib/pg_term_reason/.gitignore             |  4 +
 contrib/pg_term_reason/Makefile               | 22 +++++
 contrib/pg_term_reason/meson.build            | 23 +++++
 .../pg_term_reason/pg_term_reason--1.0.sql    | 14 +++
 contrib/pg_term_reason/pg_term_reason.c       | 89 +++++++++++++++++++
 contrib/pg_term_reason/pg_term_reason.control |  5 ++
 contrib/pg_term_reason/t/001_basic.pl         | 28 ++++++
 9 files changed, 187 insertions(+)
 create mode 100644 contrib/pg_term_reason/.gitignore
 create mode 100644 contrib/pg_term_reason/Makefile
 create mode 100644 contrib/pg_term_reason/meson.build
 create mode 100644 contrib/pg_term_reason/pg_term_reason--1.0.sql
 create mode 100644 contrib/pg_term_reason/pg_term_reason.c
 create mode 100644 contrib/pg_term_reason/pg_term_reason.control
 create mode 100644 contrib/pg_term_reason/t/001_basic.pl

diff --git a/contrib/Makefile b/contrib/Makefile
index 2f0a88d3f7..82eb2c0d12 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -37,6 +37,7 @@ SUBDIRS = \
 		pg_prewarm	\
 		pg_stat_statements \
 		pg_surgery	\
+		pg_term_reason \
 		pg_trgm		\
 		pgrowlocks	\
 		pgstattuple	\
diff --git a/contrib/meson.build b/contrib/meson.build
index ed30ee7d63..398d58d71e 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -53,6 +53,7 @@ subdir('pgrowlocks')
 subdir('pg_stat_statements')
 subdir('pgstattuple')
 subdir('pg_surgery')
+subdir('pg_term_reason')
 subdir('pg_trgm')
 subdir('pg_visibility')
 subdir('pg_walinspect')
diff --git a/contrib/pg_term_reason/.gitignore b/contrib/pg_term_reason/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/contrib/pg_term_reason/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/pg_term_reason/Makefile b/contrib/pg_term_reason/Makefile
new file mode 100644
index 0000000000..fa751801f8
--- /dev/null
+++ b/contrib/pg_term_reason/Makefile
@@ -0,0 +1,22 @@
+# contrib/pg_term_reason/Makefile
+
+MODULE_big = pg_term_reason
+OBJS = \
+	pg_term_reason.o
+
+EXTENSION = pg_term_reason
+DATA = pg_term_reason--1.0.sql
+PGFILEDESC = "pg_term_reason - add pg_terminate_backend_reasoned function"
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_term_reason
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_term_reason/meson.build b/contrib/pg_term_reason/meson.build
new file mode 100644
index 0000000000..ad93b4b5f2
--- /dev/null
+++ b/contrib/pg_term_reason/meson.build
@@ -0,0 +1,23 @@
+pg_term_sources = files(
+  'pg_term_reason.c',
+)
+
+pg_term_reason = shared_module('pg_term_reason',
+  pg_term_sources,
+  kwargs: contrib_mod_args,
+)
+contrib_targets += pg_term_reason
+
+install_data(
+  'pg_term_reason--1.0.sql',
+  'pg_term_reason.control',
+  kwargs: contrib_data_args,
+)
+
+tests += {
+  'tap': {
+    'tests': [
+      't/001_basic.pl',
+    ],
+  },
+}
diff --git a/contrib/pg_term_reason/pg_term_reason--1.0.sql b/contrib/pg_term_reason/pg_term_reason--1.0.sql
new file mode 100644
index 0000000000..e0f1bd5779
--- /dev/null
+++ b/contrib/pg_term_reason/pg_term_reason--1.0.sql
@@ -0,0 +1,14 @@
+/* contrib/pg_term_reason/pg_term_reason--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_term_reason" to load this file. \quit
+
+CREATE FUNCTION pg_terminate_backend_msg(pid integer, timeout int8 DEFAULT 0, reason text DEFAULT '')
+RETURNS boolean
+AS 'MODULE_PATHNAME', 'pg_terminate_backend_msg'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION pg_cancel_backend_msg(pid integer, reason text DEFAULT '')
+RETURNS boolean
+AS 'MODULE_PATHNAME', 'pg_cancel_backend_msg'
+LANGUAGE C STRICT;
diff --git a/contrib/pg_term_reason/pg_term_reason.c b/contrib/pg_term_reason/pg_term_reason.c
new file mode 100644
index 0000000000..5c6ceb00fd
--- /dev/null
+++ b/contrib/pg_term_reason/pg_term_reason.c
@@ -0,0 +1,89 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_term_reason.c
+ *	  Functions to terminate/cancel postgres backends with additional message
+ *
+ * IDENTIFICATION
+ *	  contrib/pg_term_reason/pg_term_reason.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "varatt.h"
+#include "storage/signalfuncs.h"
+#include "storage/lock.h"
+#include "storage/procarray.h"
+#include "storage/proc.h"
+
+PG_MODULE_MAGIC_EXT(
+					.name = "pg_term_reason",
+					.version = PG_VERSION
+);
+
+PG_FUNCTION_INFO_V1(pg_terminate_backend_msg);
+PG_FUNCTION_INFO_V1(pg_cancel_backend_msg);
+
+static void
+set_reason(int pid, const char *msg, int msglen)
+{
+	int			len;
+	PGPROC		*proc;
+
+	if (msglen <= 0) {
+		return;
+	}
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+	proc = BackendPidGetProcWithLock(pid);
+
+	if (proc != NULL) {
+		len = Min(PROC_TERM_REASON_MAX_LEN - 1, msglen);
+		strncpy(proc->termReasonStr, msg, len);
+	}
+
+	LWLockRelease(ProcArrayLock);
+}
+
+Datum
+pg_cancel_backend_msg(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	text		*reason;
+	char		*reason_str;
+	int			reason_len;
+
+	pid = PG_GETARG_INT32(0);
+	reason = PG_GETARG_TEXT_P(1);
+
+	reason_str = VARDATA(reason);
+	reason_len = VARSIZE(reason) - VARHDRSZ;
+
+	set_reason(pid, reason_str, reason_len);
+
+	return pg_cancel_backend_impl(pid);
+}
+
+Datum
+pg_terminate_backend_msg(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	int			timeout;		/* milliseconds */
+	text		*reason;
+	char		*reason_str;
+	int			reason_len;
+
+	pid = PG_GETARG_INT32(0);
+	timeout = PG_GETARG_INT64(1);
+	reason = PG_GETARG_TEXT_P(2);
+
+	reason_str = VARDATA(reason);
+	reason_len = VARSIZE(reason) - VARHDRSZ;
+
+	set_reason(pid, reason_str, reason_len);
+
+	return pg_terminate_backend_impl(pid, timeout);
+}
diff --git a/contrib/pg_term_reason/pg_term_reason.control b/contrib/pg_term_reason/pg_term_reason.control
new file mode 100644
index 0000000000..d3b04459aa
--- /dev/null
+++ b/contrib/pg_term_reason/pg_term_reason.control
@@ -0,0 +1,5 @@
+# pg_term_reason extension
+comment = 'extension to terminate backends with additional reason string'
+default_version = '1.0'
+module_pathname = '$libdir/pg_term_reason'
+relocatable = true
diff --git a/contrib/pg_term_reason/t/001_basic.pl b/contrib/pg_term_reason/t/001_basic.pl
new file mode 100644
index 0000000000..5d9dadc462
--- /dev/null
+++ b/contrib/pg_term_reason/t/001_basic.pl
@@ -0,0 +1,28 @@
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->start;
+
+$node->safe_psql('postgres', 'create extension pg_term_reason;');
+
+my ($stdout, $stderr);
+$node->psql('postgres',
+	q[select pg_terminate_backend_msg(pg_backend_pid(), 0, 'hello from tap tests!');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/hello from tap tests\!/, "expected message to be passed");
+
+$stdout = '';
+$stderr = '';
+$node->psql('postgres',
+	q[select pg_cancel_backend_msg(pg_backend_pid(), 'hello from tap tests again!');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/hello from tap tests again\!/, "expected message to be passed");
+
+$node->stop;
+
+done_testing();
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2025-12-17 11:34  Andrey Borodin <[email protected]>
  parent: Roman Khapov <[email protected]>
  1 sibling, 0 replies; 15+ messages in thread

From: Andrey Borodin @ 2025-12-17 11:34 UTC (permalink / raw)
  To: Roman Khapov <[email protected]>; +Cc: [email protected]



> On 13 Dec 2025, at 12:44, Roman Khapov <[email protected]> wrote:
> 
> Recently I started working on patch for adding additional message from admin 
> in pg_terminate_backend for one of our greenplum fork. The main idea is that there must be 
> done little investigation every time you see 'FATAL:  terminating connection due to administrator command’ to understand the reason, especially in cases where connection was terminated by another
> user. So it was decided to create some new functions, that allows to terminate connection with
> additional message.

Overall idea seems good to me.
Keep in mind that Postgres literals are translated into many languages. So text ought to be clear enough for translators to build a sentence that precedes termination reason.

> 
> I did POC patches with the next main ideas:
> - lets add termReasonStr field in every PGPROC, that field can be used in ProcessInterrupts() 
> - implementation of pg_terminate_backend/pg_cancel_backend should be accessible from extensions, so lets move it in pg_terminate_backend_impl/pg_cancel_backend_impl and add definitions for it somewhere
> - write simple extensions, which defines functions like pg_terminate_backend_msg, that sets termReasonStr and calls pg_terminate_backend_impl

First thing that we need to do is to agree on API of the new feature. We do not need core-extension separation for this.
My vote would be for having pg_cancel_backend(reason text)\pg_terminate_backend(reason text) along with parameterless versions.
32 bytes per PGPROC seems reasonable for a "reason". The patch doesn't seem to take care of cleaning "termReasonStr". Is it done elsewhere?
We have a race condition if many backends cancel same backend. Won't they mess each other's reason?

Thanks!


Best regards, Andrey Borodin.






^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2025-12-17 16:02  Daniel Gustafsson <[email protected]>
  parent: Roman Khapov <[email protected]>
  1 sibling, 1 reply; 15+ messages in thread

From: Daniel Gustafsson @ 2025-12-17 16:02 UTC (permalink / raw)
  To: Roman Khapov <[email protected]>; +Cc: [email protected]

> On 13 Dec 2025, at 08:44, Roman Khapov <[email protected]> wrote:

> Recently I started working on patch for adding additional message from admin 
> in pg_terminate_backend for one of our greenplum fork.

Greenplum already has support for passing a message in the terminate command
doesnt it?  Or at least it used to have but perhaps it was ripped out, my
memory is getting a bit fuzzy.

https://github.com/greenplum-db/gpdb-archive/commit/fa6c2d43d675aa05e2d9f797e3008f6fe075ee2c

--
Daniel Gustafsson






^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2025-12-20 09:26  Roman Khapov <[email protected]>
  parent: Daniel Gustafsson <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Roman Khapov @ 2025-12-20 09:26 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: [email protected]


> On 17 Dec 2025, at 21:02, Daniel Gustafsson <[email protected]> wrote:
> 
> Greenplum already has support for passing a message in the terminate command
> doesnt it?  Or at least it used to have but perhaps it was ripped out, my
> memory is getting a bit fuzzy.
> 
> https://github.com/greenplum-db/gpdb-archive/commit/fa6c2d43d675aa05e2d9f797e3008f6fe075ee2c
> 

Well, seems like I missed that patch..

Anyway, now I need same functionality in PostgreSQL, and your patch seems interesting,
especially in part where you keep messages in separated shmem region.

So I adopted your patch, maybe in that form it can be useful for PostgreSQL?









Attachments:

  [application/octet-stream] v2-0001-pg_terminate_backend_msg-and-pg_cancel_backend_ms.patch (16.0K, 2-v2-0001-pg_terminate_backend_msg-and-pg_cancel_backend_ms.patch)
  download | inline diff:
From e85dae5f0ebfce8612c615aa8c9505672e08bed2 Mon Sep 17 00:00:00 2001
From: roman khapov <[email protected]>
Date: Sat, 20 Dec 2025 08:03:01 +0000
Subject: [PATCH v2] pg_terminate_backend_msg and pg_cancel_backend_msg

Sometimes it is useful to terminate some backend
process with additional message from admin.

This patch introduces two new functions:
 - pg_terminate_backend_msg(pid, timeout, msg)
 - pg_cancel_backend_msg(pid, msg)

The functions are similar with pg_terminate_backend/pg_cancel_backend,
but adds additional argument: the message, that will be passed into
FATAL/ERROR packet when terminating/canceling backend.

To do that, the patch introduces new module: BackendMsg - shared memory
region that holds pairs of (message, pid) which are checked in ProcessInterrupts()

Ex. of usage:
postgres=# select pg_terminate_backend_msg(pg_backend_pid(), 0, 'Some message');
FATAL:  terminating connection due to administrator command: Some message

Author: Daniel Gustafsson <[email protected]>
Author: Roman Khapov <[email protected]>
Reviewed-by:
Discussion:
---
 src/backend/catalog/system_functions.sql      |   5 +
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/ipc/signalfuncs.c         |  78 +++++++--
 src/backend/tcop/postgres.c                   |  32 +++-
 src/backend/utils/init/postinit.c             |   2 +
 src/backend/utils/misc/Makefile               |   3 +-
 src/backend/utils/misc/backend_msg.c          | 155 ++++++++++++++++++
 src/include/catalog/pg_proc.dat               |   7 +
 src/include/utils/backend_msg.h               |  30 ++++
 .../modules/test_misc/t/010_backend_msg.pl    |  31 ++++
 10 files changed, 330 insertions(+), 16 deletions(-)
 create mode 100644 src/backend/utils/misc/backend_msg.c
 create mode 100644 src/include/utils/backend_msg.h
 create mode 100644 src/test/modules/test_misc/t/010_backend_msg.pl

diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 2d946d6d9e9..5209ee3f1ab 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -404,6 +404,11 @@ CREATE OR REPLACE FUNCTION
   RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend'
   PARALLEL SAFE;
 
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend_msg(pid integer, timeout int8 DEFAULT 0, msg text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend_msg'
+  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 b23d0c19360..94a89ff5e57 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -52,6 +52,7 @@
 #include "storage/sinvaladt.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
+#include "utils/backend_msg.h"
 
 /* GUCs */
 int			shared_memory_type = DEFAULT_SHARED_MEMORY_TYPE;
@@ -140,6 +141,7 @@ CalculateShmemSize(void)
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
 	size = add_size(size, WaitLSNShmemSize());
+	size = add_size(size, BackendStatusShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -328,6 +330,7 @@ CreateOrAttachShmemStructs(void)
 	InjectionPointShmemInit();
 	AioShmemInit();
 	WaitLSNShmemInit();
+	BackendMsgShmemInit();
 }
 
 /*
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index a3a670ba247..d7ca2d77186 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -25,6 +25,8 @@
 #include "storage/procarray.h"
 #include "utils/acl.h"
 #include "utils/fmgrprotos.h"
+#include "utils/builtins.h"
+#include "utils/backend_msg.h"
 
 
 /*
@@ -48,7 +50,7 @@
 #define SIGNAL_BACKEND_NOSUPERUSER 3
 #define SIGNAL_BACKEND_NOAUTOVAC 4
 static int
-pg_signal_backend(int pid, int sig)
+pg_signal_backend(int pid, int sig, const char *msg)
 {
 	PGPROC	   *proc = BackendPidGetProc(pid);
 
@@ -111,6 +113,15 @@ pg_signal_backend(int pid, int sig)
 	 * too unlikely to worry about.
 	 */
 
+	if (msg != NULL)
+	{
+		int		r = BackendMsgSet(pid, msg);
+
+		if (r != -1 && r != strlen(msg))
+			ereport(NOTICE,
+					(errmsg("message is too long, truncated to %d", r)));
+	}
+
 	/* If we have setsid(), signal the backend's whole process group */
 #ifdef HAVE_SETSID
 	if (kill(-pid, sig))
@@ -132,10 +143,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 Datum
+pg_cancel_backend_internal(pid_t pid, const 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,
@@ -161,6 +172,28 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum
+pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+	int		pid;
+	
+	pid = PG_GETARG_INT32(0);
+
+	return pg_cancel_backend_internal(pid, NULL);
+}
+
+Datum
+pg_cancel_backend_msg(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	char		*msg;
+
+	pid = PG_GETARG_INT32(0);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	return pg_cancel_backend_internal(pid, msg);
+}
+
 /*
  * Wait until there is no backend process with the given PID and return true.
  * On timeout, a warning is emitted and false is returned.
@@ -233,22 +266,17 @@ pg_wait_until_termination(int pid, int64 timeout)
  *
  * Note that only superusers can signal superuser-owned processes.
  */
-Datum
-pg_terminate_backend(PG_FUNCTION_ARGS)
+static Datum
+pg_terminate_backend_internal(int pid, int timeout, const char *msg)
 {
-	int			pid;
 	int			r;
-	int			timeout;		/* milliseconds */
-
-	pid = PG_GETARG_INT32(0);
-	timeout = PG_GETARG_INT64(1);
 
 	if (timeout < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("\"timeout\" must not be negative")));
 
-	r = pg_signal_backend(pid, SIGTERM);
+	r = pg_signal_backend(pid, SIGTERM, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -278,6 +306,32 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum
+pg_terminate_backend(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	int			timeout;		/* milliseconds */
+
+	pid = PG_GETARG_INT32(0);
+	timeout = PG_GETARG_INT64(1);
+
+	return pg_terminate_backend_internal(pid, timeout, NULL);
+}
+
+Datum
+pg_terminate_backend_msg(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	int			timeout;		/* milliseconds */
+	char		*msg;
+
+	pid = PG_GETARG_INT32(0);
+	timeout = PG_GETARG_INT64(1);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(2));
+
+	return pg_terminate_backend_internal(pid, timeout, msg);
+}
+
 /*
  * Signal to reload the database configuration
  *
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7dd75a490aa..94c1636c7e7 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -81,6 +81,7 @@
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
+#include "utils/backend_msg.h"
 
 /* ----------------
  *		global variables
@@ -3356,9 +3357,22 @@ ProcessInterrupts(void)
 			proc_exit(0);
 		}
 		else
+		{
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						errmsg("terminating connection due to administrator command: %s", msg)));
+			}
+
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
+		}
 	}
 
 	if (CheckClientConnectionPending)
@@ -3466,9 +3480,21 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request: %s", msg)));
+			}
+			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 4ed69ac7ba2..c653bcc12c5 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -69,6 +69,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/timeout.h"
+#include "utils/backend_msg.h"
 
 static HeapTuple GetDatabaseTuple(const char *dbname);
 static HeapTuple GetDatabaseTupleByOid(Oid dboid);
@@ -899,6 +900,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 			InitializeSystemUser(MyClientConnectionInfo.authn_id,
 								 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
+		BackendMsgInit(MyProcNumber);
 	}
 
 	/* Report any SSL/GSS details for the session. */
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index f142d17178b..5494994669f 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -32,7 +32,8 @@ OBJS = \
 	stack_depth.o \
 	superuser.o \
 	timeout.o \
-	tzparser.o
+	tzparser.o \
+	backend_msg.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_msg.c b/src/backend/utils/misc/backend_msg.c
new file mode 100644
index 00000000000..7f638c864bc
--- /dev/null
+++ b/src/backend/utils/misc/backend_msg.c
@@ -0,0 +1,155 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/misc/backend_msg.c
+ *
+ *--------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "storage/ipc.h"
+#include "utils/backend_msg.h"
+
+typedef struct {
+	pid_t pid;
+	slock_t lock;
+	char msg[BACKEND_MSG_MAX_LEN];
+} BackendMsgSlot;
+
+
+static BackendMsgSlot *BackendMsgSlots;
+static BackendMsgSlot *MyBackendMsgSlot;
+
+static void
+backend_msg_slot_clean(int code, Datum arg)
+{
+	(void) code;
+	(void) arg;
+
+	Assert(MyBackendMsgSlot != NULL);
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot->msg[0] = '\0';
+	MyBackendMsgSlot->pid = 0;
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot = NULL;
+}
+
+
+void BackendMsgShmemInit(void)
+{
+	Size	size;
+	bool	found;
+
+	size = BackendMsgShmemSize();
+	BackendMsgSlots = ShmemInitStruct("BackendMsgSlots", size, &found);
+
+	if (found)
+		return;
+	
+	memset(BackendMsgSlots, 0, size);
+
+	for (int i = 0; i < MaxBackends; ++i)
+		SpinLockInit(&BackendMsgSlots[i].lock);
+}
+
+Size
+BackendMsgShmemSize(void)
+{
+	return mul_size(MaxBackends, sizeof(BackendMsgSlot));
+}
+
+void BackendMsgInit(int id)
+{
+	BackendMsgSlot		*slot;
+
+	slot = &BackendMsgSlots[id];
+
+	slot->msg[0] = '\0';
+	slot->pid = MyProcPid;
+
+	MyBackendMsgSlot = slot;
+
+	on_shmem_exit(backend_msg_slot_clean, Int32GetDatum(0) /* not used */);
+}
+
+int BackendMsgSet(pid_t pid, const char *msg)
+{
+	BackendMsgSlot		*slot;
+	int					len;
+
+	if (msg == NULL || msg[0] == '\0')
+		return 0;
+
+	for (int i = 0; i < MaxBackends; ++i)
+	{
+		slot = &BackendMsgSlots[i];
+
+		if (slot->pid == 0 || slot->pid != pid)
+			continue;
+
+		SpinLockAcquire(&slot->lock);
+
+		if (slot->pid != pid)
+		{
+			SpinLockRelease(&slot->lock);
+			break;
+		}
+
+		len = stpncpy(slot->msg, msg, sizeof(slot->msg)) - slot->msg;
+
+		SpinLockRelease(&slot->lock);
+
+		return len;
+	}
+
+	ereport(LOG,
+			(errmsg("Can't set message for missing backend %d, requested by %d",
+				pid, MyProcPid)));
+
+	return -1;
+}
+
+int BackendMsgGet(char *buf, int max_len)
+{
+	int		len;
+
+	if (MyBackendMsgSlot == NULL)
+		return 0;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	len = strlcpy(buf, MyBackendMsgSlot->msg, max_len);
+	memset(MyBackendMsgSlot->msg, '\0', sizeof(MyBackendMsgSlot->msg));
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return len;
+}
+
+bool BackendMsgIsSet(void)
+{
+	bool result = false;
+
+	if (MyBackendMsgSlot == NULL)
+		return false;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+	result = MyBackendMsgSlot->msg[0] != '\0';
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return result;
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fd9448ec7b9..66b4a397284 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6713,6 +6713,13 @@
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
   prosrc => 'pg_terminate_backend' },
+{ oid => '8328', descr => 'cancel a server process\' current query with message',
+  proname => 'pg_cancel_backend_msg', provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4 text', prosrc => 'pg_cancel_backend_msg' },
+{ oid => '8329', descr => 'terminate a server process with message',
+  proname => 'pg_terminate_backend_msg', provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,msg}',
+  prosrc => 'pg_terminate_backend_msg' },
 { oid => '2172', descr => 'prepare for taking an online backup',
   proname => 'pg_backup_start', provolatile => 'v', proparallel => 'r',
   prorettype => 'pg_lsn', proargtypes => 'text bool',
diff --git a/src/include/utils/backend_msg.h b/src/include/utils/backend_msg.h
new file mode 100644
index 00000000000..db69efbe915
--- /dev/null
+++ b/src/include/utils/backend_msg.h
@@ -0,0 +1,30 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/backend_msg.h
+ *
+ *--------------------------------------------------------------------
+ */
+
+#ifndef BACKEND_MSG_H
+#define BACKEND_MSG_H
+
+#include <sys/types.h>
+
+#define BACKEND_MSG_MAX_LEN 128
+
+extern void BackendMsgShmemInit(void);
+extern Size BackendMsgShmemSize(void);
+extern void BackendMsgInit(int id);
+extern int BackendMsgSet(pid_t pid, const char *msg);
+extern int BackendMsgGet(char *buf, int max_len);
+extern bool BackendMsgIsSet(void);
+
+
+#endif /* BACKEND_MSG_H */
diff --git a/src/test/modules/test_misc/t/010_backend_msg.pl b/src/test/modules/test_misc/t/010_backend_msg.pl
new file mode 100644
index 00000000000..d0d68b453f2
--- /dev/null
+++ b/src/test/modules/test_misc/t/010_backend_msg.pl
@@ -0,0 +1,31 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Check that messages are passed to backends by
+# pg_terminate_backend_msg, pg_cancel_backend_msg
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->start;
+
+my ($stdout, $stderr);
+$node->psql('postgres',
+	q[select pg_terminate_backend_msg(pg_backend_pid(), 0, 'Have you seen my coffee cup?');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/Have you seen my coffee cup\?/, "expected message to be passed");
+
+$stdout = '';
+$stderr = '';
+$node->psql('postgres',
+	q[select pg_cancel_backend_msg(pg_backend_pid(), 'You have to wear some ridiculous tie');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/You have to wear some ridiculous tie/, "expected message to be passed");
+
+$node->stop;
+
+done_testing();
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2025-12-20 10:32  Kirill Reshke <[email protected]>
  parent: Roman Khapov <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Kirill Reshke @ 2025-12-20 10:32 UTC (permalink / raw)
  To: Roman Khapov <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; [email protected]

On Sat, 20 Dec 2025 at 14:27, Roman Khapov <[email protected]> wrote:
>
>
> > On 17 Dec 2025, at 21:02, Daniel Gustafsson <[email protected]> wrote:
> >
> > Greenplum already has support for passing a message in the terminate command
> > doesnt it?  Or at least it used to have but perhaps it was ripped out, my
> > memory is getting a bit fuzzy.
> >
> > https://github.com/greenplum-db/gpdb-archive/commit/fa6c2d43d675aa05e2d9f797e3008f6fe075ee2c
> >
>
> Well, seems like I missed that patch..
>
> Anyway, now I need same functionality in PostgreSQL, and your patch seems interesting,
> especially in part where you keep messages in separated shmem region.
>
> So I adopted your patch, maybe in that form it can be useful for PostgreSQL?
>



>  +CREATE OR REPLACE FUNCTION
> +  pg_terminate_backend_msg(pid integer, timeout int8 DEFAULT 0, msg text DEFAULT '')
> +  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend_msg'
> +  PARALLEL SAFE;

I don't think we need to create a function with a name other than
`pg_terminate_backend`.  I also do not think we need
pg_terminate_backend_msg as a wrapper to another function - all of
this can be a single function, accepting different number of params,
exampli gratia "bt_index_check"

```
Datum
bt_index_check(PG_FUNCTION_ARGS)
{
Oid indrelid = PG_GETARG_OID(0);

...

if (PG_NARGS() >= 2)
args.heapallindexed = PG_GETARG_BOOL(1);
if (PG_NARGS() >= 3)
args.checkunique = PG_GETARG_BOOL(2);
....


PG_RETURN_VOID();
}

```

-- 
Best regards,
Kirill Reshke





^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-01 00:08  Jim Jones <[email protected]>
  parent: Kirill Reshke <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Jim Jones @ 2026-02-01 00:08 UTC (permalink / raw)
  To: Kirill Reshke <[email protected]>; Roman Khapov <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; [email protected]

Hi Roman,

As pointed out by Kirill, there is no reason to create
pg_terminate_backend_msg or pg_cancel_backend_msg. You can simply use
the existing functions and expand them to use one extra parameter, e.g.

CREATE OR REPLACE FUNCTION
  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, msg text
DEFAULT '')
  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS
'pg_terminate_backend'
  PARALLEL SAFE;


Here I don't think we need to check PG_NARGS, since the function calls
will always have a default value. Something like this perhaps:


Datum pg_terminate_backend(PG_FUNCTION_ARGS)
{
  int pid;
  int timeout; /* milliseconds */
  char *msg;

  pid = PG_GETARG_INT32(0);
  timeout = PG_GETARG_INT64(1);
  msg = text_to_cstring(PG_GETARG_TEXT_PP(2));

  return pg_terminate_backend_internal(pid, timeout, msg);
}

stpncpy() -> strlcpy()

Documentation is missing -- assuming the feature design is already solid.

Add backend_msg.c to meson.build.

I rebased the patch (it was failing for quite some time) with some
suggestions. Feel free to remove them and revert to your v2 if you disagree.

I'll review the rest of code in the next days.

Best, Jim

Attachments:

  [text/x-patch] v3-0001-pg_terminate_backend_msg-and-pg_cancel_backend_ms.patch (15.8K, 2-v3-0001-pg_terminate_backend_msg-and-pg_cancel_backend_ms.patch)
  download | inline diff:
From 37672f6496436ea8ec68cb19b150df32c7b0217d Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Sun, 1 Feb 2026 00:26:21 +0100
Subject: [PATCH v3] pg_terminate_backend_msg and pg_cancel_backend_msg

Sometimes it is useful to terminate some backend
process with additional message from admin.

This patch introduces two new functions:
 - pg_terminate_backend_msg(pid, timeout, msg)
 - pg_cancel_backend_msg(pid, msg)

The functions are similar with pg_terminate_backend/pg_cancel_backend,
but adds additional argument: the message, that will be passed into
FATAL/ERROR packet when terminating/canceling backend.

To do that, the patch introduces new module: BackendMsg - shared memory
region that holds pairs of (message, pid) which are checked in ProcessInterrupts()

Ex. of usage:
postgres=# select pg_terminate_backend_msg(pg_backend_pid(), 0, 'Some message');
FATAL:  terminating connection due to administrator command: Some message

Author: Daniel Gustafsson <[email protected]>
Author: Roman Khapov <[email protected]>
Reviewed-by:
Discussion:
---
 src/backend/catalog/system_functions.sql      |   7 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/ipc/signalfuncs.c         |  54 ++++--
 src/backend/tcop/postgres.c                   |  32 +++-
 src/backend/utils/init/postinit.c             |   2 +
 src/backend/utils/misc/Makefile               |   3 +-
 src/backend/utils/misc/backend_msg.c          | 155 ++++++++++++++++++
 src/backend/utils/misc/meson.build            |   1 +
 src/include/catalog/pg_proc.dat               |   5 +-
 src/include/utils/backend_msg.h               |  28 ++++
 .../modules/test_misc/t/010_backend_msg.pl    |  31 ++++
 11 files changed, 302 insertions(+), 19 deletions(-)
 create mode 100644 src/backend/utils/misc/backend_msg.c
 create mode 100644 src/include/utils/backend_msg.h
 create mode 100644 src/test/modules/test_misc/t/010_backend_msg.pl

diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index eb9e31ae1b..5e2c138619 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -400,7 +400,12 @@ CREATE OR REPLACE FUNCTION
   PARALLEL SAFE;
 
 CREATE OR REPLACE FUNCTION
-  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0)
+  pg_cancel_backend(pid integer, msg text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_cancel_backend'
+  PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, msg text DEFAULT '')
   RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend'
   PARALLEL SAFE;
 
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 1f7e933d50..9f50adf030 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -52,6 +52,7 @@
 #include "storage/sinvaladt.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
+#include "utils/backend_msg.h"
 
 /* GUCs */
 int			shared_memory_type = DEFAULT_SHARED_MEMORY_TYPE;
@@ -140,6 +141,7 @@ CalculateShmemSize(void)
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
 	size = add_size(size, WaitLSNShmemSize());
+	size = add_size(size, BackendStatusShmemSize());
 	size = add_size(size, LogicalDecodingCtlShmemSize());
 
 	/* include additional requested shmem from preload libraries */
@@ -327,6 +329,7 @@ CreateOrAttachShmemStructs(void)
 	InjectionPointShmemInit();
 	AioShmemInit();
 	WaitLSNShmemInit();
+	BackendMsgShmemInit();
 	LogicalDecodingCtlShmemInit();
 }
 
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 6f7759cd72..f242cebf46 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -25,6 +25,8 @@
 #include "storage/procarray.h"
 #include "utils/acl.h"
 #include "utils/fmgrprotos.h"
+#include "utils/builtins.h"
+#include "utils/backend_msg.h"
 
 
 /*
@@ -48,7 +50,7 @@
 #define SIGNAL_BACKEND_NOSUPERUSER 3
 #define SIGNAL_BACKEND_NOAUTOVAC 4
 static int
-pg_signal_backend(int pid, int sig)
+pg_signal_backend(int pid, int sig, const char *msg)
 {
 	PGPROC	   *proc = BackendPidGetProc(pid);
 
@@ -111,6 +113,15 @@ pg_signal_backend(int pid, int sig)
 	 * too unlikely to worry about.
 	 */
 
+	if (msg != NULL)
+	{
+		int		r = BackendMsgSet(pid, msg);
+
+		if (r != -1 && r != strlen(msg))
+			ereport(NOTICE,
+					(errmsg("message is too long, truncated to %d", r)));
+	}
+
 	/* If we have setsid(), signal the backend's whole process group */
 #ifdef HAVE_SETSID
 	if (kill(-pid, sig))
@@ -132,10 +143,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 Datum
+pg_cancel_backend_internal(pid_t pid, const 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,
@@ -161,6 +172,17 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	char	   *msg;
+
+	pid = PG_GETARG_INT32(0);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	return pg_cancel_backend_internal(pid, msg);
+}
+
 /*
  * Wait until there is no backend process with the given PID and return true.
  * On timeout, a warning is emitted and false is returned.
@@ -233,22 +255,17 @@ pg_wait_until_termination(int pid, int64 timeout)
  *
  * Note that only superusers can signal superuser-owned processes.
  */
-Datum
-pg_terminate_backend(PG_FUNCTION_ARGS)
+static Datum
+pg_terminate_backend_internal(int pid, int timeout, const char *msg)
 {
-	int			pid;
 	int			r;
-	int			timeout;		/* milliseconds */
-
-	pid = PG_GETARG_INT32(0);
-	timeout = PG_GETARG_INT64(1);
 
 	if (timeout < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("\"timeout\" must not be negative")));
 
-	r = pg_signal_backend(pid, SIGTERM);
+	r = pg_signal_backend(pid, SIGTERM, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -278,6 +295,19 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum pg_terminate_backend(PG_FUNCTION_ARGS)
+{
+	int pid;
+	int timeout; /* milliseconds */
+	char *msg;
+
+	pid = PG_GETARG_INT32(0);
+	timeout = PG_GETARG_INT64(1);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(2));
+
+	return pg_terminate_backend_internal(pid, timeout, msg);
+}
+
 /*
  * Signal to reload the database configuration
  *
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b4a8d2f3a1..3c6d285a4d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -81,6 +81,7 @@
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
+#include "utils/backend_msg.h"
 
 /* ----------------
  *		global variables
@@ -3356,9 +3357,22 @@ ProcessInterrupts(void)
 			proc_exit(0);
 		}
 		else
+		{
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						errmsg("terminating connection due to administrator command: %s", msg)));
+			}
+
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
+		}
 	}
 
 	if (CheckClientConnectionPending)
@@ -3466,9 +3480,21 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request: %s", msg)));
+			}
+			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 3f401faf3d..debf6da4a7 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -69,6 +69,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/timeout.h"
+#include "utils/backend_msg.h"
 
 static HeapTuple GetDatabaseTuple(const char *dbname);
 static HeapTuple GetDatabaseTupleByOid(Oid dboid);
@@ -902,6 +903,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 			InitializeSystemUser(MyClientConnectionInfo.authn_id,
 								 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
+		BackendMsgInit(MyProcNumber);
 	}
 
 	/* Report any SSL/GSS details for the session. */
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index f142d17178..5494994669 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -32,7 +32,8 @@ OBJS = \
 	stack_depth.o \
 	superuser.o \
 	timeout.o \
-	tzparser.o
+	tzparser.o \
+	backend_msg.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_msg.c b/src/backend/utils/misc/backend_msg.c
new file mode 100644
index 0000000000..e36a6bc9ce
--- /dev/null
+++ b/src/backend/utils/misc/backend_msg.c
@@ -0,0 +1,155 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/misc/backend_msg.c
+ *
+ *--------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "storage/ipc.h"
+#include "utils/backend_msg.h"
+
+typedef struct {
+	pid_t pid;
+	slock_t lock;
+	char msg[BACKEND_MSG_MAX_LEN];
+} BackendMsgSlot;
+
+
+static BackendMsgSlot *BackendMsgSlots;
+static BackendMsgSlot *MyBackendMsgSlot;
+
+static void
+backend_msg_slot_clean(int code, Datum arg)
+{
+	(void) code;
+	(void) arg;
+
+	Assert(MyBackendMsgSlot != NULL);
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot->msg[0] = '\0';
+	MyBackendMsgSlot->pid = 0;
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot = NULL;
+}
+
+
+void BackendMsgShmemInit(void)
+{
+	Size	size;
+	bool	found;
+
+	size = BackendMsgShmemSize();
+	BackendMsgSlots = ShmemInitStruct("BackendMsgSlots", size, &found);
+
+	if (found)
+		return;
+	
+	memset(BackendMsgSlots, 0, size);
+
+	for (int i = 0; i < MaxBackends; ++i)
+		SpinLockInit(&BackendMsgSlots[i].lock);
+}
+
+Size
+BackendMsgShmemSize(void)
+{
+	return mul_size(MaxBackends, sizeof(BackendMsgSlot));
+}
+
+void BackendMsgInit(int id)
+{
+	BackendMsgSlot		*slot;
+
+	slot = &BackendMsgSlots[id];
+
+	slot->msg[0] = '\0';
+	slot->pid = MyProcPid;
+
+	MyBackendMsgSlot = slot;
+
+	on_shmem_exit(backend_msg_slot_clean, Int32GetDatum(0) /* not used */);
+}
+
+int BackendMsgSet(pid_t pid, const char *msg)
+{
+	BackendMsgSlot		*slot;
+	int					len;
+
+	if (msg == NULL || msg[0] == '\0')
+		return 0;
+
+	for (int i = 0; i < MaxBackends; ++i)
+	{
+		slot = &BackendMsgSlots[i];
+
+		if (slot->pid == 0 || slot->pid != pid)
+			continue;
+
+		SpinLockAcquire(&slot->lock);
+
+		if (slot->pid != pid)
+		{
+			SpinLockRelease(&slot->lock);
+			break;
+		}
+
+		len = strlcpy(slot->msg, msg, sizeof(slot->msg));
+
+		SpinLockRelease(&slot->lock);
+
+		return len;
+	}
+
+	ereport(LOG,
+			(errmsg("Can't set message for missing backend %ld, requested by %ld",
+				(long) pid, (long) MyProcPid)));
+
+	return -1;
+}
+
+int BackendMsgGet(char *buf, int max_len)
+{
+	int		len;
+
+	if (MyBackendMsgSlot == NULL)
+		return 0;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	len = strlcpy(buf, MyBackendMsgSlot->msg, max_len);
+	memset(MyBackendMsgSlot->msg, '\0', sizeof(MyBackendMsgSlot->msg));
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return len;
+}
+
+bool BackendMsgIsSet(void)
+{
+	bool result = false;
+
+	if (MyBackendMsgSlot == NULL)
+		return false;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+	result = MyBackendMsgSlot->msg[0] != '\0';
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return result;
+}
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 232e74d0af..831bf6c6ba 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -1,6 +1,7 @@
 # Copyright (c) 2022-2026, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'backend_msg.c',
   'conffiles.c',
   'guc.c',
   'guc_funcs.c',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5e5e33f64f..47aa30d716 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6724,10 +6724,11 @@
 
 { 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', proargnames => '{pid,msg}',
+  prosrc => 'pg_cancel_backend' },
 { oid => '2096', descr => 'terminate a server process',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
+  proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,msg}',
   prosrc => 'pg_terminate_backend' },
 { oid => '2172', descr => 'prepare for taking an online backup',
   proname => 'pg_backup_start', provolatile => 'v', proparallel => 'r',
diff --git a/src/include/utils/backend_msg.h b/src/include/utils/backend_msg.h
new file mode 100644
index 0000000000..825bf7100e
--- /dev/null
+++ b/src/include/utils/backend_msg.h
@@ -0,0 +1,28 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/backend_msg.h
+ *
+ *--------------------------------------------------------------------
+ */
+
+#ifndef BACKEND_MSG_H
+#define BACKEND_MSG_H
+
+#define BACKEND_MSG_MAX_LEN 128
+
+extern void BackendMsgShmemInit(void);
+extern Size BackendMsgShmemSize(void);
+extern void BackendMsgInit(int id);
+extern int BackendMsgSet(pid_t pid, const char *msg);
+extern int BackendMsgGet(char *buf, int max_len);
+extern bool BackendMsgIsSet(void);
+
+
+#endif /* BACKEND_MSG_H */
diff --git a/src/test/modules/test_misc/t/010_backend_msg.pl b/src/test/modules/test_misc/t/010_backend_msg.pl
new file mode 100644
index 0000000000..69aef23de5
--- /dev/null
+++ b/src/test/modules/test_misc/t/010_backend_msg.pl
@@ -0,0 +1,31 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Check that messages are passed to backends by
+# pg_terminate_backend, pg_cancel_backend
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->start;
+
+my ($stdout, $stderr);
+$node->psql('postgres',
+	q[select pg_terminate_backend(pg_backend_pid(), 0, 'Have you seen my coffee cup?');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/Have you seen my coffee cup\?/, "expected message to be passed");
+
+$stdout = '';
+$stderr = '';
+$node->psql('postgres',
+	q[select pg_cancel_backend(pg_backend_pid(), 'You have to wear some ridiculous tie');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/You have to wear some ridiculous tie/, "expected message to be passed");
+
+$node->stop;
+
+done_testing();
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-01 13:58  Jim Jones <[email protected]>
  parent: Jim Jones <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Jim Jones @ 2026-02-01 13:58 UTC (permalink / raw)
  To: Kirill Reshke <[email protected]>; Roman Khapov <[email protected]>; +Cc: Daniel Gustafsson <[email protected]>; [email protected]



On 01/02/2026 01:08, Jim Jones wrote:
> As pointed out by Kirill, there is no reason to create
> pg_terminate_backend_msg or pg_cancel_backend_msg. You can simply use
> the existing functions and expand them to use one extra parameter

Since the message's size is limited to BACKEND_MSG_MAX_LEN, shouldn't
you use it to limit msg at pg_terminate_backend[_msg]()? Something like:

Datum pg_terminate_backend(PG_FUNCTION_ARGS)
{
  int pid;
  int timeout; /* milliseconds */
  char msg[BACKEND_MSG_MAX_LEN];

  pid = PG_GETARG_INT32(0);
  timeout = PG_GETARG_INT64(1);
  text_to_cstring_buffer(PG_GETARG_TEXT_PP(2), msg, sizeof(msg));

  return pg_terminate_backend_internal(pid, timeout, msg);
}








^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-03 07:52  Roman Khapov <[email protected]>
  parent: Jim Jones <[email protected]>
  0 siblings, 2 replies; 15+ messages in thread

From: Roman Khapov @ 2026-02-03 07:52 UTC (permalink / raw)
  To: Jim Jones <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]

Hi Jim!
Thanks for your review and rebase!

> Since the message's size is limited to BACKEND_MSG_MAX_LEN, shouldn't
> you use it to limit msg at pg_terminate_backend[_msg]()? Something like:
> 

The message is truncated inside BackendMsgSet function, so I see a little
point in truncating it at pg_terminate_backend..

Also, changing the stpncpy() to strlcpy() breaks the logic
of returning result length of the message and NOTICE message about it,
so I reverted this change. But this note make me think about adding test
to truncation logic, so I added it in v4.

--
Best regards,
Roman Khapov



Attachments:

  [application/octet-stream] v4-0001-message-in-pg_terminate_backend-and-pg_cancel_bac.patch (18.3K, 2-v4-0001-message-in-pg_terminate_backend-and-pg_cancel_bac.patch)
  download | inline diff:
From c335e0c6b19982c9ea45b1979be4eeaa4ab3ece3 Mon Sep 17 00:00:00 2001
From: roman khapov <[email protected]>
Date: Tue, 3 Feb 2026 07:37:15 +0000
Subject: [PATCH v4] message in pg_terminate_backend and pg_cancel_backend

Sometimes it is useful to terminate some backend
process with additional message from admin.

This patch introduces a new argument, message to
pg_terminate_backend and pg_cancel_backend.
The message, that will be passed into
FATAL/ERROR packet when terminating/canceling backend.

To do that, the patch introduces new module: BackendMsg - shared memory
region that holds pairs of (message, pid) which are checked in ProcessInterrupts()

Ex. of usage:
postgres=# select pg_terminate_backend(pg_backend_pid(), 0, 'Some message');
FATAL:  terminating connection due to administrator command: Some message

Author: Daniel Gustafsson <[email protected]>
Author: Roman Khapov <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Jim Jones <[email protected]>
---
 doc/src/sgml/func/func-admin.sgml             |  10 +-
 src/backend/catalog/system_functions.sql      |   7 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/ipc/signalfuncs.c         |  54 ++++--
 src/backend/tcop/postgres.c                   |  32 +++-
 src/backend/utils/init/postinit.c             |   2 +
 src/backend/utils/misc/Makefile               |   3 +-
 src/backend/utils/misc/backend_msg.c          | 156 ++++++++++++++++++
 src/backend/utils/misc/meson.build            |   1 +
 src/include/utils/backend_msg.h               |  28 ++++
 src/test/modules/test_misc/meson.build        |   1 +
 .../modules/test_misc/t/011_backend_msg.pl    |  42 +++++
 12 files changed, 320 insertions(+), 19 deletions(-)
 create mode 100644 src/backend/utils/misc/backend_msg.c
 create mode 100644 src/include/utils/backend_msg.h
 create mode 100644 src/test/modules/test_misc/t/011_backend_msg.pl

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 3ac81905d1f..40bc0947f75 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -147,7 +147,7 @@
         <indexterm>
          <primary>pg_cancel_backend</primary>
         </indexterm>
-        <function>pg_cancel_backend</function> ( <parameter>pid</parameter> <type>integer</type> )
+        <function>pg_cancel_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>message</parameter> <type>test</type> <literal>DEFAULT</literal> <literal>''</literal> )
         <returnvalue>boolean</returnvalue>
        </para>
        <para>
@@ -160,6 +160,9 @@
         <literal>pg_signal_autovacuum_worker</literal> are permitted to
         cancel autovacuum worker processes, which are otherwise considered
         superuser backends.
+        If <parameter>message</parameter> is specified and non-empty, this
+        string will be passed as additional message in ERROR text for
+        canceled backend.
        </para></entry>
       </row>
 
@@ -225,7 +228,7 @@
         <indexterm>
          <primary>pg_terminate_backend</primary>
         </indexterm>
-        <function>pg_terminate_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>timeout</parameter> <type>bigint</type> <literal>DEFAULT</literal> <literal>0</literal> )
+        <function>pg_terminate_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>timeout</parameter> <type>bigint</type> <literal>DEFAULT</literal> <literal>0</literal>, <parameter>message</parameter> <type>test</type> <literal>DEFAULT</literal> <literal>''</literal>)
         <returnvalue>boolean</returnvalue>
        </para>
        <para>
@@ -249,6 +252,9 @@
         the process is terminated, the function
         returns <literal>true</literal>.  On timeout, a warning is emitted and
         <literal>false</literal> is returned.
+        If <parameter>message</parameter> is specified and non-empty, this
+        string will be passed as additional message in FATAL text for
+        terminated backend.
        </para></entry>
       </row>
      </tbody>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index eb9e31ae1bf..5e2c1386193 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -400,7 +400,12 @@ CREATE OR REPLACE FUNCTION
   PARALLEL SAFE;
 
 CREATE OR REPLACE FUNCTION
-  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0)
+  pg_cancel_backend(pid integer, msg text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_cancel_backend'
+  PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, msg text DEFAULT '')
   RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend'
   PARALLEL SAFE;
 
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 1f7e933d500..9f50adf030c 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -52,6 +52,7 @@
 #include "storage/sinvaladt.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
+#include "utils/backend_msg.h"
 
 /* GUCs */
 int			shared_memory_type = DEFAULT_SHARED_MEMORY_TYPE;
@@ -140,6 +141,7 @@ CalculateShmemSize(void)
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
 	size = add_size(size, WaitLSNShmemSize());
+	size = add_size(size, BackendStatusShmemSize());
 	size = add_size(size, LogicalDecodingCtlShmemSize());
 
 	/* include additional requested shmem from preload libraries */
@@ -327,6 +329,7 @@ CreateOrAttachShmemStructs(void)
 	InjectionPointShmemInit();
 	AioShmemInit();
 	WaitLSNShmemInit();
+	BackendMsgShmemInit();
 	LogicalDecodingCtlShmemInit();
 }
 
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 6f7759cd720..b66aadc7c0e 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -25,6 +25,8 @@
 #include "storage/procarray.h"
 #include "utils/acl.h"
 #include "utils/fmgrprotos.h"
+#include "utils/builtins.h"
+#include "utils/backend_msg.h"
 
 
 /*
@@ -48,7 +50,7 @@
 #define SIGNAL_BACKEND_NOSUPERUSER 3
 #define SIGNAL_BACKEND_NOAUTOVAC 4
 static int
-pg_signal_backend(int pid, int sig)
+pg_signal_backend(int pid, int sig, const char *msg)
 {
 	PGPROC	   *proc = BackendPidGetProc(pid);
 
@@ -111,6 +113,15 @@ pg_signal_backend(int pid, int sig)
 	 * too unlikely to worry about.
 	 */
 
+	if (msg != NULL && msg[0] != '\0')
+	{
+		int		r = BackendMsgSet(pid, msg);
+
+		if (r != -1 && r != strlen(msg))
+			ereport(NOTICE,
+					(errmsg("message is too long, truncated to %d", r)));
+	}
+
 	/* If we have setsid(), signal the backend's whole process group */
 #ifdef HAVE_SETSID
 	if (kill(-pid, sig))
@@ -132,10 +143,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 Datum
+pg_cancel_backend_internal(pid_t pid, const 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,
@@ -161,6 +172,17 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+	int			pid;
+	char	   *msg;
+
+	pid = PG_GETARG_INT32(0);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	return pg_cancel_backend_internal(pid, msg);
+}
+
 /*
  * Wait until there is no backend process with the given PID and return true.
  * On timeout, a warning is emitted and false is returned.
@@ -233,22 +255,17 @@ pg_wait_until_termination(int pid, int64 timeout)
  *
  * Note that only superusers can signal superuser-owned processes.
  */
-Datum
-pg_terminate_backend(PG_FUNCTION_ARGS)
+static Datum
+pg_terminate_backend_internal(int pid, int timeout, const char *msg)
 {
-	int			pid;
 	int			r;
-	int			timeout;		/* milliseconds */
-
-	pid = PG_GETARG_INT32(0);
-	timeout = PG_GETARG_INT64(1);
 
 	if (timeout < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("\"timeout\" must not be negative")));
 
-	r = pg_signal_backend(pid, SIGTERM);
+	r = pg_signal_backend(pid, SIGTERM, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -278,6 +295,19 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum pg_terminate_backend(PG_FUNCTION_ARGS)
+{
+	int pid;
+	int timeout; /* milliseconds */
+	char *msg;
+
+	pid = PG_GETARG_INT32(0);
+	timeout = PG_GETARG_INT64(1);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(2));
+
+	return pg_terminate_backend_internal(pid, timeout, msg);
+}
+
 /*
  * Signal to reload the database configuration
  *
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b4a8d2f3a1c..3c6d285a4dc 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -81,6 +81,7 @@
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
+#include "utils/backend_msg.h"
 
 /* ----------------
  *		global variables
@@ -3356,9 +3357,22 @@ ProcessInterrupts(void)
 			proc_exit(0);
 		}
 		else
+		{
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						errmsg("terminating connection due to administrator command: %s", msg)));
+			}
+
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
+		}
 	}
 
 	if (CheckClientConnectionPending)
@@ -3466,9 +3480,21 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request: %s", msg)));
+			}
+			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 3f401faf3de..debf6da4a7b 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -69,6 +69,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/timeout.h"
+#include "utils/backend_msg.h"
 
 static HeapTuple GetDatabaseTuple(const char *dbname);
 static HeapTuple GetDatabaseTupleByOid(Oid dboid);
@@ -902,6 +903,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 			InitializeSystemUser(MyClientConnectionInfo.authn_id,
 								 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
+		BackendMsgInit(MyProcNumber);
 	}
 
 	/* Report any SSL/GSS details for the session. */
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index f142d17178b..5494994669f 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -32,7 +32,8 @@ OBJS = \
 	stack_depth.o \
 	superuser.o \
 	timeout.o \
-	tzparser.o
+	tzparser.o \
+	backend_msg.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_msg.c b/src/backend/utils/misc/backend_msg.c
new file mode 100644
index 00000000000..b7c7d30e049
--- /dev/null
+++ b/src/backend/utils/misc/backend_msg.c
@@ -0,0 +1,156 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/misc/backend_msg.c
+ *
+ *--------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "storage/ipc.h"
+#include "utils/backend_msg.h"
+
+typedef struct {
+	pid_t pid;
+	slock_t lock;
+	char msg[BACKEND_MSG_MAX_LEN];
+} BackendMsgSlot;
+
+
+static BackendMsgSlot *BackendMsgSlots;
+static BackendMsgSlot *MyBackendMsgSlot;
+
+static void
+backend_msg_slot_clean(int code, Datum arg)
+{
+	(void) code;
+	(void) arg;
+
+	Assert(MyBackendMsgSlot != NULL);
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot->msg[0] = '\0';
+	MyBackendMsgSlot->pid = 0;
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot = NULL;
+}
+
+
+void BackendMsgShmemInit(void)
+{
+	Size	size;
+	bool	found;
+
+	size = BackendMsgShmemSize();
+	BackendMsgSlots = ShmemInitStruct("BackendMsgSlots", size, &found);
+
+	if (found)
+		return;
+	
+	memset(BackendMsgSlots, 0, size);
+
+	for (int i = 0; i < MaxBackends; ++i)
+		SpinLockInit(&BackendMsgSlots[i].lock);
+}
+
+Size
+BackendMsgShmemSize(void)
+{
+	return mul_size(MaxBackends, sizeof(BackendMsgSlot));
+}
+
+void BackendMsgInit(int id)
+{
+	BackendMsgSlot		*slot;
+
+	slot = &BackendMsgSlots[id];
+
+	slot->msg[0] = '\0';
+	slot->pid = MyProcPid;
+
+	MyBackendMsgSlot = slot;
+
+	on_shmem_exit(backend_msg_slot_clean, Int32GetDatum(0) /* not used */);
+}
+
+int BackendMsgSet(pid_t pid, const char *msg)
+{
+	BackendMsgSlot		*slot;
+	int					len;
+
+	if (msg == NULL || msg[0] == '\0')
+		return 0;
+
+	for (int i = 0; i < MaxBackends; ++i)
+	{
+		slot = &BackendMsgSlots[i];
+
+		if (slot->pid == 0 || slot->pid != pid)
+			continue;
+
+		SpinLockAcquire(&slot->lock);
+
+		if (slot->pid != pid)
+		{
+			SpinLockRelease(&slot->lock);
+			break;
+		}
+
+		len = stpncpy(slot->msg, msg, sizeof(slot->msg) - 1) - slot->msg;
+		slot->msg[len] = '\0';
+
+		SpinLockRelease(&slot->lock);
+
+		return len;
+	}
+
+	ereport(LOG,
+			(errmsg("Can't set message for missing backend %ld, requested by %ld",
+				(long) pid, (long) MyProcPid)));
+
+	return -1;
+}
+
+int BackendMsgGet(char *buf, int max_len)
+{
+	int		len;
+
+	if (MyBackendMsgSlot == NULL)
+		return 0;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	len = strlcpy(buf, MyBackendMsgSlot->msg, max_len);
+	memset(MyBackendMsgSlot->msg, '\0', sizeof(MyBackendMsgSlot->msg));
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return len;
+}
+
+bool BackendMsgIsSet(void)
+{
+	bool result = false;
+
+	if (MyBackendMsgSlot == NULL)
+		return false;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+	result = MyBackendMsgSlot->msg[0] != '\0';
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return result;
+}
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 232e74d0af9..831bf6c6bab 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -1,6 +1,7 @@
 # Copyright (c) 2022-2026, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'backend_msg.c',
   'conffiles.c',
   'guc.c',
   'guc_funcs.c',
diff --git a/src/include/utils/backend_msg.h b/src/include/utils/backend_msg.h
new file mode 100644
index 00000000000..825bf7100e2
--- /dev/null
+++ b/src/include/utils/backend_msg.h
@@ -0,0 +1,28 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/backend_msg.h
+ *
+ *--------------------------------------------------------------------
+ */
+
+#ifndef BACKEND_MSG_H
+#define BACKEND_MSG_H
+
+#define BACKEND_MSG_MAX_LEN 128
+
+extern void BackendMsgShmemInit(void);
+extern Size BackendMsgShmemSize(void);
+extern void BackendMsgInit(int id);
+extern int BackendMsgSet(pid_t pid, const char *msg);
+extern int BackendMsgGet(char *buf, int max_len);
+extern bool BackendMsgIsSet(void);
+
+
+#endif /* BACKEND_MSG_H */
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6e8db1621a7..674675b7ce1 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -19,6 +19,7 @@ tests += {
       't/008_replslot_single_user.pl',
       't/009_log_temp_files.pl',
       't/010_index_concurrently_upsert.pl',
+      't/011_backend_msg.pl',
     ],
     # The injection points are cluster-wide, so disable installcheck
     'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/011_backend_msg.pl b/src/test/modules/test_misc/t/011_backend_msg.pl
new file mode 100644
index 00000000000..795f3d55cfb
--- /dev/null
+++ b/src/test/modules/test_misc/t/011_backend_msg.pl
@@ -0,0 +1,42 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Check that messages are passed to backends by
+# pg_terminate_backend, pg_cancel_backend
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->start;
+
+my ($stdout, $stderr);
+$node->psql('postgres',
+	q[select pg_terminate_backend(pg_backend_pid(), 0, 'Have you seen my coffee cup?');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/Have you seen my coffee cup\?/, "expected message to be passed");
+
+$stdout = '';
+$stderr = '';
+$node->psql('postgres',
+	q[select pg_cancel_backend(pg_backend_pid(), 'You have to wear some ridiculous tie');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/You have to wear some ridiculous tie/, "expected message to be passed");
+
+$stdout = '';
+$stderr = '';
+my $longstr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+my $truncated = substr($longstr, 0, 127);
+$node->psql('postgres',
+	qq[select pg_terminate_backend(pg_backend_pid(), 0, '$longstr');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/NOTICE:  message is too long, truncated to 127/, "NOTICE message should be created");
+like($stderr, qr/\Q$truncated\E/, "expected truncated message (127 chars) to be passed");
+unlike($stderr, qr/\Q$longstr\E/, "full message must not be passed");
+
+$node->stop;
+
+done_testing();
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-03 08:21  Jim Jones <[email protected]>
  parent: Roman Khapov <[email protected]>
  1 sibling, 0 replies; 15+ messages in thread

From: Jim Jones @ 2026-02-03 08:21 UTC (permalink / raw)
  To: Roman Khapov <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]


On 03/02/2026 08:52, Roman Khapov wrote:
> The message is truncated inside BackendMsgSet function, so I see a little
> point in truncating it at pg_terminate_backend..

Thanks for the update!

You might have a point there. One issue I see is with UTF8 character
boundaries. Calculating len like this can lead to invalid characters,
for instance:

postgres=# SELECT
pg_terminate_backend(pg_backend_pid(),0,repeat('🐘',1000));

NOTICE:  message is too long, truncated to 127
FATAL:  terminating connection due to administrator command:
🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘🐘�
server closed the connection unexpectedly
        This probably means the server terminated abnormally


You might wanna take a look at other alternatives, such as pg_mbcliplen,
for instance (pseudocode):

len = strlen(msg);
if (len >= sizeof(slot->msg))
  len = pg_mbcliplen(msg, len, sizeof(slot->msg) - 1);

memcpy(slot->msg, msg, len);
slot->msg[len] = '\0';

Best, Jim






^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-03 08:26  Andrey Borodin <[email protected]>
  parent: Roman Khapov <[email protected]>
  1 sibling, 1 reply; 15+ messages in thread

From: Andrey Borodin @ 2026-02-03 08:26 UTC (permalink / raw)
  To: Roman Khapov <[email protected]>; +Cc: Jim Jones <[email protected]>; Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]



> On 3 Feb 2026, at 12:52, Roman Khapov <[email protected]> wrote:
> 
> <v4-0001-message-in-pg_terminate_backend-and-pg_cancel_bac.patch>

Some notes on this version:

1. Did you mean BackendMsgShmemSize()?
    size = add_size(size, BackendStatusShmemSize());

2. In docs: 

<function>pg_cancel_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>message</parameter> <type>test</type> <literal>DEFAULT</literal> <literal>''</literal> )

Did you mean <type>text</type>?

3. Windows build failed [0]

4. In src/include/utils/misc/backend_msg.c identification is backend_msg.h


Best regards, Andrey Borodin.

[0] https://github.com/x4m/postgres_g/runs/62314358734





^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-03 12:28  Roman Khapov <[email protected]>
  parent: Andrey Borodin <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Roman Khapov @ 2026-02-03 12:28 UTC (permalink / raw)
  To: Andrey Borodin <[email protected]>; Jim Jones <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]

Hi again Jim, Andrew!

Thanks for another round on review, updated the patch according to comments.

Also, fix `make check` by updating pg_proc.data with new functions,
similar to uuid7 way: defining _msg versions of the functions

--
Best regards,
Roman Khapov




> On 3 Feb 2026, at 13:26, Andrey Borodin <[email protected]> wrote:
> 
> 
> 
>> On 3 Feb 2026, at 12:52, Roman Khapov <[email protected]> wrote:
>> 
>> <v4-0001-message-in-pg_terminate_backend-and-pg_cancel_bac.patch>
> 
> Some notes on this version:
> 
> 1. Did you mean BackendMsgShmemSize()?
>    size = add_size(size, BackendStatusShmemSize());
> 
> 2. In docs: 
> 
> <function>pg_cancel_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>message</parameter> <type>test</type> <literal>DEFAULT</literal> <literal>''</literal> )
> 
> Did you mean <type>text</type>?
> 
> 3. Windows build failed [0]
> 
> 4. In src/include/utils/misc/backend_msg.c identification is backend_msg.h
> 
> 
> Best regards, Andrey Borodin.
> 
> [0] https://github.com/x4m/postgres_g/runs/62314358734




Attachments:

  [application/octet-stream] v5-0001-message-in-pg_terminate_backend-and-pg_cancel_bac.patch (19.8K, 2-v5-0001-message-in-pg_terminate_backend-and-pg_cancel_bac.patch)
  download | inline diff:
From 67e0d1ca030c9ba24055c05f739eab70e6c296e4 Mon Sep 17 00:00:00 2001
From: roman khapov <[email protected]>
Date: Tue, 3 Feb 2026 10:19:39 +0000
Subject: [PATCH v5] message in pg_terminate_backend and pg_cancel_backend

Sometimes it is useful to terminate some backend
process with additional message from admin.

This patch introduces a new argument, message to
pg_terminate_backend and pg_cancel_backend.
The message, that will be passed into
FATAL/ERROR packet when terminating/canceling backend.

To do that, the patch introduces new module: BackendMsg - shared memory
region that holds pairs of (message, pid) which are checked in ProcessInterrupts()

Ex. of usage:
postgres=# select pg_terminate_backend(pg_backend_pid(), 0, 'Some message');
FATAL:  terminating connection due to administrator command: Some message

Author: Daniel Gustafsson <[email protected]>
Author: Roman Khapov <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Jim Jones <[email protected]>
Reviewed-by: Andrey Borodin <[email protected]>
---
 doc/src/sgml/func/func-admin.sgml             |  14 +-
 src/backend/storage/ipc/ipci.c                |   3 +
 src/backend/storage/ipc/signalfuncs.c         |  90 ++++++++--
 src/backend/tcop/postgres.c                   |  32 +++-
 src/backend/utils/init/postinit.c             |   2 +
 src/backend/utils/misc/Makefile               |   3 +-
 src/backend/utils/misc/backend_msg.c          | 157 ++++++++++++++++++
 src/backend/utils/misc/meson.build            |   1 +
 src/include/catalog/pg_proc.dat               |   7 +
 src/include/utils/backend_msg.h               |  28 ++++
 src/test/modules/test_misc/meson.build        |   1 +
 .../modules/test_misc/t/011_backend_msg.pl    |  42 +++++
 12 files changed, 362 insertions(+), 18 deletions(-)
 create mode 100644 src/backend/utils/misc/backend_msg.c
 create mode 100644 src/include/utils/backend_msg.h
 create mode 100644 src/test/modules/test_misc/t/011_backend_msg.pl

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 3ac81905d1f..f0ab05fe774 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -147,7 +147,7 @@
         <indexterm>
          <primary>pg_cancel_backend</primary>
         </indexterm>
-        <function>pg_cancel_backend</function> ( <parameter>pid</parameter> <type>integer</type> )
+        <function>pg_cancel_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>message</parameter> <type>text</type> <literal>DEFAULT</literal> <literal>''</literal> )
         <returnvalue>boolean</returnvalue>
        </para>
        <para>
@@ -160,6 +160,11 @@
         <literal>pg_signal_autovacuum_worker</literal> are permitted to
         cancel autovacuum worker processes, which are otherwise considered
         superuser backends.
+        If <parameter>message</parameter> is specified and non-empty, this
+        string will be passed as additional message in ERROR text for
+        canceled backend. Please note: if multiple backends
+        simultaneously terminate or cancel the same target, only one of those
+        messages will be delivered.
        </para></entry>
       </row>
 
@@ -225,7 +230,7 @@
         <indexterm>
          <primary>pg_terminate_backend</primary>
         </indexterm>
-        <function>pg_terminate_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>timeout</parameter> <type>bigint</type> <literal>DEFAULT</literal> <literal>0</literal> )
+        <function>pg_terminate_backend</function> ( <parameter>pid</parameter> <type>integer</type>, <parameter>timeout</parameter> <type>bigint</type> <literal>DEFAULT</literal> <literal>0</literal>, <parameter>message</parameter> <type>text</type> <literal>DEFAULT</literal> <literal>''</literal>)
         <returnvalue>boolean</returnvalue>
        </para>
        <para>
@@ -249,6 +254,11 @@
         the process is terminated, the function
         returns <literal>true</literal>.  On timeout, a warning is emitted and
         <literal>false</literal> is returned.
+        If <parameter>message</parameter> is specified and non-empty, this
+        string will be passed as additional message in FATAL text for
+        terminated backend. Please note: if multiple backends
+        simultaneously terminate or cancel the same target, only one of those
+        messages will be delivered.
        </para></entry>
       </row>
      </tbody>
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 1f7e933d500..ee8765c66c3 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -52,6 +52,7 @@
 #include "storage/sinvaladt.h"
 #include "utils/guc.h"
 #include "utils/injection_point.h"
+#include "utils/backend_msg.h"
 
 /* GUCs */
 int			shared_memory_type = DEFAULT_SHARED_MEMORY_TYPE;
@@ -140,6 +141,7 @@ CalculateShmemSize(void)
 	size = add_size(size, SlotSyncShmemSize());
 	size = add_size(size, AioShmemSize());
 	size = add_size(size, WaitLSNShmemSize());
+	size = add_size(size, BackendMsgShmemSize());
 	size = add_size(size, LogicalDecodingCtlShmemSize());
 
 	/* include additional requested shmem from preload libraries */
@@ -327,6 +329,7 @@ CreateOrAttachShmemStructs(void)
 	InjectionPointShmemInit();
 	AioShmemInit();
 	WaitLSNShmemInit();
+	BackendMsgShmemInit();
 	LogicalDecodingCtlShmemInit();
 }
 
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 6f7759cd720..32cb06f8496 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -25,6 +25,8 @@
 #include "storage/procarray.h"
 #include "utils/acl.h"
 #include "utils/fmgrprotos.h"
+#include "utils/builtins.h"
+#include "utils/backend_msg.h"
 
 
 /*
@@ -48,7 +50,7 @@
 #define SIGNAL_BACKEND_NOSUPERUSER 3
 #define SIGNAL_BACKEND_NOAUTOVAC 4
 static int
-pg_signal_backend(int pid, int sig)
+pg_signal_backend(int pid, int sig, const char *msg)
 {
 	PGPROC	   *proc = BackendPidGetProc(pid);
 
@@ -111,6 +113,15 @@ pg_signal_backend(int pid, int sig)
 	 * too unlikely to worry about.
 	 */
 
+	if (msg != NULL && msg[0] != '\0')
+	{
+		int		r = BackendMsgSet(pid, msg);
+
+		if (r != -1 && r != strlen(msg))
+			ereport(NOTICE,
+					(errmsg("message is too long, truncated to %d bytes", r)));
+	}
+
 	/* If we have setsid(), signal the backend's whole process group */
 #ifdef HAVE_SETSID
 	if (kill(-pid, sig))
@@ -132,10 +143,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 Datum
+pg_cancel_backend_internal(pid_t pid, const 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,
@@ -161,6 +172,34 @@ pg_cancel_backend(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum pg_cancel_backend(PG_FUNCTION_ARGS)
+{
+	int		pid;
+
+	pid = PG_GETARG_INT32(0);
+
+	return pg_cancel_backend_internal(pid, NULL);
+}
+
+Datum pg_cancel_backend_msg(PG_FUNCTION_ARGS)
+{
+	int		pid;
+	char	*msg;
+	Datum	result;
+
+	pid = PG_GETARG_INT32(0);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	if (msg == NULL) {
+		PG_RETURN_BOOL(false);
+	}
+
+	result = pg_cancel_backend_internal(pid, msg);
+
+	pfree(msg);
+
+	return result;
+}
+
 /*
  * Wait until there is no backend process with the given PID and return true.
  * On timeout, a warning is emitted and false is returned.
@@ -233,22 +272,17 @@ pg_wait_until_termination(int pid, int64 timeout)
  *
  * Note that only superusers can signal superuser-owned processes.
  */
-Datum
-pg_terminate_backend(PG_FUNCTION_ARGS)
+static Datum
+pg_terminate_backend_internal(int pid, int timeout, const char *msg)
 {
-	int			pid;
 	int			r;
-	int			timeout;		/* milliseconds */
-
-	pid = PG_GETARG_INT32(0);
-	timeout = PG_GETARG_INT64(1);
 
 	if (timeout < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("\"timeout\" must not be negative")));
 
-	r = pg_signal_backend(pid, SIGTERM);
+	r = pg_signal_backend(pid, SIGTERM, msg);
 
 	if (r == SIGNAL_BACKEND_NOSUPERUSER)
 		ereport(ERROR,
@@ -278,6 +312,38 @@ pg_terminate_backend(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(r == SIGNAL_BACKEND_SUCCESS);
 }
 
+Datum pg_terminate_backend(PG_FUNCTION_ARGS)
+{
+	int		pid;
+	int		timeout_ms;
+
+	pid = PG_GETARG_INT32(0);
+	timeout_ms = PG_GETARG_INT64(1);
+
+	return pg_terminate_backend_internal(pid, timeout_ms, NULL);
+}
+
+Datum pg_terminate_backend_msg(PG_FUNCTION_ARGS)
+{
+	int		pid;
+	int		timeout_ms;
+	char	*msg;
+	Datum	result;
+
+	pid = PG_GETARG_INT32(0);
+	timeout_ms = PG_GETARG_INT64(1);
+	msg = text_to_cstring(PG_GETARG_TEXT_PP(2));
+	if (msg == NULL) {
+		PG_RETURN_BOOL(false);
+	}
+
+	result = pg_terminate_backend_internal(pid, timeout_ms, msg);
+
+	pfree(msg);
+
+	return result;
+}
+
 /*
  * Signal to reload the database configuration
  *
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b4a8d2f3a1c..3c6d285a4dc 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -81,6 +81,7 @@
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 #include "utils/varlena.h"
+#include "utils/backend_msg.h"
 
 /* ----------------
  *		global variables
@@ -3356,9 +3357,22 @@ ProcessInterrupts(void)
 			proc_exit(0);
 		}
 		else
+		{
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						errmsg("terminating connection due to administrator command: %s", msg)));
+			}
+
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
+		}
 	}
 
 	if (CheckClientConnectionPending)
@@ -3466,9 +3480,21 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead)
 		{
 			LockErrorCleanup();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+
+			if (BackendMsgIsSet())
+			{
+				char msg[BACKEND_MSG_MAX_LEN];
+
+				BackendMsgGet(msg, sizeof(msg));
+
+				ereport(ERROR,
+						(errcode(ERRCODE_QUERY_CANCELED),
+						 errmsg("canceling statement due to user request: %s", msg)));
+			}
+			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 3f401faf3de..debf6da4a7b 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -69,6 +69,7 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/timeout.h"
+#include "utils/backend_msg.h"
 
 static HeapTuple GetDatabaseTuple(const char *dbname);
 static HeapTuple GetDatabaseTupleByOid(Oid dboid);
@@ -902,6 +903,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
 			InitializeSystemUser(MyClientConnectionInfo.authn_id,
 								 hba_authname(MyClientConnectionInfo.auth_method));
 		am_superuser = superuser();
+		BackendMsgInit(MyProcNumber);
 	}
 
 	/* Report any SSL/GSS details for the session. */
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index f142d17178b..5494994669f 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -32,7 +32,8 @@ OBJS = \
 	stack_depth.o \
 	superuser.o \
 	timeout.o \
-	tzparser.o
+	tzparser.o \
+	backend_msg.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_msg.c b/src/backend/utils/misc/backend_msg.c
new file mode 100644
index 00000000000..34d44069056
--- /dev/null
+++ b/src/backend/utils/misc/backend_msg.c
@@ -0,0 +1,157 @@
+/*--------------------------------------------------------------------
+ * backend_msg.c
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/misc/backend_msg.c
+ *
+ *--------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "mb/pg_wchar.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "storage/ipc.h"
+#include "utils/backend_msg.h"
+
+typedef struct {
+	pid_t pid;
+	slock_t lock;
+	char msg[BACKEND_MSG_MAX_LEN];
+} BackendMsgSlot;
+
+
+static BackendMsgSlot *BackendMsgSlots;
+static BackendMsgSlot *MyBackendMsgSlot;
+
+static void
+backend_msg_slot_clean(int code, Datum arg)
+{
+	(void) code;
+	(void) arg;
+
+	Assert(MyBackendMsgSlot != NULL);
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot->msg[0] = '\0';
+	MyBackendMsgSlot->pid = 0;
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	MyBackendMsgSlot = NULL;
+}
+
+void BackendMsgShmemInit(void)
+{
+	Size	size;
+	bool	found;
+
+	size = BackendMsgShmemSize();
+	BackendMsgSlots = ShmemInitStruct("BackendMsgSlots", size, &found);
+
+	if (found)
+		return;
+
+	memset(BackendMsgSlots, 0, size);
+
+	for (int i = 0; i < MaxBackends; ++i)
+		SpinLockInit(&BackendMsgSlots[i].lock);
+}
+
+Size
+BackendMsgShmemSize(void)
+{
+	return mul_size(MaxBackends, sizeof(BackendMsgSlot));
+}
+
+void BackendMsgInit(int id)
+{
+	BackendMsgSlot		*slot;
+
+	slot = &BackendMsgSlots[id];
+
+	slot->msg[0] = '\0';
+	slot->pid = MyProcPid;
+
+	MyBackendMsgSlot = slot;
+
+	on_shmem_exit(backend_msg_slot_clean, Int32GetDatum(0) /* not used */);
+}
+
+int BackendMsgSet(pid_t pid, const char *msg)
+{
+	BackendMsgSlot		*slot;
+	int					len;
+
+	if (msg == NULL || msg[0] == '\0')
+		return 0;
+
+	for (int i = 0; i < MaxBackends; ++i)
+	{
+		slot = &BackendMsgSlots[i];
+
+		if (slot->pid == 0 || slot->pid != pid)
+			continue;
+
+		SpinLockAcquire(&slot->lock);
+
+		if (slot->pid != pid)
+		{
+			SpinLockRelease(&slot->lock);
+			break;
+		}
+
+		len = pg_mbcliplen(msg, strlen(msg), sizeof(slot->msg) - 1);
+		memcpy(slot->msg, msg, len);
+		slot->msg[len] = '\0';
+
+		SpinLockRelease(&slot->lock);
+
+		return len;
+	}
+
+	ereport(LOG,
+			(errmsg("can't set message for missing backend %ld, requested by %ld",
+				(long) pid, (long) MyProcPid)));
+
+	return -1;
+}
+
+int BackendMsgGet(char *buf, int max_len)
+{
+	int		len;
+
+	if (MyBackendMsgSlot == NULL)
+		return 0;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+
+	len = strlcpy(buf, MyBackendMsgSlot->msg, max_len);
+	memset(MyBackendMsgSlot->msg, '\0', sizeof(MyBackendMsgSlot->msg));
+
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return len;
+}
+
+bool BackendMsgIsSet(void)
+{
+	bool result = false;
+
+	if (MyBackendMsgSlot == NULL)
+		return false;
+
+	SpinLockAcquire(&MyBackendMsgSlot->lock);
+	result = MyBackendMsgSlot->msg[0] != '\0';
+	SpinLockRelease(&MyBackendMsgSlot->lock);
+
+	return result;
+}
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 232e74d0af9..831bf6c6bab 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -1,6 +1,7 @@
 # Copyright (c) 2022-2026, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'backend_msg.c',
   'conffiles.c',
   'guc.c',
   'guc_funcs.c',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5e5e33f64fc..36df85318f4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6725,10 +6725,17 @@
 { oid => '2171', descr => 'cancel a server process\' current query',
   proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'int4', prosrc => 'pg_cancel_backend' },
+{ oid => '8223', descr => 'cancel a server process\' current query',
+  proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4 text', prosrc => 'pg_cancel_backend_msg' },
 { oid => '2096', descr => 'terminate a server process',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
   prosrc => 'pg_terminate_backend' },
+{ oid => '8222', descr => 'terminate a server process',
+  proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
+  proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,message}',
+  prosrc => 'pg_terminate_backend_msg' },
 { oid => '2172', descr => 'prepare for taking an online backup',
   proname => 'pg_backup_start', provolatile => 'v', proparallel => 'r',
   prorettype => 'pg_lsn', proargtypes => 'text bool',
diff --git a/src/include/utils/backend_msg.h b/src/include/utils/backend_msg.h
new file mode 100644
index 00000000000..7e53c59845f
--- /dev/null
+++ b/src/include/utils/backend_msg.h
@@ -0,0 +1,28 @@
+/*--------------------------------------------------------------------
+ * backend_msg.h
+ *
+ * Utility to pass additional message to backend processes.
+ * Ex: cancel or terminate messages
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/backend_msg.h
+ *
+ *--------------------------------------------------------------------
+ */
+
+#ifndef BACKEND_MSG_H
+#define BACKEND_MSG_H
+
+#define BACKEND_MSG_MAX_LEN 128
+
+extern void BackendMsgShmemInit(void);
+extern Size BackendMsgShmemSize(void);
+extern void BackendMsgInit(int id);
+extern int BackendMsgSet(pid_t pid, const char *msg);
+extern int BackendMsgGet(char *buf, int max_len);
+extern bool BackendMsgIsSet(void);
+
+
+#endif /* BACKEND_MSG_H */
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6e8db1621a7..674675b7ce1 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -19,6 +19,7 @@ tests += {
       't/008_replslot_single_user.pl',
       't/009_log_temp_files.pl',
       't/010_index_concurrently_upsert.pl',
+      't/011_backend_msg.pl',
     ],
     # The injection points are cluster-wide, so disable installcheck
     'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/011_backend_msg.pl b/src/test/modules/test_misc/t/011_backend_msg.pl
new file mode 100644
index 00000000000..94448821a67
--- /dev/null
+++ b/src/test/modules/test_misc/t/011_backend_msg.pl
@@ -0,0 +1,42 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Check that messages are passed to backends by
+# pg_terminate_backend, pg_cancel_backend
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init();
+$node->start;
+
+my ($stdout, $stderr);
+$node->psql('postgres',
+	q[select pg_terminate_backend(pg_backend_pid(), 0, 'Have you seen my coffee cup?');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/Have you seen my coffee cup\?/, "expected message to be passed in pg_terminate_backend");
+
+$stdout = '';
+$stderr = '';
+$node->psql('postgres',
+	q[select pg_cancel_backend(pg_backend_pid(), 'You have to wear some ridiculous tie');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/You have to wear some ridiculous tie/, "expected message to be passed in pg_cancel_backend");
+
+$stdout = '';
+$stderr = '';
+my $longstr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+my $truncated = substr($longstr, 0, 127);
+$node->psql('postgres',
+	qq[select pg_terminate_backend(pg_backend_pid(), 0, '$longstr');],
+	stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/NOTICE:  message is too long, truncated to 127 bytes/, "NOTICE message should be created");
+like($stderr, qr/\Q$truncated\E/, "expected truncated message (127 chars) to be passed");
+unlike($stderr, qr/\Q$longstr\E/, "full message must not be passed");
+
+$node->stop;
+
+done_testing();
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-02-03 18:22  Jim Jones <[email protected]>
  parent: Roman Khapov <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Jim Jones @ 2026-02-03 18:22 UTC (permalink / raw)
  To: Roman Khapov <[email protected]>; Andrey Borodin <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]

Hi Roman

On 03/02/2026 13:28, Roman Khapov wrote:
> Thanks for another round on review, updated the patch according to comments.
> 
> Also, fix `make check` by updating pg_proc.data with new functions,
> similar to uuid7 way: defining _msg versions of the functions


Why did you decide to revert the pg_proc.dat and system_functions.sql
changes from v3? In v3 I thought that adding a msg DEFAULT '' to the
corresponding function in system_functions.sql ...

--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -400,7 +400,12 @@ CREATE OR REPLACE FUNCTION
   PARALLEL SAFE;

 CREATE OR REPLACE FUNCTION
-  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0)
+  pg_cancel_backend(pid integer, msg text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_cancel_backend'
+  PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, msg text
DEFAULT '')
   RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS
'pg_terminate_backend'
   PARALLEL SAFE;

... and updating pg_proc.dat accordingly would do the trick

diff --git a/src/include/catalog/pg_proc.dat
b/src/include/catalog/pg_proc.dat
index 5e5e33f64f..47aa30d716 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6724,10 +6724,11 @@

 { 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', proargnames => '{pid,msg}',
+  prosrc => 'pg_cancel_backend' },
 { oid => '2096', descr => 'terminate a server process',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype =>
'bool',
-  proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
+  proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,msg}',
   prosrc => 'pg_terminate_backend' },
 { oid => '2172', descr => 'prepare for taking an online backup',
   proname => 'pg_backup_start', provolatile => 'v', proparallel => 'r',


My rationale is based on the header in system_function.sql:

...
 * src/backend/catalog/system_functions.sql
 *
 * This file redefines certain built-in functions that are impractical
 * to fully define in pg_proc.dat.  In most cases that's because they use
 * SQL-standard function bodies and/or default expressions...


Am I missing something?

Thanks

Best, Jim







^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-04-05 19:27  warda Bibi <[email protected]>
  parent: Jim Jones <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: warda Bibi @ 2026-04-05 19:27 UTC (permalink / raw)
  To: Jim Jones <[email protected]>; +Cc: Roman Khapov <[email protected]>; Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]

Hi all,

Thank you for the continued work on this patch.

Prafulla Ranadive and I reviewed this patch through the patch review
workshop. Overall, we find that the proposed feature is useful. We
have reviewed all five versions and have the following observations.

We agree with Jim's point of view that the v3 approach is better. v5
introduced separate _msg overloads (OID 8223 for
pg_cancel_backend_msg, OID 8222 for pg_terminate_backend_msg), which
adds catalog bloat and forces callers to use a different function
name:

> +{ oid => '8223', descr => 'cancel a server process\' current query',
>   proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
>   proargtypes => 'int4 text', prosrc => 'pg_cancel_backend_msg' },
> +{ oid => '8222', descr => 'terminate a server process',
>   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
>   proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,message}',
>   prosrc => 'pg_terminate_backend_msg' },

V3 keeps one OID per function and stays backward-compatible. v6
restores the v3 approach.

--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6727,19 +6727,14 @@

 { oid => '2171', descr => 'cancel a server process\' current query',
   proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4', prosrc => 'pg_cancel_backend' },
-{ oid => '8223', descr => 'cancel a server process\' current query',
-  proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4 text', prosrc => 'pg_cancel_backend_msg' },
+  proargtypes => 'int4 text', proargnames => '{pid,message}',
+  proargdefaults => '{""}'.
+  prosrc => 'pg_cancel_backend' },
 { oid => '2096', descr => 'terminate a server process',
-  proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
-  proargdefaults => '{0}',
-  prosrc => 'pg_terminate_backend' },
-{ oid => '8222', descr => 'terminate a server process',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,message}',
-  prosrc => 'pg_terminate_backend_msg' },
+  proargdefaults => '{0,""}',
+  prosrc => 'pg_terminate_backend' },


--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -378,6 +378,16 @@ BEGIN ATOMIC
 END;

+CREATE OR REPLACE FUNCTION
+  pg_cancel_backend(pid integer, message text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_cancel_backend'
+  PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, message
text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend'
+  PARALLEL SAFE;
+

The current approach scans all MaxBackends slots by PID twice. Since
pg_signal_backend() already calls BackendPidGetProc(pid) and has the
PGPROC* in hand, the PGPROC index into allProcs[ ] is the ProcNumber,
which is also the direct index into BackendMsgSlots[]. So v6 changes
the signature to BackendMsgSet(ProcNumber procno, ...) and the call in
pg_signal_backend() passes GetNumberFromPGProc(proc), making the
lookup O(1).

-int BackendMsgSet(pid_t pid, const char *msg)
+int BackendMsgSet(ProcNumber procno, const char *msg)
 {
    BackendMsgSlot      *slot;
    int                 len;
@@ -94,35 +94,26 @@ int BackendMsgSet(pid_t pid, const char *msg)
    if (msg == NULL || msg[0] == '\0')
        return 0;

-   for (int i = 0; i < MaxBackends; ++i)
-   {
-       slot = &BackendMsgSlots[i];
-
-       if (slot->pid == 0 || slot->pid != pid)
-           continue;
-
-       SpinLockAcquire(&slot->lock);
-
-       if (slot->pid != pid)
-       {
-           SpinLockRelease(&slot->lock);
-           break;
-       }
+   slot = &BackendMsgSlots[procno];


Also, fixed a stale file-header path in backend_msg.c:

--- a/src/backend/utils/misc/backend_msg.c
+++ b/src/backend/utils/misc/backend_msg.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * src/include/utils/misc/backend_msg.c
+ * src/backend/utils/misc/backend_msg.c

Removed dead NULL checks on msg in pg_cancel_backend_msg() and
pg_terminate_backend_msg() because text_to_cstring() never returns
NULL.

    pid = PG_GETARG_INT32(0);
    msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
-   if (msg == NULL) {
-       PG_RETURN_BOOL(false);
-   }

Furthermore, the else before the fallback ereport(FATAL) in the
terminate path is missing in v5. Without the else, the fallback
ereport is unreachable when BackendMsgIsSet() is true because
ereport(FATAL) never returns, but it misleads readers into thinking
the generic message is always emitted. Adding an else is also
consistent with the pg_cancel approach. v6 adds else to make the
intent explicit.

-           ereport(FATAL,
-                   (errcode(ERRCODE_ADMIN_SHUTDOWN),
-                    errmsg("terminating connection due to
administrator command")));
+           else
+               ereport(FATAL,
+                       (errcode(ERRCODE_ADMIN_SHUTDOWN),
+                        errmsg("terminating connection due to
administrator command")));
        }
    }


The _internal helper functions (pg_cancel_backend_internal,
pg_terminate_backend_internal) each have only one call site. What is
the intent behind keeping them separate, or can they be inlined
directly into pg_cancel_backend() and pg_terminate_backend()?

Andrey noted that:

> We have a race condition if many backends cancel same backend.
> Won't they mess each other's reason?

The spinlock in BackendMsgSet protects the write, but there is no
atomicity between writing the slot and delivering the signal. Another
backend can overwrite the slot between one writer's write and the
target reading it. Would it be valuable to define the behavior
explicitly, for example last-writer-wins, or document the limitation?

Andrey also raised this about translation:

> Keep in mind that Postgres literals are translated into many languages.
> So text ought to be clear enough for translators to build a sentence
> that precedes termination reason.

The current pattern:

>   errmsg("terminating connection due to administrator command: %s", msg)

The colon-append pattern breaks in languages where the reason clause
is structured differently. Any suggestions on how to approach this, or
how similar cases are handled elsewhere in the codebase?

Also, parallel workers receive the generic message as they hit the
IsBackgroundWorker branch in ProcessInterrupts(), which bypasses
BackendMsgGet(). This is fine since parallel workers are ephemeral,
but should it be documented in the docs?

Patch attached.

--

Best Wishes,
Warda Bibi


Attachments:

  [application/octet-stream] v6-0001-message-in-pg_terminate_backend-and-pg_cancel_backend.patch (8.8K, 2-v6-0001-message-in-pg_terminate_backend-and-pg_cancel_backend.patch)
  download | inline diff:
From 3e166b0fb0cc9800a9a898240adb4750e4c6c705 Mon Sep 17 00:00:00 2001
From: Warda Bibi <[email protected]>
Date: Sat, 4 Apr 2026 20:12:58 +0500
Subject: [PATCH v6] message in pg_terminate_backend and pg_cancel_backend
 
Sometimes it is useful to terminate or cancel a backend with an
additional message explaining the reason, so the client sees something
more informative than the generic error text.

This patch adds an optional message argument to pg_terminate_backend()
and pg_cancel_backend(), folded into the existing functions via DEFAULT
parameters so no new catalog entries are needed:

  pg_cancel_backend(pid integer, message text DEFAULT '')
  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, message text DEFAULT '')

The message is stored in a dedicated shared memory region
(BackendMsgSlots, one slot per MaxBackends indexed by ProcNumber) and
read by the target backend in ProcessInterrupts() before the
ereport(FATAL/ERROR). Unicode-safe truncation is applied via
pg_mbcliplen() if the message exceeds BACKEND_MSG_MAX_LEN bytes, with
a NOTICE emitted to the caller.

Author: Daniel Gustafsson <[email protected]>
Author: Roman Khapov <[email protected]>
Reviewed-by: Kirill Reshke <[email protected]>
Reviewed-by: Jim Jones <[email protected]>
Reviewed-by: Andrey Borodin <[email protected]>
---
 src/backend/catalog/system_functions.sql | 10 ++++++
 src/backend/storage/ipc/signalfuncs.c    | 32 ++----------------
 src/backend/tcop/postgres.c              |  8 ++---
 src/backend/utils/misc/backend_msg.c     | 41 +++++++++---------------
 src/include/catalog/pg_proc.dat          | 15 +++------
 src/include/utils/backend_msg.h          |  4 ++-
 6 files changed, 41 insertions(+), 69 deletions(-)

diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 69699f8830a..7dbad6a279b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -378,6 +378,16 @@ BEGIN ATOMIC
 END;
 
 
+CREATE OR REPLACE FUNCTION
+  pg_cancel_backend(pid integer, message text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_cancel_backend'
+  PARALLEL SAFE;
+
+CREATE OR REPLACE FUNCTION
+  pg_terminate_backend(pid integer, timeout int8 DEFAULT 0, message text DEFAULT '')
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_terminate_backend'
+  PARALLEL SAFE;
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 2501ec58658..eabb341a2ba 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -112,7 +112,7 @@ pg_signal_backend(int pid, int sig, const char *msg)
 
 	if (msg != NULL && msg[0] != '\0')
 	{
-		int		r = BackendMsgSet(pid, msg);
+		int		r = BackendMsgSet(GetNumberFromPGProc(proc), msg);
 
 		if (r != -1 && r != strlen(msg))
 			ereport(NOTICE,
@@ -172,23 +172,11 @@ pg_cancel_backend_internal(pid_t pid, const char *msg)
 Datum pg_cancel_backend(PG_FUNCTION_ARGS)
 {
 	int		pid;
-
-	pid = PG_GETARG_INT32(0);
-
-	return pg_cancel_backend_internal(pid, NULL);
-}
-
-Datum pg_cancel_backend_msg(PG_FUNCTION_ARGS)
-{
-	int		pid;
-	char	*msg;
+	char   *msg;
 	Datum	result;
 
 	pid = PG_GETARG_INT32(0);
 	msg = text_to_cstring(PG_GETARG_TEXT_PP(1));
-	if (msg == NULL) {
-		PG_RETURN_BOOL(false);
-	}
 
 	result = pg_cancel_backend_internal(pid, msg);
 
@@ -313,26 +301,12 @@ Datum pg_terminate_backend(PG_FUNCTION_ARGS)
 {
 	int		pid;
 	int		timeout_ms;
-
-	pid = PG_GETARG_INT32(0);
-	timeout_ms = PG_GETARG_INT64(1);
-
-	return pg_terminate_backend_internal(pid, timeout_ms, NULL);
-}
-
-Datum pg_terminate_backend_msg(PG_FUNCTION_ARGS)
-{
-	int		pid;
-	int		timeout_ms;
-	char	*msg;
+	char   *msg;
 	Datum	result;
 
 	pid = PG_GETARG_INT32(0);
 	timeout_ms = PG_GETARG_INT64(1);
 	msg = text_to_cstring(PG_GETARG_TEXT_PP(2));
-	if (msg == NULL) {
-		PG_RETURN_BOOL(false);
-	}
 
 	result = pg_terminate_backend_internal(pid, timeout_ms, msg);
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 92040d748f4..25d2857f69f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3402,10 +3402,10 @@ ProcessInterrupts(void)
 						(errcode(ERRCODE_ADMIN_SHUTDOWN),
 						errmsg("terminating connection due to administrator command: %s", msg)));
 			}
-
-			ereport(FATAL,
-					(errcode(ERRCODE_ADMIN_SHUTDOWN),
-					 errmsg("terminating connection due to administrator command")));
+			else
+				ereport(FATAL,
+						(errcode(ERRCODE_ADMIN_SHUTDOWN),
+						 errmsg("terminating connection due to administrator command")));
 		}
 	}
 
diff --git a/src/backend/utils/misc/backend_msg.c b/src/backend/utils/misc/backend_msg.c
index 34d44069056..560aee148be 100644
--- a/src/backend/utils/misc/backend_msg.c
+++ b/src/backend/utils/misc/backend_msg.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * src/include/utils/misc/backend_msg.c
+ * src/backend/utils/misc/backend_msg.c
  *
  *--------------------------------------------------------------------
  */
@@ -86,7 +86,7 @@ void BackendMsgInit(int id)
 	on_shmem_exit(backend_msg_slot_clean, Int32GetDatum(0) /* not used */);
 }
 
-int BackendMsgSet(pid_t pid, const char *msg)
+int BackendMsgSet(ProcNumber procno, const char *msg)
 {
 	BackendMsgSlot		*slot;
 	int					len;
@@ -94,35 +94,26 @@ int BackendMsgSet(pid_t pid, const char *msg)
 	if (msg == NULL || msg[0] == '\0')
 		return 0;
 
-	for (int i = 0; i < MaxBackends; ++i)
-	{
-		slot = &BackendMsgSlots[i];
-
-		if (slot->pid == 0 || slot->pid != pid)
-			continue;
-
-		SpinLockAcquire(&slot->lock);
-
-		if (slot->pid != pid)
-		{
-			SpinLockRelease(&slot->lock);
-			break;
-		}
+	slot = &BackendMsgSlots[procno];
 
-		len = pg_mbcliplen(msg, strlen(msg), sizeof(slot->msg) - 1);
-		memcpy(slot->msg, msg, len);
-		slot->msg[len] = '\0';
+	SpinLockAcquire(&slot->lock);
 
+	if (slot->pid == 0)
+	{
 		SpinLockRelease(&slot->lock);
-
-		return len;
+		ereport(LOG,
+				(errmsg("can't set message for missing backend, requested by %ld",
+					(long) MyProcPid)));
+		return -1;
 	}
 
-	ereport(LOG,
-			(errmsg("can't set message for missing backend %ld, requested by %ld",
-				(long) pid, (long) MyProcPid)));
+	len = pg_mbcliplen(msg, strlen(msg), sizeof(slot->msg) - 1);
+	memcpy(slot->msg, msg, len);
+	slot->msg[len] = '\0';
 
-	return -1;
+	SpinLockRelease(&slot->lock);
+
+	return len;
 }
 
 int BackendMsgGet(char *buf, int max_len)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2732dcb6867..3833fc4ba2c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6727,19 +6727,14 @@
 
 { oid => '2171', descr => 'cancel a server process\' current query',
   proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4', prosrc => 'pg_cancel_backend' },
-{ oid => '8223', descr => 'cancel a server process\' current query',
-  proname => 'pg_cancel_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4 text', prosrc => 'pg_cancel_backend_msg' },
+  proargtypes => 'int4 text', proargnames => '{pid,message}',
+  proargdefaults => '{""}',
+  prosrc => 'pg_cancel_backend' },
 { oid => '2096', descr => 'terminate a server process',
-  proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
-  proargtypes => 'int4 int8', proargnames => '{pid,timeout}',
-  proargdefaults => '{0}',
-  prosrc => 'pg_terminate_backend' },
-{ oid => '8222', descr => 'terminate a server process',
   proname => 'pg_terminate_backend', provolatile => 'v', prorettype => 'bool',
   proargtypes => 'int4 int8 text', proargnames => '{pid,timeout,message}',
-  prosrc => 'pg_terminate_backend_msg' },
+  proargdefaults => '{0,""}',
+  prosrc => 'pg_terminate_backend' },
 { oid => '2172', descr => 'prepare for taking an online backup',
   proname => 'pg_backup_start', provolatile => 'v', proparallel => 'r',
   prorettype => 'pg_lsn', proargtypes => 'text bool',
diff --git a/src/include/utils/backend_msg.h b/src/include/utils/backend_msg.h
index 7e53c59845f..5ccad83af11 100644
--- a/src/include/utils/backend_msg.h
+++ b/src/include/utils/backend_msg.h
@@ -15,12 +15,14 @@
 #ifndef BACKEND_MSG_H
 #define BACKEND_MSG_H
 
+#include "storage/proc.h"
+
 #define BACKEND_MSG_MAX_LEN 128
 
 extern void BackendMsgShmemInit(void);
 extern Size BackendMsgShmemSize(void);
 extern void BackendMsgInit(int id);
-extern int BackendMsgSet(pid_t pid, const char *msg);
+extern int BackendMsgSet(ProcNumber procno, const char *msg);
 extern int BackendMsgGet(char *buf, int max_len);
 extern bool BackendMsgIsSet(void);
 
-- 
2.51.0



^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-04-09 11:07  Jim Jones <[email protected]>
  parent: warda Bibi <[email protected]>
  0 siblings, 1 reply; 15+ messages in thread

From: Jim Jones @ 2026-04-09 11:07 UTC (permalink / raw)
  To: warda Bibi <[email protected]>; +Cc: Roman Khapov <[email protected]>; Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]

Hi Warda and Prafulla

On 05/04/2026 21:27, warda Bibi wrote:
> Prafulla Ranadive and I reviewed this patch through the patch review
> workshop. Overall, we find that the proposed feature is useful.

Thanks for the nice review!

Roman, are you still planning to work on this? The patch has been
failing for several weeks.

https://cirrus-ci.com/github/postgresql-cfbot/postgresql/cf%2F6331

Best, Jim






^ permalink  raw  reply  [nested|flat] 15+ messages in thread

* Re: Additional message in pg_terminate_backend
@ 2026-04-10 06:05  Roman Khapov <[email protected]>
  parent: Jim Jones <[email protected]>
  0 siblings, 0 replies; 15+ messages in thread

From: Roman Khapov @ 2026-04-10 06:05 UTC (permalink / raw)
  To: Jim Jones <[email protected]>; +Cc: warda Bibi <[email protected]>; Andrey Borodin <[email protected]>; Kirill Reshke <[email protected]>; Daniel Gustafsson <[email protected]>; [email protected]

Hi!

Thanks Warda for review!

Yes, I am planning to continue work on this patch, will send new version soon.

--
Best regards,
Roman Khapov

> On 9 Apr 2026, at 16:07, Jim Jones <[email protected]> wrote:
> 
> Hi Warda and Prafulla
> 
> On 05/04/2026 21:27, warda Bibi wrote:
>> Prafulla Ranadive and I reviewed this patch through the patch review
>> workshop. Overall, we find that the proposed feature is useful.
> 
> Thanks for the nice review!
> 
> Roman, are you still planning to work on this? The patch has been
> failing for several weeks.
> 
> https://cirrus-ci.com/github/postgresql-cfbot/postgresql/cf%2F6331
> 
> Best, Jim
> 






^ permalink  raw  reply  [nested|flat] 15+ messages in thread


end of thread, other threads:[~2026-04-10 06:05 UTC | newest]

Thread overview: 15+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-12-13 07:44 Additional message in pg_terminate_backend Roman Khapov <[email protected]>
2025-12-17 11:34 ` Andrey Borodin <[email protected]>
2025-12-17 16:02 ` Daniel Gustafsson <[email protected]>
2025-12-20 09:26   ` Roman Khapov <[email protected]>
2025-12-20 10:32     ` Kirill Reshke <[email protected]>
2026-02-01 00:08       ` Jim Jones <[email protected]>
2026-02-01 13:58         ` Jim Jones <[email protected]>
2026-02-03 07:52           ` Roman Khapov <[email protected]>
2026-02-03 08:21             ` Jim Jones <[email protected]>
2026-02-03 08:26             ` Andrey Borodin <[email protected]>
2026-02-03 12:28               ` Roman Khapov <[email protected]>
2026-02-03 18:22                 ` Jim Jones <[email protected]>
2026-04-05 19:27                   ` warda Bibi <[email protected]>
2026-04-09 11:07                     ` Jim Jones <[email protected]>
2026-04-10 06:05                       ` Roman Khapov <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox