Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Rewrite the pthread_getattr_np usage to avoid the problem on musl of it
reporting the stack limit of the main thread incorrectly. On platforms where
pthread_getattr_np underreports the main thread's stack size, use
RLIMIT_STACK for the main thread instead.
  • Loading branch information
Yhg1s committed Oct 13, 2026
commit 723c7223b455e45eea12627e15152647420c129f
6 changes: 2 additions & 4 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -1107,13 +1107,11 @@ def next(self):
enum._test_simple_enum(_Precedence, _ast_unparse._Precedence)

@support.cpython_only
@support.run_with_limited_c_stack(
100_000 if sys.platform == "android" else 500_000)
@support.run_with_limited_c_stack(500_000)
@skip_wasi_stack_overflow()
@skip_emscripten_stack_overflow()
def test_ast_recursion_limit(self):
# Android test devices have less memory.
crash_depth = 100_000 if sys.platform == "android" else 500_000
crash_depth = 500_000
success_depth = 200
if _testinternalcapi is not None:
remaining = _testinternalcapi.get_c_recursion_remaining()
Expand Down
9 changes: 3 additions & 6 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,14 +773,12 @@ def test_yet_more_evil_still_undecodable(self):

@support.cpython_only
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
@support.run_with_limited_c_stack(
100_000 if sys.platform == "android" else 500_000)
@support.run_with_limited_c_stack(500_000)
@support.skip_emscripten_stack_overflow()
def test_compiler_recursion_limit(self):
# Compiler frames are small
limit = 100
# Android test devices have less memory.
crash_depth = limit * (1000 if sys.platform == "android" else 5000)
crash_depth = limit * 5000
success_depth = limit

def check_limit(prefix, repeated, mode="single"):
Expand Down Expand Up @@ -1092,8 +1090,7 @@ def test_path_like_objects(self):
# complex statements.
@support.requires_resource('cpu')
def test_stack_overflow(self):
# Android test devices have less memory.
size = 100_000 if sys.platform == "android" else 200_000
size = 200_000
compile("if a: b\n" * size, "<dummy>", "exec")

# Multiple users rely on the fact that CPython does not generate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Enable stack size limit detection on musl and other non-glibc Linux
platforms (like Android), instead of relying on a fixed stack size limit.
34 changes: 29 additions & 5 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count)
# define Py_C_STACK_SIZE 4000000
#endif

#ifdef HAVE_UNRELIABLE_PTHREAD_GETATTR_NP
#ifdef HAVE_SYS_RESOURCE_H
# include <sys/resource.h>
#endif
#ifdef HAVE_SYS_SYSCALL_H
# include <sys/syscall.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#endif

#if defined(__EMSCRIPTEN__)

// Temporary workaround to make `pthread_getattr_np` work on Emscripten.
Expand Down Expand Up @@ -142,11 +154,7 @@ hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp)
*top = (uintptr_t)stack_addr;
*base = ((uintptr_t)stack_addr) - stack_size;
#else
/// XXX musl supports HAVE_PTHRED_GETATTR_NP, but the resulting stack size
/// (on alpine at least) is much smaller than expected and imposes undue limits
/// compared to the old stack size estimation. (We assume musl is not glibc.)
# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && \
!defined(__NetBSD__) && (defined(__GLIBC__) || !defined(__linux__))
# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && !defined(__NetBSD__)
size_t stack_size, guard_size;
void *stack_addr;
pthread_attr_t attr;
Expand All @@ -157,6 +165,22 @@ hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp)
err |= pthread_attr_destroy(&attr);
}
if (err == 0) {
# ifdef HAVE_UNRELIABLE_PTHREAD_GETATTR_NP
/* pthread_getattr_np on the main thread of a musl process reports a
* stack size based on the current stack rather than RLIMIT_STACK
* New threads pass their size to pthread_attr_setstacksize and are
* reported correctly even on musl, so this fallback only applies to
Comment thread
Yhg1s marked this conversation as resolved.
Outdated
* the main thread. */
if ((pid_t)syscall(SYS_gettid) == getpid()) {
struct rlimit rl;
if (getrlimit(RLIMIT_STACK, &rl) == 0 &&
rl.rlim_cur != RLIM_INFINITY &&
stack_size < rl.rlim_cur)
{
stack_size = rl.rlim_cur;
}
}
# endif
*base = ((uintptr_t)stack_addr) + guard_size;
*top = (uintptr_t)stack_addr + stack_size;
return;
Expand Down
64 changes: 64 additions & 0 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -5586,6 +5586,41 @@ AC_CHECK_FUNCS([ \

PY_CHECK_FUNC_PRIVATE([strerror_r], [@%:@include <string.h>])

# Detect musl's behaviour of reporting the current allocated stack size for
# the main thread, rather than the max size, in pthread_getattr_np.
AS_IF([test "$ac_cv_func_pthread_getattr_np" = yes],
[AC_CACHE_CHECK([whether pthread_getattr_np reports the main thread stack reliably],
[ac_cv_unreliable_pthread_getattr_np],
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
#define _GNU_SOURCE
#include <pthread.h>
#include <stdlib.h>
#include <sys/resource.h>

int main(void) {
pthread_attr_t attr;
void *stack_addr;
size_t stack_size, guard_size;
struct rlimit rl;

if (pthread_getattr_np(pthread_self(), &attr) != 0) return 1;
if (pthread_attr_getstack(&attr, &stack_addr, &stack_size) != 0 ||
pthread_attr_getguardsize(&attr, &guard_size) != 0) {
pthread_attr_destroy(&attr);
return 1;
}
pthread_attr_destroy(&attr);
if (getrlimit(RLIMIT_STACK, &rl) != 0) return 1;
if (rl.rlim_cur == RLIM_INFINITY) return 0;
return (stack_size >= rl.rlim_cur / 2) ? 0 : 1;
}
]])],
[ac_cv_unreliable_pthread_getattr_np=no],
[ac_cv_unreliable_pthread_getattr_np=yes],
[ac_cv_unreliable_pthread_getattr_np=no])])
AS_VAR_IF([ac_cv_unreliable_pthread_getattr_np], [yes],
[AC_DEFINE([HAVE_UNRELIABLE_PTHREAD_GETATTR_NP], [1],
[Define if pthread_getattr_np under-reports the main thread's stack size (musl behaviour).])])])

# os.statx uses Linux's statx function. AIX also has a function named statx,
# but it's unrelated. Check only on Linux (including Android).
Expand Down
4 changes: 4 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,10 @@
/* Define to 1 if you have the 'unlockpt' function. */
#undef HAVE_UNLOCKPT

/* Define if pthread_getattr_np under-reports the main thread's stack size
(musl behaviour). */
#undef HAVE_UNRELIABLE_PTHREAD_GETATTR_NP

/* Define to 1 if you have the 'unshare' function. */
#undef HAVE_UNSHARE

Expand Down
Loading