public inbox for [email protected]
help / color / mirror / Atom feedFrom: Roman Khapov <[email protected]>
To: [email protected]
Subject: Additional message in pg_terminate_backend
Date: Sat, 13 Dec 2025 12:44:46 +0500
Message-ID: <[email protected]> (raw)
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)
view thread (8+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected]
Subject: Re: Additional message in pg_terminate_backend
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox