From bf42e2cf8d8c516aa20306a1137eba4d1b4a12a6 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Fri, 2 Oct 2026 09:06:30 +0000
Subject: [PATCH v4 1/3] Allow selected PANIC errors to skip core dumps

PANIC always calls abort(), which is useful for server bugs but can
worsen disk pressure for operational failures such as ENOSPC.

Add errabort(bool) to control the current report's core-dump preference,
leaving the default unchanged.  Keep errnocoredump_on_errno() as a
saved-errno convenience wrapper that suppresses only on a match.  An
explicit errabort(true) can restore abort() after suppression.

Marked PANICs use _exit(2) without cleanup callbacks, preserving existing
postmaster crash handling.  A core-worthy outer PANIC still takes
precedence over nested suppression.

Add a developer override and document the reporting helpers.

Discussion: https://postgr.es/m/20231118222911.trznxgya4hvzapva@awork3.anarazel.de
---
 doc/src/sgml/config.sgml                  | 23 ++++++++++
 doc/src/sgml/sources.sgml                 | 25 +++++++++++
 src/backend/utils/error/elog.c            | 54 +++++++++++++++++++++--
 src/backend/utils/misc/guc_parameters.dat |  7 +++
 src/include/utils/elog.h                  |  4 ++
 5 files changed, 110 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f36fbb60101..e57cc0fef61 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -13547,6 +13547,29 @@ LOG:  CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1)
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-force-core-dump-on-panic" xreflabel="force_core_dump_on_panic">
+       <term><varname>force_core_dump_on_panic</varname> (<type>boolean</type>)
+       <indexterm>
+        <primary><varname>force_core_dump_on_panic</varname> configuration parameter</primary>
+       </indexterm>
+       </term>
+       <listitem>
+        <para>
+         Some <literal>PANIC</literal> errors are marked to terminate without
+         requesting a core dump.  When this option is set to
+         <literal>on</literal>, those errors call
+         <function>abort()</function> instead, which normally produces a core
+         dump file.  This can be useful for debugging, but repeated failures
+         can consume significant disk space.  Unlike
+         <xref linkend="guc-send-abort-for-crash"/>, this parameter controls
+         the process reporting the <literal>PANIC</literal>, not the other
+         processes terminated by the postmaster afterwards.  This parameter
+         can only be set in the <filename>postgresql.conf</filename> file or on
+         the server command line.
+        </para>
+       </listitem>
+      </varlistentry>
+
      <varlistentry id="guc-debug-logical-replication-streaming" xreflabel="debug_logical_replication_streaming">
       <term><varname>debug_logical_replication_streaming</varname> (<type>enum</type>)
       <indexterm>
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..3026f10ff8d 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -392,6 +392,31 @@ ereport(ERROR,
      too much.
     </para>
    </listitem>
+     <listitem>
+        <para>
+         <function>errabort(bool do_abort)</function> controls whether a
+         <literal>PANIC</literal> calls <function>abort()</function>, the default.
+         Passing <literal>false</literal> selects <function>_exit(2)</function>
+         instead, avoiding a core dump without running cleanup callbacks or
+         changing postmaster crash handling.  Calling
+         <function>errabort(true)</function> restores the default for the current
+         report.  This preference also applies to errors promoted to
+         <literal>PANIC</literal>, but does not change ordinary
+         <literal>ERROR</literal> or <literal>FATAL</literal> termination.
+         An outer <literal>PANIC</literal> that requests a core dump, or
+         <xref linkend="guc-force-core-dump-on-panic"/>, still overrides suppression.
+        </para>
+     </listitem>
+     <listitem>
+        <para>
+         <function>errnocoredump_on_errno(int errnum)</function> is a convenience
+         wrapper that calls <function>errabort(false)</function> when the
+         <literal>errno</literal> saved at the start of the error report matches
+         <parameter>errnum</parameter>.  A nonmatch leaves the preference unchanged,
+         so multiple calls can select more than one error number without undoing
+         an earlier match.
+        </para>
+     </listitem>
   </itemizedlist>
    </para>
 
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index b9d2c96b97a..4d588823719 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -117,6 +117,7 @@ int			Log_destination = LOG_DESTINATION_STDERR;
 char	   *Log_destination_string = NULL;
 bool		syslog_sequence_numbers = true;
 bool		syslog_split_messages = true;
+bool		force_core_dump_on_panic = false;
 
 /* Processed form of backtrace_functions GUC */
 static char *backtrace_function_list;
@@ -487,6 +488,7 @@ errfinish(const char *filename, int lineno, const char *funcname)
 {
 	ErrorData  *edata = &errordata[errordata_stack_depth];
 	int			elevel;
+	bool		no_core_dump;
 	MemoryContext oldcontext;
 	ErrorContextCallback *econtext;
 
@@ -521,6 +523,17 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 econtext = econtext->previous)
 		econtext->callback(econtext->arg);
 
+	/*
+	 * Capture this before releasing the current error stack entry below.  A
+	 * core-worthy outer PANIC must not be downgraded by a nested error.
+	 */
+	no_core_dump = edata->no_core_dump;
+	for (int i = 0; no_core_dump && i < errordata_stack_depth; i++)
+	{
+		if (errordata[i].elevel >= PANIC && !errordata[i].no_core_dump)
+			no_core_dump = false;
+	}
+
 	/*
 	 * If ERROR (not more nor less) we pass it off to the current handler.
 	 * Printing it and popping the stack is the responsibility of the handler.
@@ -611,13 +624,16 @@ errfinish(const char *filename, int lineno, const char *funcname)
 	if (elevel >= PANIC)
 	{
 		/*
-		 * Serious crash time. Postmaster will observe SIGABRT process exit
-		 * status and kill the other backends too.
+		 * Serious crash time.  SIGABRT and exit code 2 both trigger
+		 * postmaster crash handling.  Marked errors use _exit(2) to avoid
+		 * core dumps without running cleanup callbacks.
 		 *
-		 * XXX: what if we are *in* the postmaster?  abort() won't kill our
+		 * XXX: what if we are *in* the postmaster?  Neither path kills our
 		 * children...
 		 */
 		fflush(NULL);
+		if (no_core_dump && !force_core_dump_on_panic)
+			_exit(2);
 		abort();
 	}
 
@@ -1652,6 +1668,37 @@ errhidecontext(bool hide_ctx)
 	return 0;					/* return value does not matter */
 }
 
+/*
+ * errabort --- set whether a PANIC should call abort()
+ */
+int
+errabort(bool do_abort)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+
+	CHECK_STACK_DEPTH();
+	edata->no_core_dump = !do_abort;
+
+	return 0;
+}
+
+/*
+ * errnocoredump_on_errno --- skip a core dump for the specified errno
+ */
+int
+errnocoredump_on_errno(int errnum)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+
+	/* we don't bother incrementing recursion_depth */
+	CHECK_STACK_DEPTH();
+
+	if (edata->saved_errno == errnum)
+		return errabort(false);
+
+	return 0;					/* return value does not matter */
+}
+
 /*
  * errposition --- add cursor position to the current error
  */
@@ -2130,6 +2177,7 @@ ThrowErrorData(ErrorData *edata)
 	newedata->internalpos = edata->internalpos;
 	if (edata->internalquery)
 		newedata->internalquery = pstrdup(edata->internalquery);
+	newedata->no_core_dump = edata->no_core_dump;
 
 	MemoryContextSwitchTo(oldcontext);
 	recursion_depth--;
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index c57441f7d98..88b88160265 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1104,6 +1104,13 @@
   options => 'file_extend_method_options',
 },
 
+{ name => 'force_core_dump_on_panic', type => 'bool', context => 'PGC_SIGHUP', group => 'DEVELOPER_OPTIONS',
+  short_desc => 'Force core dumps for PANIC errors marked to skip them.',
+  flags => 'GUC_NOT_IN_SAMPLE',
+  variable => 'force_core_dump_on_panic',
+  boot_val => 'false',
+},
+
 { name => 'from_collapse_limit', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER',
   short_desc => 'Sets the FROM-list size beyond which subqueries are not collapsed.',
   long_desc => 'The planner will merge subqueries into upper queries if the resulting FROM list would have no more than this many items.',
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 6ae376ba001..6446e5d250f 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -219,6 +219,8 @@ extern int	errcontext_msg(const char *fmt, ...) pg_attribute_printf(1, 2);
 
 extern int	errhidestmt(bool hide_stmt);
 extern int	errhidecontext(bool hide_ctx);
+extern int	errabort(bool do_abort);
+extern int	errnocoredump_on_errno(int errnum);
 
 extern int	errbacktrace(void);
 
@@ -422,6 +424,7 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 	pg_re_throw()
 
 extern PGDLLIMPORT sigjmp_buf *PG_exception_stack;
+extern PGDLLIMPORT bool force_core_dump_on_panic;
 
 
 /* Stuff that error handlers might want to use */
@@ -439,6 +442,7 @@ typedef struct ErrorData
 	bool		output_to_client;	/* will report to client? */
 	bool		hide_stmt;		/* true to prevent STATEMENT: inclusion */
 	bool		hide_ctx;		/* true to prevent CONTEXT: inclusion */
+	bool		no_core_dump;	/* true to skip core dump for PANIC */
 	const char *filename;		/* __FILE__ of ereport() call */
 	int			lineno;			/* __LINE__ of ereport() call */
 	const char *funcname;		/* __func__ of ereport() call */
-- 
2.43.0

