Skip to content

bpo-32307: thread: fix stack size on musl platforms. #26224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Force a default thread stack size value for all platforms instead of
assuming that they all have enough for Python threads. This makes it so too
many recursions are caught by the interpreter instead of stack overflowing
on musl libc.
22 changes: 13 additions & 9 deletions Python/thread_pthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
be conditional on _POSIX_THREAD_ATTR_STACKSIZE being defined. */
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
#ifndef THREAD_STACK_SIZE
#define THREAD_STACK_SIZE 0 /* use default stack size */
/* -1 means THREAD_STACK_SIZE hasn't been given a proper value yet.
-DTHREAD_STACK_SIZE=0 in CFLAGS will force the platform's default instead. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid disruption for anyone who may already be supplying CFLAGS containing -DTHREAD_STACK_SIZE=0 in their builds already keep the existing 0 behavior: 0 should be the platform specific values below.

If we want a new value in some circumstances, that should either be enabled via an appropriate test in configure.ac and thus via pyconfig.h. Or yet another platform specific compile time #if check should be used here to detect musl libc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or yet another platform specific compile time #if check should be used here to detect musl libc.

To be clear, the purpose here is to make the code universal. Right now, the only platform where the default thread size actually works is glibc. Instead of adding a case for each and every platform other than glibc, IMO it makes much more sense to have a sane default and override for one or two platforms as necessary, instead.

Testing for musl is unsupported, they don't provide any identifying macros, by design.

#define THREAD_STACK_SIZE -1
#endif

/* The default stack size for new threads on OSX and BSD is small enough that
Expand All @@ -39,33 +41,35 @@
* The default stack sizes below are the empirically determined minimal stack
* sizes where a simple recursive function doesn't cause a hard crash.
*/
#if defined(__APPLE__) && defined(THREAD_STACK_SIZE) && THREAD_STACK_SIZE == 0
#if defined(__APPLE__) && THREAD_STACK_SIZE == -1
#undef THREAD_STACK_SIZE
/* Note: This matches the value of -Wl,-stack_size in configure.ac */
#define THREAD_STACK_SIZE 0x1000000
#endif
#if defined(__FreeBSD__) && defined(THREAD_STACK_SIZE) && THREAD_STACK_SIZE == 0
#if defined(__FreeBSD__) && THREAD_STACK_SIZE == -1
#undef THREAD_STACK_SIZE
#define THREAD_STACK_SIZE 0x400000
#endif
#if defined(_AIX) && defined(THREAD_STACK_SIZE) && THREAD_STACK_SIZE == 0
#undef THREAD_STACK_SIZE
#define THREAD_STACK_SIZE 0x200000
#endif
/* bpo-38852: test_threading.test_recursion_limit() checks that 1000 recursive
Python calls (default recursion limit) doesn't crash, but raise a regular
RecursionError exception. In debug mode, Python function calls allocates
more memory on the stack, so use a stack of 8 MiB. */
#if defined(__ANDROID__) && defined(THREAD_STACK_SIZE) && THREAD_STACK_SIZE == 0
#if defined(__ANDROID__) && THREAD_STACK_SIZE == -1
# ifdef Py_DEBUG
# undef THREAD_STACK_SIZE
# define THREAD_STACK_SIZE 0x800000
# endif
#endif
#if defined(__VXWORKS__) && defined(THREAD_STACK_SIZE) && THREAD_STACK_SIZE == 0
#if defined(__VXWORKS__) && THREAD_STACK_SIZE == -1
#undef THREAD_STACK_SIZE
#define THREAD_STACK_SIZE 0x100000
#endif
#if THREAD_STACK_SIZE == -1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this #if does not look right. it is always undoing and overriding what all of the above #if's have just gone through the platform specific trouble to setup as their own defaults. because they're all testing for == -1

#undef THREAD_STACK_SIZE
/* This is big enough for most platforms without being wasteful. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a unittest in the stdlib that exercises the default recursion limit to on a thread to ensure the process doesn't crash? (it'll be imperfect as different internal call recursion codepaths have different C stack needs, but it still serves as a good failsafe; perhaps have the test raise recursionlimit to 1.5x its default value).

https://github.com/python/cpython/blob/v3.10.0/Lib/test/test_threading.py#L1234 test_threading.test_recursion_limit exists. We should probably change that to raise the recursion limit a bit above the default in it's thread for some safety margin.

#define THREAD_STACK_SIZE 0x200000
#endif

/* for safety, ensure a viable minimum stacksize */
#define THREAD_STACK_MIN 0x8000 /* 32 KiB */
#else /* !_POSIX_THREAD_ATTR_STACKSIZE */
Expand Down