summaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorTom Lane2024-12-17 17:08:39 +0000
committerTom Lane2024-12-17 17:08:42 +0000
commitc91963da1302e8dd490bde115f3956f7d2f1258d (patch)
treebd1f6e3c4319e2efd4382ceb59e9e44486033470 /src/backend/utils
parent957ba9ff14066782a42ebb974913b2fc616c99e1 (diff)
Set the stack_base_ptr in main(), not in random other places.
Previously we did this in PostmasterMain() and InitPostmasterChild(), which meant that stack depth checking was disabled in non-postmaster server processes, for instance in single-user mode. That seems like a fairly bad idea, since there's no a-priori restriction on the complexity of queries we will run in single-user mode. Moreover, this led to not having quite the same stack depth limit in all processes, which likely has no real-world effect but it offends my inner neatnik. Setting the depth in main() guarantees that check_stack_depth() is armed and has a consistent interpretation of stack depth in all forms of server processes. While at it, move the code associated with checking the stack depth out of tcop/postgres.c (which was never a great home for it) into a new file src/backend/utils/misc/stack_depth.c. Discussion: https://postgr.es/m/2081982.1734393311@sss.pgh.pa.us
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/init/miscinit.c7
-rw-r--r--src/backend/utils/misc/Makefile1
-rw-r--r--src/backend/utils/misc/meson.build1
-rw-r--r--src/backend/utils/misc/stack_depth.c197
4 files changed, 199 insertions, 7 deletions
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 3b7b2ebec08..d24ac133fb7 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -106,13 +106,6 @@ InitPostmasterChild(void)
pgwin32_signal_initialize();
#endif
- /*
- * Set reference point for stack-depth checking. This might seem
- * redundant in !EXEC_BACKEND builds, but it's better to keep the depth
- * logic the same with and without that build option.
- */
- (void) set_stack_base();
-
InitProcessGlobals();
/*
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index d9f59785b98..b362ae43771 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -29,6 +29,7 @@ OBJS = \
queryenvironment.o \
rls.o \
sampling.o \
+ stack_depth.o \
superuser.o \
timeout.o \
tzparser.o
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 66695022052..51dea174d2b 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -14,6 +14,7 @@ backend_sources += files(
'queryenvironment.c',
'rls.c',
'sampling.c',
+ 'stack_depth.c',
'superuser.c',
'timeout.c',
'tzparser.c',
diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
new file mode 100644
index 00000000000..6bc02ea1d4c
--- /dev/null
+++ b/src/backend/utils/misc/stack_depth.c
@@ -0,0 +1,197 @@
+/*-------------------------------------------------------------------------
+ *
+ * stack_depth.c
+ * Functions for monitoring and limiting process stack depth
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/stack_depth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <limits.h>
+#include <sys/resource.h>
+
+#include "miscadmin.h"
+#include "utils/guc_hooks.h"
+
+
+/* GUC variable for maximum stack depth (measured in kilobytes) */
+int max_stack_depth = 100;
+
+/* max_stack_depth converted to bytes for speed of checking */
+static long max_stack_depth_bytes = 100 * 1024L;
+
+/*
+ * Stack base pointer -- initialized by set_stack_base(), which
+ * should be called from main().
+ */
+static char *stack_base_ptr = NULL;
+
+
+/*
+ * set_stack_base: set up reference point for stack depth checking
+ *
+ * Returns the old reference point, if any.
+ */
+pg_stack_base_t
+set_stack_base(void)
+{
+#ifndef HAVE__BUILTIN_FRAME_ADDRESS
+ char stack_base;
+#endif
+ pg_stack_base_t old;
+
+ old = stack_base_ptr;
+
+ /*
+ * Set up reference point for stack depth checking. On recent gcc we use
+ * __builtin_frame_address() to avoid a warning about storing a local
+ * variable's address in a long-lived variable.
+ */
+#ifdef HAVE__BUILTIN_FRAME_ADDRESS
+ stack_base_ptr = __builtin_frame_address(0);
+#else
+ stack_base_ptr = &stack_base;
+#endif
+
+ return old;
+}
+
+/*
+ * restore_stack_base: restore reference point for stack depth checking
+ *
+ * This can be used after set_stack_base() to restore the old value. This
+ * is currently only used in PL/Java. When PL/Java calls a backend function
+ * from different thread, the thread's stack is at a different location than
+ * the main thread's stack, so it sets the base pointer before the call, and
+ * restores it afterwards.
+ */
+void
+restore_stack_base(pg_stack_base_t base)
+{
+ stack_base_ptr = base;
+}
+
+
+/*
+ * check_stack_depth/stack_is_too_deep: check for excessively deep recursion
+ *
+ * This should be called someplace in any recursive routine that might possibly
+ * recurse deep enough to overflow the stack. Most Unixen treat stack
+ * overflow as an unrecoverable SIGSEGV, so we want to error out ourselves
+ * before hitting the hardware limit.
+ *
+ * check_stack_depth() just throws an error summarily. stack_is_too_deep()
+ * can be used by code that wants to handle the error condition itself.
+ */
+void
+check_stack_depth(void)
+{
+ if (stack_is_too_deep())
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
+ errmsg("stack depth limit exceeded"),
+ errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), "
+ "after ensuring the platform's stack depth limit is adequate.",
+ max_stack_depth)));
+ }
+}
+
+bool
+stack_is_too_deep(void)
+{
+ char stack_top_loc;
+ long stack_depth;
+
+ /*
+ * Compute distance from reference point to my local variables
+ */
+ stack_depth = (long) (stack_base_ptr - &stack_top_loc);
+
+ /*
+ * Take abs value, since stacks grow up on some machines, down on others
+ */
+ if (stack_depth < 0)
+ stack_depth = -stack_depth;
+
+ /*
+ * Trouble?
+ *
+ * The test on stack_base_ptr prevents us from erroring out if called
+ * before that's been set. Logically it should be done first, but putting
+ * it last avoids wasting cycles during normal cases.
+ */
+ if (stack_depth > max_stack_depth_bytes &&
+ stack_base_ptr != NULL)
+ return true;
+
+ return false;
+}
+
+
+/* GUC check hook for max_stack_depth */
+bool
+check_max_stack_depth(int *newval, void **extra, GucSource source)
+{
+ long newval_bytes = *newval * 1024L;
+ long stack_rlimit = get_stack_depth_rlimit();
+
+ if (stack_rlimit > 0 && newval_bytes > stack_rlimit - STACK_DEPTH_SLOP)
+ {
+ GUC_check_errdetail("\"max_stack_depth\" must not exceed %ldkB.",
+ (stack_rlimit - STACK_DEPTH_SLOP) / 1024L);
+ GUC_check_errhint("Increase the platform's stack depth limit via \"ulimit -s\" or local equivalent.");
+ return false;
+ }
+ return true;
+}
+
+/* GUC assign hook for max_stack_depth */
+void
+assign_max_stack_depth(int newval, void *extra)
+{
+ long newval_bytes = newval * 1024L;
+
+ max_stack_depth_bytes = newval_bytes;
+}
+
+/*
+ * Obtain platform stack depth limit (in bytes)
+ *
+ * Return -1 if unknown
+ */
+long
+get_stack_depth_rlimit(void)
+{
+#if defined(HAVE_GETRLIMIT)
+ static long val = 0;
+
+ /* This won't change after process launch, so check just once */
+ if (val == 0)
+ {
+ struct rlimit rlim;
+
+ if (getrlimit(RLIMIT_STACK, &rlim) < 0)
+ val = -1;
+ else if (rlim.rlim_cur == RLIM_INFINITY)
+ val = LONG_MAX;
+ /* rlim_cur is probably of an unsigned type, so check for overflow */
+ else if (rlim.rlim_cur >= LONG_MAX)
+ val = LONG_MAX;
+ else
+ val = rlim.rlim_cur;
+ }
+ return val;
+#else
+ /* On Windows we set the backend stack size in src/backend/Makefile */
+ return WIN32_STACK_RLIMIT;
+#endif
+}