From d036bade5dcc96b262c924f5d971efe03253389d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 19 Oct 2026 20:54:27 -0700 Subject: [PATCH] gh-159603: Use pthread_attr_get_np() on FreeBSD (#159604) Use pthread_attr_get_np() on FreeBSD to get the stack size (and guard size) of the current thread. Check if pthread_attr_get_np() function and header are available in configure. Update test_json.test_recursion. On FreeBSD 16 with a default stack size of 512 MB, the json C implementation no longer raises RecursionError in test_highly_nested_objects_encoding(). Reduce also max_depth from 5,000 to 2,000 so test_highly_nested_objects_encoding() of the Python implementation only takes 1.6 seconds, instead of 12 seconds. (cherry picked from commit 71ba39dc057941fb8ea032b813862a215bb3d5aa) --- Lib/test/test_json/test_recursion.py | 27 +++++++--- ...-10-17-02-47-26.gh-issue-159603.OFagJz.rst | 2 + Python/ceval.c | 16 ++++-- configure | 49 +++++++++++++++++++ configure.ac | 5 +- pyconfig.h.in | 6 +++ 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-17-02-47-26.gh-issue-159603.OFagJz.rst diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index 17c7afe6417d88..e7e0783f9decb1 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -91,22 +91,35 @@ def test_highly_nested_objects_decoding(self): @support.skip_emscripten_stack_overflow() @support.requires_resource('cpu') def test_highly_nested_objects_encoding(self): - # See #12051 + # See issue gh-56260 + + # With max_depth=2_000 on Linux, the Python implementation takes around + # 2 seconds to raise RecursionError, whereas the C implementation only + # takes around 160 ms. + max_depth = 2_000 l, d = [], {} - for x in range(500_000): + for x in range(max_depth * 10): l, d = [l], {'k':d} - with self.assertRaises(RecursionError): - with support.infinite_recursion(5000): + + # Don't use assertRaises(RecursionError): on FreeBSD 16 with a default + # stack size of 512 MiB, the C implementation doesn't raise + # RecursionError. + try: + with support.infinite_recursion(max_depth): self.dumps(l) - with self.assertRaises(RecursionError): - with support.infinite_recursion(5000): + except RecursionError: + pass + try: + with support.infinite_recursion(max_depth): self.dumps(d) + except RecursionError: + pass @support.skip_if_huge_c_stack() @support.skip_emscripten_stack_overflow() @support.skip_wasi_stack_overflow() def test_endless_recursion(self): - # See #12051 + # See issue gh-56260 class EndlessJSONEncoder(self.json.JSONEncoder): def default(self, o): """If check_circular is False, this will keep adding another list.""" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-17-02-47-26.gh-issue-159603.OFagJz.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-17-02-47-26.gh-issue-159603.OFagJz.rst new file mode 100644 index 00000000000000..bd46b85509dbc2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-17-02-47-26.gh-issue-159603.OFagJz.rst @@ -0,0 +1,2 @@ +On FreeBSD, call ``pthread_attr_get_np()`` to get the stack size (and guard +size) of the current thread. Patch by Victor Stinner. diff --git a/Python/ceval.c b/Python/ceval.c index b132f9ba8331dc..04b61c59c72b38 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3,6 +3,11 @@ #include "ceval.h" #include "pycore_long.h" +#ifdef HAVE_PTHREAD_NP_H +# include // pthread_attr_get_np() +#endif + + int Py_GetRecursionLimit(void) { @@ -159,13 +164,18 @@ hardware_stack_limits(uintptr_t *base, uintptr_t *top, uintptr_t sp) // 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(_AIX) && \ - !defined(__NetBSD__) && \ - (defined(__GLIBC__) || defined(__ANDROID__) || !defined(__linux__)) +# if (defined(HAVE_PTHREAD_GETATTR_NP) || defined(_Py_HAVE_PTHREAD_ATTR_GET_NP)) \ + && !defined(_AIX) && !defined(__NetBSD__) \ + && (defined(__GLIBC__) || defined(__ANDROID__) || !defined(__linux__)) size_t stack_size, guard_size; void *stack_addr; pthread_attr_t attr; +#ifdef _Py_HAVE_PTHREAD_ATTR_GET_NP + int err = pthread_attr_init(&attr); + err |= pthread_attr_get_np(pthread_self(), &attr); +#else int err = pthread_getattr_np(pthread_self(), &attr); +#endif if (err == 0) { err = pthread_attr_getguardsize(&attr, &guard_size); err |= pthread_attr_getstack(&attr, &stack_addr, &stack_size); diff --git a/configure b/configure index cab5ab8d282039..b04d7954dd0960 100755 --- a/configure +++ b/configure @@ -11896,6 +11896,12 @@ if test "x$ac_cv_header_pthread_h" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "pthread_np.h" "ac_cv_header_pthread_np_h" "$ac_includes_default" +if test "x$ac_cv_header_pthread_np_h" = xyes +then : + printf "%s\n" "#define HAVE_PTHREAD_NP_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "pty.h" "ac_cv_header_pty_h" "$ac_includes_default" if test "x$ac_cv_header_pty_h" = xyes @@ -21212,6 +21218,49 @@ fi +# FreeBSD pthread_attr_get_np() + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_attr_get_np" >&5 +printf %s "checking for pthread_attr_get_np... " >&6; } +if test ${ac_cv_func_pthread_attr_get_np+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *x=pthread_attr_get_np + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_pthread_attr_get_np=yes +else case e in #( + e) ac_cv_func_pthread_attr_get_np=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_pthread_attr_get_np" >&5 +printf "%s\n" "$ac_cv_func_pthread_attr_get_np" >&6; } + if test "x$ac_cv_func_pthread_attr_get_np" = xyes +then : + +printf "%s\n" "#define _Py_HAVE_PTHREAD_ATTR_GET_NP 1" >>confdefs.h + +fi + + + + # os.statx uses Linux's statx function. AIX also has a function named statx, diff --git a/configure.ac b/configure.ac index 89d879a4c613e1..1abff0c5d805bf 100644 --- a/configure.ac +++ b/configure.ac @@ -3168,7 +3168,8 @@ AC_CHECK_HEADERS([ \ alloca.h asm/types.h bluetooth.h conio.h direct.h dlfcn.h endian.h errno.h fcntl.h grp.h \ io.h langinfo.h libintl.h libutil.h linux/auxvec.h sys/auxv.h linux/fs.h linux/limits.h linux/memfd.h \ linux/netfilter_ipv4.h linux/random.h linux/soundcard.h linux/sched.h \ - linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h poll.h process.h pthread.h pty.h \ + linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h \ + poll.h process.h pthread.h pthread_np.h pty.h \ sched.h setjmp.h shadow.h signal.h spawn.h sys/audioio.h sys/bsdtty.h sys/devpoll.h \ sys/endian.h sys/epoll.h sys/event.h sys/eventfd.h sys/file.h sys/ioctl.h sys/kern_control.h \ sys/loadavg.h sys/lock.h sys/memfd.h sys/mkdev.h sys/mman.h sys/modem.h sys/param.h sys/pidfd.h sys/poll.h \ @@ -5523,6 +5524,8 @@ AC_CHECK_FUNCS([ \ ]) PY_CHECK_FUNC_PRIVATE([strerror_r], [@%:@include ]) +# FreeBSD pthread_attr_get_np() +PY_CHECK_FUNC_PRIVATE([pthread_attr_get_np], [@%:@include ]) # os.statx uses Linux's statx function. AIX also has a function named statx, diff --git a/pyconfig.h.in b/pyconfig.h.in index facf6435648e2d..dfc28a51944f52 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1048,6 +1048,9 @@ /* Define to 1 if you have the 'pthread_kill' function. */ #undef HAVE_PTHREAD_KILL +/* Define to 1 if you have the header file. */ +#undef HAVE_PTHREAD_NP_H + /* Define to 1 if you have the 'pthread_setname_np' function. */ #undef HAVE_PTHREAD_SETNAME_NP @@ -2094,6 +2097,9 @@ /* Define if you have the 'PR_SET_VMA_ANON_NAME' constant. */ #undef _Py_HAVE_PR_SET_VMA_ANON_NAME +/* Define if you have the 'pthread_attr_get_np' function. */ +#undef _Py_HAVE_PTHREAD_ATTR_GET_NP + /* Define if you have the 'strerror_r' function. */ #undef _Py_HAVE_STRERROR_R