Skip to content
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
7 changes: 6 additions & 1 deletion Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,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
Expand Down
24 changes: 23 additions & 1 deletion Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 9 additions & 2 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -5017,8 +5017,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;
}

Expand Down
Loading