From 723c7223b455e45eea12627e15152647420c129f Mon Sep 17 00:00:00 2001 From: Thomas Wouters Date: Tue, 13 Oct 2026 23:11:30 +0200 Subject: [PATCH 1/3] 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. --- Lib/test/test_ast/test_ast.py | 6 +- Lib/test/test_compile.py | 9 +-- ...-10-13-23-08-01.gh-issue-159320._Yhg1s.rst | 2 + Python/ceval.c | 34 ++++++++-- configure | 64 +++++++++++++++++++ configure.ac | 35 ++++++++++ pyconfig.h.in | 4 ++ 7 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-13-23-08-01.gh-issue-159320._Yhg1s.rst diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index bb282eba5d41eec..b1483ad6bf2d401 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -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() diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 53b4be1e75b65a1..60886b1a55c16f4 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -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"): @@ -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, "", "exec") # Multiple users rely on the fact that CPython does not generate diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-13-23-08-01.gh-issue-159320._Yhg1s.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-13-23-08-01.gh-issue-159320._Yhg1s.rst new file mode 100644 index 000000000000000..e9a4609a8a4f2d1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-13-23-08-01.gh-issue-159320._Yhg1s.rst @@ -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. diff --git a/Python/ceval.c b/Python/ceval.c index acf012c44a53050..f17f7ac6e810a99 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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 +#endif +#ifdef HAVE_SYS_SYSCALL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#endif + #if defined(__EMSCRIPTEN__) // Temporary workaround to make `pthread_getattr_np` work on Emscripten. @@ -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; @@ -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 + * 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; diff --git a/configure b/configure index c777d8117c66c4c..ca4ad39c486ea93 100755 --- a/configure +++ b/configure @@ -21773,6 +21773,70 @@ fi +# Detect musl's behaviour of reporting the current allocated stack size for +# the main thread, rather than the max size, in pthread_getattr_np. +if test "$ac_cv_func_pthread_getattr_np" = yes +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthread_getattr_np reports the main thread stack reliably" >&5 +printf %s "checking whether pthread_getattr_np reports the main thread stack reliably... " >&6; } +if test ${ac_cv_unreliable_pthread_getattr_np+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test "$cross_compiling" = yes +then : + ac_cv_unreliable_pthread_getattr_np=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#define _GNU_SOURCE +#include +#include +#include + +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; +} + +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + ac_cv_unreliable_pthread_getattr_np=no +else case e in #( + e) ac_cv_unreliable_pthread_getattr_np=yes ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_unreliable_pthread_getattr_np" >&5 +printf "%s\n" "$ac_cv_unreliable_pthread_getattr_np" >&6; } + if test "x$ac_cv_unreliable_pthread_getattr_np" = xyes +then : + +printf "%s\n" "#define HAVE_UNRELIABLE_PTHREAD_GETATTR_NP 1" >>confdefs.h + +fi +fi # os.statx uses Linux's statx function. AIX also has a function named statx, # but it's unrelated. Check only on Linux (including Android). diff --git a/configure.ac b/configure.ac index a8760c94ac151aa..33bef05f86e4cbb 100644 --- a/configure.ac +++ b/configure.ac @@ -5586,6 +5586,41 @@ AC_CHECK_FUNCS([ \ PY_CHECK_FUNC_PRIVATE([strerror_r], [@%:@include ]) +# 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 +#include +#include + +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). diff --git a/pyconfig.h.in b/pyconfig.h.in index 08306a1dc8ba135..f01398d60f770f8 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -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 From 9569c070a169581ea3abcc9e20c5a640a03fbbeb Mon Sep 17 00:00:00 2001 From: Thomas Wouters Date: Tue, 13 Oct 2026 23:37:37 +0200 Subject: [PATCH 2/3] Add HAVE_UNRELIABLE_PTHREAD_GETATTR_NP to the long, long list of ignored symbols for the Py check. --- Tools/build/check_capi_macros_ignored.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/build/check_capi_macros_ignored.txt b/Tools/build/check_capi_macros_ignored.txt index 877c8d72e72827c..ef477976d6a02e3 100644 --- a/Tools/build/check_capi_macros_ignored.txt +++ b/Tools/build/check_capi_macros_ignored.txt @@ -617,6 +617,7 @@ HAVE_UNAME HAVE_UNISTD_H HAVE_UNLINKAT HAVE_UNLOCKPT +HAVE_UNRELIABLE_PTHREAD_GETATTR_NP HAVE_UNSHARE HAVE_USABLE_WCHAR_T HAVE_UTIL_H From 036f753895eba6fc2bb276c06f11d02289ec58b1 Mon Sep 17 00:00:00 2001 From: Thomas Wouters Date: Wed, 14 Oct 2026 14:13:33 +0200 Subject: [PATCH 3/3] Fix punctuation/formatting. --- Python/ceval.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index f17f7ac6e810a99..3c2fd6760820c88 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -167,10 +167,10 @@ hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp) 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 + * 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 - * the main thread. */ + * the main thread. */ if ((pid_t)syscall(SYS_gettid) == getpid()) { struct rlimit rl; if (getrlimit(RLIMIT_STACK, &rl) == 0 &&