Skip to content
Open
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.
44 changes: 36 additions & 8 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count)
# define Py_C_STACK_SIZE 4000000
#endif

#ifdef _Py_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

static void
hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp)
{
Expand All @@ -89,14 +101,8 @@ 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
// musl supports HAVE_PTHREAD_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. musl doesn't give
// us a way to detect it, so we have to assume any non-glibc,
// non-Android Linux is musl and mustn't rely on pthread_getattr_np.
# if (defined(HAVE_PTHREAD_GETATTR_NP) || defined(_Py_HAVE_PTHREAD_ATTR_GET_NP)) \
&& !defined(_AIX) && !defined(__NetBSD__) \
&& (defined(__GLIBC__) || defined(__ANDROID__) || !defined(__linux__))
&& !defined(_AIX) && !defined(__NetBSD__)
size_t stack_size, guard_size;
void *stack_addr;
pthread_attr_t attr;
Expand All @@ -113,8 +119,30 @@ hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp)
err |= pthread_attr_destroy(&attr);
}
if (err == 0) {
*base = ((uintptr_t)stack_addr) + guard_size;
*top = (uintptr_t)stack_addr + stack_size;
# ifdef _Py_HAVE_UNRELIABLE_PTHREAD_GETATTR_NP
/* pthread_getattr_np on the main thread of a musl process reports
* only the part of the stack that is currently mapped, rather than
* the size allowed. The reported top is correct.
* New threads 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) {
/* If there's a reasonable limit set (one that fits in the
* address space below *top), use that.
* Otherwise use the compile-time guess */
size_t limit = Py_C_STACK_SIZE;
if (rl.rlim_cur != RLIM_INFINITY && rl.rlim_cur < *top) {
limit = (size_t)rl.rlim_cur;
}
if (stack_size < limit) {
stack_addr = (void *)(*top - limit);
}
}
}
# endif
*base = ((uintptr_t)stack_addr) + guard_size;
return;
}
# endif
Expand Down
72 changes: 72 additions & 0 deletions configure

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

41 changes: 41 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -5763,6 +5763,47 @@ PY_CHECK_FUNC_PRIVATE([strerror_r], [@%:@include <string.h>])
# FreeBSD pthread_attr_get_np()
PY_CHECK_FUNC_PRIVATE([pthread_attr_get_np], [@%:@include <pthread_np.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],
[# Run the test with a finite stack limit (1 MiB),
# so the result does not depend on the limit configure runs under.
py_save_stack_limit=`ulimit -S -s 2>/dev/null`
ulimit -S -s 1024 2>/dev/null
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_IF([test -n "$py_save_stack_limit"],
[ulimit -S -s "$py_save_stack_limit" 2>/dev/null])])
AS_VAR_IF([ac_cv_unreliable_pthread_getattr_np], [yes],
[AC_DEFINE([_Py_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 @@ -2173,6 +2173,10 @@
/* Define if you have the 'tanpi' function. */
#undef _Py_HAVE_TANPI

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

/* Thread stack size set by the linker (in bytes). */
#undef _Py_LINKER_THREAD_STACK_SIZE

Expand Down
Loading