Repository navigation
C stack limit detection is disabled on musl, so deep C recursion crashes instead of raising RecursionError #159876
Description
Bug report
Bug description:
On Alpine Linux (musl libc), test_lazy_import.LazyImportTypeTests.test_deeply_dotted_name terminates the
interpreter with SIGSEGV instead of raising RecursionError:
$ ./python -m unittest test.test_lazy_import.LazyImportTypeTests.test_deeply_dotted_name
test_deeply_dotted_name (...)
A long dotted name must not overflow the C stack. ... Segmentation fault
The crash is a C stack overflow in the prologue of a recursive call:
Thread 2 "Thread-1 (run)" received signal SIGSEGV, Segmentation fault.
0x00005555556bc2dc in lazy_import_replay_from (...) at ../Objects/lazyimportobject.c:280
#1 0x00005555556bc331 in lazy_import_replay_from (...) at ../Objects/lazyimportobject.c:287
#2 0x00005555556bc331 in lazy_import_replay_from (...) at ../Objects/lazyimportobject.c:287
... (repeats)
lazy_import_replay_from() does guard itself with _Py_EnterRecursiveCallTstate(), so the guard is simply
not firing.
Cause
The test runs its body in a thread with a 256 KiB stack
(@support.run_with_limited_c_stack(depth=10_000, size=256 * 1024)), but CPython never learns that.
hardware_stack_limits() in Python/ceval.c only queries the real stack bounds when:
# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && \
!defined(__NetBSD__) && \
(defined(__GLIBC__) || defined(__ANDROID__) || !defined(__linux__))
(Python/ceval.c:150-152 on main, :162-164 on 3.15, :462-464 on 3.14.)
On musl, __linux__ is defined while __GLIBC__ and __ANDROID__ are not, so the probe is skipped and
CPython falls back to guessing base = sp - Py_C_STACK_SIZE. With the -Wl,-z,stack-size= that configure
adds, Py_C_STACK_SIZE is _Py_LINKER_THREAD_STACK_SIZE = 1048576, so the interpreter assumes 1 MiB on
every thread regardless of the thread's real size.
Measured with _testinternalcapi.get_c_recursion_remaining():
import threading, _testinternalcapi as T
for size in (256 * 1024, 8 * 1024 * 1024):
threading.stack_size(size)
def show(size=size):
print(" thread stack %7d B -> c_recursion_remaining=%d"
% (size, T.get_c_recursion_remaining()))
t = threading.Thread(target=show); t.start(); t.join()Alpine 3.24 / musl: thread stack 262144 B -> c_recursion_remaining=1450
thread stack 8388608 B -> c_recursion_remaining=1450 # constant
Debian / glibc: thread stack 262144 B -> c_recursion_remaining=250
thread stack 8388608 B -> c_recursion_remaining=12650 # tracks reality
So on a 256 KiB thread the interpreter believes it has roughly 4x the stack it actually has, the soft limit
lands below the real stack base, and the guard can never trigger.
The exclusion looks broader than the problem it was added for
It came from GH-134336 ("Disable computed stack limit checks on non-glibc linux", gh-131338). But musl's
pthread_getattr_np() is accurate for secondary threads and only wrong for the main thread:
/* cc -o stackprobe stackprobe.c */
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
static void report(const char *tag) {
pthread_attr_t attr; void *addr = NULL; size_t size = 0, guard = 0;
if (pthread_getattr_np(pthread_self(), &attr)) { printf("%s: failed\n", tag); return; }
pthread_attr_getstack(&attr, &addr, &size);
pthread_attr_getguardsize(&attr, &guard);
pthread_attr_destroy(&attr);
printf("%-14s base=%p size=%zu (%.2f MiB) guard=%zu\n", tag, addr, size, size/1048576.0, guard);
}
static void *thr(void *a) { report((const char *)a); return NULL; }
int main(void) {
pthread_attr_t a; pthread_t t;
report("main");
pthread_attr_init(&a); pthread_attr_setstacksize(&a, 8u<<20);
pthread_create(&t, &a, thr, (void *)"thread-8MiB"); pthread_join(t, NULL);
pthread_attr_destroy(&a);
pthread_create(&t, NULL, thr, (void *)"thread-default"); pthread_join(t, NULL);
return 0;
}Alpine 3.24 / musl 1.2:
main base=0x7ffeda154000 size=131072 (0.12 MiB) guard=0 <-- wrong, RLIMIT_STACK is 8 MiB
thread-8MiB base=0x7f772ee70000 size=8391456 (8.00 MiB) guard=8192 <-- correct
thread-default base=0x7f772f650000 size=133920 (0.13 MiB) guard=8192 <-- correct
Debian / glibc 2.41:
main base=0x7fffa3c90000 size=8380416 (7.99 MiB) guard=0
thread-8MiB base=0x7909e3800000 size=8388608 (8.00 MiB) guard=4096
thread-default base=0x7909e3800000 size=8388608 (8.00 MiB) guard=4096
This is the same shape as gh-159320 / GH-159321, which re-enabled detection for Android after it had been
caught by the same blanket condition.
Scope
The broken detection is not specific to test_lazy_import or to 3.15+: the guard is identical on 3.14, and
the probe above returns the same constant 1450 on a 3.14 build. 3.14 is only green because
test_lazy_import does not exist there and no other test asks for a thread smaller than the assumed
Py_C_STACK_SIZE. Any embedder or stdlib caller creating a musl thread with less than ~1 MiB of stack can
overflow silently.
Apart from this one test, the suite is clean on Alpine: main 550 test files run / 1 failed, 3.15 548 /
1 failed (same test), 3.14 525 / 0 failed.
I appreciate musl is PEP 11 tier 0 (configure says so explicitly) and that gh-151546, gh-139467 and gh-76488
were all closed as OS-unsupported. Filing this one anyway because it is a hard crash where a guard exists
and is expected to work, and because the Android case was treated as worth fixing.
CPython versions tested on:
3.14, 3.15, CPython main branch
Operating systems tested on:
Linux
Activity
serhiy-storchaka commentedon Oct 20, 2026
My apologies, the report was created by Claude, and I wasn't familiar enough with this to shorten it.
pythongh-159876: Detect the C stack size of non-main threads on musl
pythongh-159876: Detect the C stack size of non-main threads on musl