From 032ecd53ebc31d567ff28643a3083fe5405e38fd Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats@timescale.com>
Date: Tue, 24 May 2022 11:02:38 +0200
Subject: Add signal-safe function to write to log

Any functions that use `malloc` directly or indirectly are not
signal-safe, and `vfprintf` is not signal-safe but used from inside
`bgworker_die`. This can cause the system to end up in a deadlock and
not respond to signals.

This commit adds a signal-safe version of write_stderr() and uses it
from signal handlers rather than write_stderr().
---
 src/backend/postmaster/bgworker.c | 16 ++++++++++++----
 src/backend/utils/error/elog.c    | 21 ++++++++++++++++-----
 src/include/utils/elog.h          |  6 ++++++
 3 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 30682b63b3..3ad44d303b 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -719,16 +719,24 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel)
 
 /*
  * Standard SIGTERM handler for background workers
+ *
+ * Only use signal-safe function in signal handler, in particular, ereport()
+ * and and any printf()-like functions since they directly or indirectly use
+ * malloc().
  */
 static void
 bgworker_die(SIGNAL_ARGS)
 {
 	PG_SETMASK(&BlockSig);
+	char message[256] = {0};
+	char* ptr = message;
+
+	ptr = stpncpy(ptr, "terminating background worker \"", sizeof(message) - (message - ptr));
+	ptr = stpncpy(ptr, MyBgworkerEntry->bgw_type, sizeof(message) - (message - ptr));
+	ptr = stpncpy(ptr, "\" due to administrator command", sizeof(message) - (message - ptr));
 
-	ereport(FATAL,
-			(errcode(ERRCODE_ADMIN_SHUTDOWN),
-			 errmsg("terminating background worker \"%s\" due to administrator command",
-					MyBgworkerEntry->bgw_type)));
+	signal_safe_write_stderr(message, strlen(message));
+	die(postgres_signal_arg);
 }
 
 /*
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 55ee5423af..d8c94ba87f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -54,11 +54,11 @@
  */
 #include "postgres.h"
 
+#include <ctype.h>
 #include <fcntl.h>
+#include <signal.h>
 #include <time.h>
 #include <unistd.h>
-#include <signal.h>
-#include <ctype.h>
 #ifdef HAVE_SYSLOG
 #include <syslog.h>
 #endif
@@ -3384,23 +3384,34 @@ write_stderr(const char *fmt,...)
 	fflush(stderr);
 #else
 	vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
+	signal_safe_write_stderr(errbuf, strlen(errbuf));
+#endif
+	va_end(ap);
 
+}
+
+void
+signal_safe_write_stderr(const char *errbuf, size_t buflen)
+{
+#ifndef WIN32
+	/* If this fails, there is not much we can do, so ignore the error */
+	(void) write(fileno(stderr), errbuf, buflen);
+#else
 	/*
 	 * On Win32, we print to stderr if running on a console, or write to
 	 * eventlog if running as a service
 	 */
 	if (pgwin32_is_service())	/* Running as a service */
 	{
-		write_eventlog(ERROR, errbuf, strlen(errbuf));
+		write_eventlog(ERROR, errbuf, buflen);
 	}
 	else
 	{
 		/* Not running as service, write to stderr */
-		write_console(errbuf, strlen(errbuf));
+		write_console(errbuf, buflen);
 		fflush(stderr);
 	}
 #endif
-	va_end(ap);
 }
 
 
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index f5c6cd904d..d6ed4e9c03 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -467,4 +467,10 @@ extern void set_syslog_parameters(const char *ident, int facility);
  */
 extern void write_stderr(const char *fmt,...) pg_attribute_printf(1, 2);
 
+/*
+ * Signal-safe function to write error messages. Can be used from inside
+ * signal-handlers.
+ */
+extern void signal_safe_write_stderr(const char* errbuf, size_t buflen);
+
 #endif							/* ELOG_H */
-- 
2.25.1

