https://github.com/python/cpython/commit/e60e7bdc92a493338aa89599099df5e9d39bc078 commit: e60e7bdc92a493338aa89599099df5e9d39bc078 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: brettcannon <[email protected]> date: 2026-10-27T22:17:27Z summary:
[3.15] gh-159652: Fix import lock leak when the C stack is nearly exhausted (GH-159660) (GH-160779) gh-159652: Fix import lock leak when the C stack is nearly exhausted (GH-159660) When an import failed with a stack overflow RecursionError, releasing the import locks on the error path could itself raise RecursionError, because the cleanup runs in a new C frame. This left the global import lock or a module lock held forever, and later imports in other threads blocked indefinitely. _imp.acquire_lock() now raises RecursionError when there is not enough stack left to release the lock. importlib calls it before taking any import lock, so this covers the per-module locks too. The module lock weakref callback skips cleanup in that case instead of reporting an unraisable exception. (cherry picked from commit 926b0bf3110bf86550ef248cf911e24fc123ec64) Co-authored-by: Cary Hawkins <[email protected]> files: A Misc/NEWS.d/next/Core_and_Builtins/2026-10-18-00-30-00.gh-issue-159652.Qk3vLm.rst M Lib/importlib/_bootstrap.py M Lib/test/test_import/__init__.py M Python/import.c diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 82f3c200ac8710e..b50eb2b677f52ae 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -446,7 +446,12 @@ def _get_module_lock(name): lock = _ModuleLock(name) def cb(ref, name=name): - _imp.acquire_lock() + try: + _imp.acquire_lock() + except RecursionError: + # Not enough stack left to take the import lock safely. + # Leave the dead weakref; _get_module_lock() replaces it. + return try: # bpo-31070: Check if another thread created a new lock # after the previous lock was destroyed diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 17b1e03470dd048..870a461e8091967 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -45,7 +45,11 @@ Py_GIL_DISABLED, no_rerun, force_not_colorized_test_class, - catch_unraisable_exception + catch_unraisable_exception, + infinite_recursion, + run_with_limited_c_stack, + skip_emscripten_stack_overflow, + skip_wasi_stack_overflow ) from test.support.import_helper import ( forget, make_legacy_pyc, unlink, unload, ready_to_import, @@ -782,6 +786,24 @@ def run(): finally: del sys.path[0] + @cpython_only + @skip_emscripten_stack_overflow() + @skip_wasi_stack_overflow() + @run_with_limited_c_stack(depth=100_000) + def test_acquire_lock_near_c_stack_limit(self): + # _imp.acquire_lock() must refuse to take the import lock when there + # is not enough C stack left to release it again. + def recurse(_): + _imp.acquire_lock() + _imp.release_lock() + list(map(recurse, [None])) + + with infinite_recursion(100_000): + with self.assertRaisesRegex(RecursionError, + 'acquiring the import lock'): + recurse(None) + self.assertFalse(_imp.lock_held()) + @unittest.skipUnless(sys.platform == "win32", "Windows-specific") def test_dll_dependency_import(self): from _winapi import GetModuleFileName diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-18-00-30-00.gh-issue-159652.Qk3vLm.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-18-00-30-00.gh-issue-159652.Qk3vLm.rst new file mode 100644 index 000000000000000..2b213c983b87187 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-18-00-30-00.gh-issue-159652.Qk3vLm.rst @@ -0,0 +1,6 @@ +Fix a hang when an import fails with :exc:`RecursionError` because the C +stack is nearly exhausted: releasing the import locks could itself fail and +leave the global import lock or a module lock held forever, so that later +imports in other threads blocked indefinitely. The import system now raises +:exc:`RecursionError` before taking an import lock when there is not enough +stack left to release it. diff --git a/Python/import.c b/Python/import.c index 92bbde37af1ba21..38b7d294a9daeed 100644 --- a/Python/import.c +++ b/Python/import.c @@ -5014,8 +5014,15 @@ static PyObject * _imp_acquire_lock_impl(PyObject *module) /*[clinic end generated code: output=1aff58cb0ee1b026 input=60e9c1b4ab471ead]*/ { - PyInterpreterState *interp = _PyInterpreterState_GET(); - _PyImport_AcquireLock(interp); + PyThreadState *tstate = _PyThreadState_GET(); + // Leave enough stack for importlib to release the lock. + if (_Py_ReachedRecursionLimitWithMargin(tstate, 2)) { + _PyErr_SetString(tstate, PyExc_RecursionError, + "maximum recursion depth exceeded while " + "acquiring the import lock"); + return NULL; + } + _PyImport_AcquireLock(tstate->interp); Py_RETURN_NONE; } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
