From 6337faf74d6c55d65ad1ee90fdd5f38b702a7657 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 12 Jan 2025 08:04:30 -0500 Subject: [PATCH 1/6] Fix merge conflicts. --- Lib/test/test_sys.py | 30 +++++++++++++++++++ ...-01-11-12-39-17.gh-issue-128717.i65d06.rst | 2 ++ Python/ceval.c | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-12-39-17.gh-issue-128717.i65d06.rst diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 400577d36cd44d..280f600508b4af 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -362,6 +362,36 @@ def test_setrecursionlimit_to_depth(self): finally: sys.setrecursionlimit(old_limit) + @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful if the GIL is disabled") + @threading_helper.requires_working_threading() + def test_racing_recursion_limit(self): + from threading import Thread + def something_recursive(): + def count(n): + if n > 0: + return count(n - 1) + 1 + return 0 + + count(50) + + def set_recursion_limit(): + for limit in range(100, 200): + sys.setrecursionlimit(limit) + + threads = [] + for _ in range(5): + threads.append(Thread(target=set_recursion_limit)) + + for _ in range(5): + threads.append(Thread(target=something_recursive)) + + with threading_helper.catch_threading_exception() as cm: + for thread in threads: + thread.start() + + if cm.exc_value: + raise cm.exc_value + def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute test.support.get_attribute(sys, "getwindowsversion") diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-12-39-17.gh-issue-128717.i65d06.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-12-39-17.gh-issue-128717.i65d06.rst new file mode 100644 index 00000000000000..212c6d3cb97216 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-11-12-39-17.gh-issue-128717.i65d06.rst @@ -0,0 +1,2 @@ +Fix a crash when setting the recursion limit while other threads are active +on the :term:`free threaded ` build. diff --git a/Python/ceval.c b/Python/ceval.c index 04dd0840519c37..9ba691e6342e30 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -265,12 +265,14 @@ void Py_SetRecursionLimit(int new_limit) { PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyEval_StopTheWorld(interp); interp->ceval.recursion_limit = new_limit; for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { int depth = p->py_recursion_limit - p->py_recursion_remaining; p->py_recursion_limit = new_limit; p->py_recursion_remaining = new_limit - depth; } + _PyEval_StartTheWorld(interp); } /* The function _Py_EnterRecursiveCallTstate() only calls _Py_CheckRecursiveCall() From d3d3d5e2a690ad042a3fa7d368a46997f782e24e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 12 Jan 2025 10:53:42 -0500 Subject: [PATCH 2/6] [3.13] gh-128717: Stop-the-world when setting the recursion limit (GH-128741) (cherry picked from commit f6c61bf2d7d8b66ccd9f16e723546bdcc251a3d0) Co-authored-by: Peter Bierma From d93cc252792aab19c4a9a3728138abb8dcac664c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 12 Jan 2025 12:05:22 -0500 Subject: [PATCH 3/6] Use threading_helper.start_threads() --- Lib/test/test_sys.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 280f600508b4af..0d96204f523177 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -385,12 +385,10 @@ def set_recursion_limit(): for _ in range(5): threads.append(Thread(target=something_recursive)) - with threading_helper.catch_threading_exception() as cm: - for thread in threads: - thread.start() - - if cm.exc_value: - raise cm.exc_value + with threading_helper.start_threads(threads): + with threading_helper.catch_threading_exception() as cm: + if cm.exc_value: + raise cm.exc_value def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute From 939308e5a30a7a2a295b36d8f8ac451fcc7e0fab Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 13 Jan 2025 11:43:27 -0500 Subject: [PATCH 4/6] Swap catch_threading_exception() and start_threads() --- Lib/test/test_sys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 0d96204f523177..c29279cc3ca6dc 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -385,8 +385,8 @@ def set_recursion_limit(): for _ in range(5): threads.append(Thread(target=something_recursive)) - with threading_helper.start_threads(threads): - with threading_helper.catch_threading_exception() as cm: + with threading_helper.catch_threading_exception() as cm: + with threading_helper.start_threads(threads): if cm.exc_value: raise cm.exc_value From c094349a30668daf473fecd1e2d16b5942a75986 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 13 Jan 2025 11:44:15 -0500 Subject: [PATCH 5/6] HEAD_LOCK() the thread state list. --- Python/ceval.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/ceval.c b/Python/ceval.c index 9ba691e6342e30..2f67d40874ba41 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -266,12 +266,14 @@ Py_SetRecursionLimit(int new_limit) { PyInterpreterState *interp = _PyInterpreterState_GET(); _PyEval_StopTheWorld(interp); + HEAD_LOCK(interp->runtime); interp->ceval.recursion_limit = new_limit; for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { int depth = p->py_recursion_limit - p->py_recursion_remaining; p->py_recursion_limit = new_limit; p->py_recursion_remaining = new_limit - depth; } + HEAD_UNLOCK(interp->runtime); _PyEval_StartTheWorld(interp); } From 90be7d8b4405040b451e309da7cd2a3c9915aa41 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 13 Jan 2025 11:45:04 -0500 Subject: [PATCH 6/6] Fix usage of cm.exc_value --- Lib/test/test_sys.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index c29279cc3ca6dc..01ce0118651b75 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -387,8 +387,10 @@ def set_recursion_limit(): with threading_helper.catch_threading_exception() as cm: with threading_helper.start_threads(threads): - if cm.exc_value: - raise cm.exc_value + pass + + if cm.exc_value: + raise cm.exc_value def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute