diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2024-12-17 12:08:39 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2024-12-17 12:08:42 -0500 |
commit | c91963da1302e8dd490bde115f3956f7d2f1258d (patch) | |
tree | bd1f6e3c4319e2efd4382ceb59e9e44486033470 /src/backend/utils/misc/stack_depth.c | |
parent | 957ba9ff14066782a42ebb974913b2fc616c99e1 (diff) | |
download | postgresql-c91963da1302e8dd490bde115f3956f7d2f1258d.tar.gz postgresql-c91963da1302e8dd490bde115f3956f7d2f1258d.zip |
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/misc/stack_depth.c')
-rw-r--r-- | src/backend/utils/misc/stack_depth.c | 197 |
1 files changed, 197 insertions, 0 deletions
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 +} |