prevent-segfaults-under-memory-pressure.patch
text/x-patch
Filename: prevent-segfaults-under-memory-pressure.patch
Type: text/x-patch
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/postmaster/postmaster.c | 1 | 0 |
| src/backend/tcop/postgres.c | 100 | 0 |
| src/backend/utils/init/miscinit.c | 3 | 0 |
| src/backend/utils/misc/guc_parameters.dat | 6 | 0 |
| src/backend/utils/misc/postgresql.conf.sample | 1 | 0 |
| src/include/miscadmin.h | 1 | 0 |
| src/include/postmaster/postmaster.h | 1 | 0 |
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 90c7c4528e8..46517c6b092 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -256,6 +256,7 @@ bool remove_temp_files_after_crash = true;
*/
bool send_abort_for_crash = false;
bool send_abort_for_kill = false;
+bool preallocate_stack = false;
/* special child processes; NULL when not running */
static PMChild *StartupPMChild = NULL,
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index dbef734a93f..21775456c35 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3619,6 +3619,106 @@ ProcessInterrupts(void)
ProcessRepackMessages();
}
+#ifndef WIN32
+/*
+ * occupy_stack - recursively touch stack pages to force physical allocation
+ *
+ * Each frame allocates 256kB (half of STACK_DEPTH_SLOP). Recursion stops
+ * when stack_is_too_deep() fires.
+ *
+ * Returns the number of frames successfully allocated.
+ */
+static pg_noinline int
+occupy_stack(void)
+{
+ /* Occupy half of STACK_DEPTH_SLOP at once */
+ volatile char stack_data[256 * 1024];
+ volatile char *p = stack_data;
+
+ /* Touch each page to force kernel to back it with physical memory */
+ for (long i = 0; i < sizeof(stack_data); i += 4096)
+ p[i] = 0;
+
+ if (!stack_is_too_deep())
+ return occupy_stack() + 1;
+ return 0;
+}
+
+static sigjmp_buf alt_sigsegv_jump;
+
+/* SIGSEGV handler for allocate_stack() */
+#if defined(USE_SIGACTION) && defined(USE_SIGINFO)
+static void
+alt_sigsegv_handler(int postgres_signal_arg, siginfo_t *info, void *context)
+#else /* no USE_SIGINFO */
+static void
+alt_sigsegv_handler(int postgres_signal_arg)
+#endif
+{
+ siglongjmp(alt_sigsegv_jump, 1);
+}
+#endif
+
+/*
+ * allocate_stack - acquire stack pages to prevent SIGSEGV under memory pressure
+ *
+ * When the system is under memory pressure (e.g., cgroup limits), the kernel
+ * may fail to extend the stack on demand, killing the backend with SIGSEGV
+ * before check_stack_depth() can intervene. This function proactively acquires
+ * physical pages of stack up to max_stack_depth via occupy_stack(), while it
+ * can still handle failures gracefully, using a temporary SIGSEGV handler on
+ * an alternate signal stack.
+ *
+ * Returns the number of frames successfully allocated (0 on Windows).
+ */
+int
+allocate_stack(void)
+{
+ int result = 0;
+
+#ifndef WIN32
+ struct sigaction act, oldact;
+ stack_t ss, old_ss;
+
+ ss.ss_sp = palloc(SIGSTKSZ);
+ ss.ss_size = SIGSTKSZ;
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, &old_ss) == -1)
+ elog(FATAL, "sigaltstack failed: %m");
+
+ act.sa_flags = SA_ONSTACK;
+ act.sa_handler = alt_sigsegv_handler;
+ sigemptyset(&act.sa_mask);
+ if (sigaction(SIGSEGV, &act, &oldact) == -1)
+ elog(FATAL, "sigaction failed: %m");
+
+ /*
+ * Use sigsetjmp to intercept SIGSEGV that can happen even at the process
+ * start, and in this case occupy_stack() can't handle it.
+ *
+ * Terminating the process with FATAL is the only way to avoid
+ * segfaults later.
+ */
+ if (sigsetjmp(alt_sigsegv_jump, 1) == 0)
+ {
+ result = occupy_stack();
+ }
+ else
+ {
+ /* We got the SIGSEGV trap */
+ elog(FATAL, "could not allocate stack");
+ }
+
+ if (sigaltstack(&old_ss, NULL) == -1)
+ elog(FATAL, "sigaltstack restore failed: %m");
+ if (sigaction(SIGSEGV, &oldact, NULL) == -1)
+ elog(FATAL, "sigaction failed: %m");
+ pfree(ss.ss_sp);
+#endif
+
+ return result;
+}
+
/*
* GUC check_hook for client_connection_check_interval
*/
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 7ffc808073a..a0dd7c6f901 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -107,6 +107,9 @@ InitPostmasterChild(void)
pgwin32_signal_initialize();
#endif
+ if (preallocate_stack)
+ (void) allocate_stack();
+
InitProcessGlobals();
/*
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index afaa058b046..b2d5642f572 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -2390,6 +2390,12 @@
max => '60',
},
+{ name => 'preallocate_stack', type => 'bool', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM',
+ short_desc => 'Preallocate stack on subprocess start.',
+ variable => 'preallocate_stack',
+ boot_val => 'false',
+},
+
{ name => 'primary_conninfo', type => 'string', context => 'PGC_SIGHUP', group => 'REPLICATION_STANDBY',
short_desc => 'Sets the connection string to be used to connect to the sending server.',
flags => 'GUC_SUPERUSER_ONLY',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ac38cddaaf9..0c112f51684 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -150,6 +150,7 @@
#autovacuum_work_mem = -1 # min 64kB, or -1 to use maintenance_work_mem
#logical_decoding_work_mem = 64MB # min 64kB
#max_stack_depth = 2MB # min 100kB
+#preallocate_stack = off # allocate max_stack_depth on subprocess start
#shared_memory_type = mmap # the default is the first option
# supported by the operating system:
# mmap
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 8ccdf61246b..b0efa042ec5 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -306,6 +306,7 @@ extern void restore_stack_base(pg_stack_base_t base);
extern void check_stack_depth(void);
extern bool stack_is_too_deep(void);
extern ssize_t get_stack_depth_rlimit(void);
+extern int allocate_stack(void);
/* in tcop/utility.c */
extern void PreventCommandIfReadOnly(const char *cmdname);
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 716b4c912b3..7898c021b12 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -70,6 +70,7 @@ extern PGDLLIMPORT bool restart_after_crash;
extern PGDLLIMPORT bool remove_temp_files_after_crash;
extern PGDLLIMPORT bool send_abort_for_crash;
extern PGDLLIMPORT bool send_abort_for_kill;
+extern PGDLLIMPORT bool preallocate_stack;
#ifdef WIN32
extern PGDLLIMPORT HANDLE PostmasterHandle;