From 2c9fee9f5d8b41c24cf78b82195c532de9595231 Mon Sep 17 00:00:00 2001
From: Greg Burd <greg@burd.me>
Date: Thu, 12 Nov 2026 12:48:30 -0500
Subject: [PATCH v4 17/21] port: Add pg_thrd_setname(), and use it in pgbench
 and pg_basebackup.

A process with several busy threads is hard to diagnose when every
thread shows the program's name in ps, top, and debuggers.  Add
pg_thrd_setname() to pg_threads_ext.h, which names the calling
thread where the system allows it and otherwise does nothing.  It
takes no thread argument, because macOS can only name the calling
thread, and it reports no errors, because a name is only a
diagnostic aid.

Use it to name pgbench's worker threads and pg_basebackup's WAL
streamer thread.  The main threads keep the process name: on Linux,
naming the main thread would rename the process as ps sees it.

Suggested-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
Discussion: https://postgr.es/m/CAKZiRmw3gpoXeOc9Pa_PRpfp-bTeQhg6gaY2Jw0Ck6NX4PWPhQ%40mail.gmail.com
---
 src/bin/pg_basebackup/pg_basebackup.c         |  3 ++
 src/bin/pgbench/pgbench.c                     | 12 +++++
 src/include/port/pg_threads_ext.h             |  3 ++
 src/port/pg_threads.c                         | 35 ++++++++++++++
 .../modules/test_pg_threads/test_pg_threads.c | 48 +++++++++++++++++++
 5 files changed, 101 insertions(+)

diff --git a/src/bin/pg_basebackup/pg_basebackup.c b/src/bin/pg_basebackup/pg_basebackup.c
index 193404c7458..89b2aaeadd6 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -496,6 +496,9 @@ LogStreamerMain(void *argument)
 	logstreamer_param *param = argument;
 	StreamCtl	stream = {0};
 
+	/* Linux keeps only 15 bytes of a thread name. */
+	pg_thrd_setname("pg_basebkup wal");
+
 	stream.startpos = param->startptr;
 	stream.timeline = param->timeline;
 	stream.sysidentifier = param->sysidentifier;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 3baf419f7d8..ce0b92f3f52 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -7508,6 +7508,18 @@ threadRun(void *arg)
 	StatsData	last,
 				aggs;
 
+	/*
+	 * Name worker threads for debuggers and tools like ps.  Thread 0 is the
+	 * main thread, which keeps the process name.
+	 */
+	if (thread->tid > 0)
+	{
+		char		name[32];
+
+		snprintf(name, sizeof(name), "pgbench %d", thread->tid);
+		pg_thrd_setname(name);
+	}
+
 	/* open log file if requested */
 	if (use_log)
 	{
diff --git a/src/include/port/pg_threads_ext.h b/src/include/port/pg_threads_ext.h
index fa3f54cf35d..d9a50d3c9a6 100644
--- a/src/include/port/pg_threads_ext.h
+++ b/src/include/port/pg_threads_ext.h
@@ -79,6 +79,9 @@ static inline int pg_thrd_barrier_wait(pg_thrd_barrier_t *barrier);
 extern const char *pg_thrd_error_string(int error);
 extern const char *pg_thrd_error_string_with_detail(int error);
 
+/* name the calling thread, for debuggers and process listings */
+extern void pg_thrd_setname(const char *name);
+
 /* internal self-test */
 static inline void pg_threads_ext_assertions(void);
 
diff --git a/src/port/pg_threads.c b/src/port/pg_threads.c
index 292771c94aa..8ac83574229 100644
--- a/src/port/pg_threads.c
+++ b/src/port/pg_threads.c
@@ -17,6 +17,9 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+#include <pthread_np.h>
+#endif
 
 
 /*-------------------------------------------------------------------------
@@ -277,6 +280,38 @@ pg_rwlock_init_impl(pg_rwlock_impl *lock, int type)
 
 #endif
 
+/*
+ * Give the calling thread a name that debuggers and tools like ps and top
+ * can show.  This is best-effort: the name may be truncated (Linux keeps 15
+ * bytes), it is silently ignored where the system has no way to name
+ * threads, and failures are not reported.
+ *
+ * Only the calling thread can be named, because macOS provides no way to
+ * name another thread.  NB: on Linux, naming a process's main thread also
+ * changes the process name shown by ps.
+ */
+void
+pg_thrd_setname(const char *name)
+{
+#if defined(WIN32)
+	wchar_t		wname[64];
+
+	if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, lengthof(wname)) > 0)
+		(void) SetThreadDescription(GetCurrentThread(), wname);
+#elif defined(__APPLE__)
+	(void) pthread_setname_np(name);
+#elif defined(__linux__)
+	char		truncated[16];
+
+	strlcpy(truncated, name, sizeof(truncated));
+	(void) pthread_setname_np(pthread_self(), truncated);
+#elif defined(__NetBSD__)
+	(void) pthread_setname_np(pthread_self(), "%s", unconstify(char *, name));
+#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+	pthread_set_name_np(pthread_self(), name);
+#endif
+}
+
 #if defined(PG_THREADS_USE_WINDOWS_H)
 
 /*-------------------------------------------------------------------------
diff --git a/src/test/modules/test_pg_threads/test_pg_threads.c b/src/test/modules/test_pg_threads/test_pg_threads.c
index 3c0320c2e1a..c2fddd73bca 100644
--- a/src/test/modules/test_pg_threads/test_pg_threads.c
+++ b/src/test/modules/test_pg_threads/test_pg_threads.c
@@ -519,6 +519,53 @@ test_rwlock(void)
 	pg_rwlock_destroy(&rwlock);
 }
 
+/*
+ * Naming the calling thread is best-effort, so most systems just check that
+ * it doesn't crash.  On Windows and Linux, read the name back.
+ */
+static int
+setname_thread(void *arg)
+{
+	pg_thrd_setname("test_pg_threads worker with a long name");
+
+#if defined(WIN32)
+	{
+		wchar_t    *description = NULL;
+		int			result = 1;
+
+		if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &description)))
+		{
+			if (wcscmp(description, L"test_pg_threads worker with a long name") == 0)
+				result = 0;
+			LocalFree(description);
+		}
+		return result;
+	}
+#elif defined(__linux__)
+	{
+		char		name[16];
+
+		/* Linux keeps the first 15 bytes. */
+		if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0)
+			return 1;
+		return strcmp(name, "test_pg_threads") == 0 ? 0 : 1;
+	}
+#else
+	return 0;
+#endif
+}
+
+static void
+test_setname(void)
+{
+	pg_thrd_t	thread;
+	int			result = -1;
+
+	CHECK(pg_thrd_create(&thread, setname_thread, NULL) == pg_thrd_success);
+	CHECK(pg_thrd_join(thread, &result) == pg_thrd_success);
+	CHECK(result == 0);
+}
+
 /* Errors must come back with a usable description. */
 static void
 test_error_strings(void)
@@ -549,6 +596,7 @@ main(int argc, char **argv)
 	test_tss();
 	test_barrier();
 	test_rwlock();
+	test_setname();
 	test_error_strings();
 
 	if (failures > 0)
-- 
2.54.0

