@@ -424,64 +424,6 @@ def __exit__(self, *args, **kwargs):
424424 self ._lock .release ()
425425
426426
427- def _get_module_chain (name ):
428- """Return the chain of dotted-name prefixes from root to leaf.
429-
430- For example: 'a.b.c' -> ['a', 'a.b', 'a.b.c']
431- """
432- parts = name .split ('.' )
433- return ['.' .join (parts [:i + 1 ]) for i in range (len (parts ))]
434-
435-
436- class _HierarchicalLockManager :
437- """Manages acquisition of multiple module locks in hierarchical order.
438-
439- This prevents deadlocks by ensuring all threads acquire locks in the
440- same order (parent modules before child modules).
441- """
442-
443- def __init__ (self , name ):
444- self ._name = name
445- self ._module_chain = _get_module_chain (name )
446- self ._locks = []
447-
448- def __enter__ (self ):
449- try :
450- for module_name in self ._module_chain :
451- # Only acquire lock if module is not already fully loaded
452- module = sys .modules .get (module_name )
453- if (module is None or
454- getattr (getattr (module , "__spec__" , None ),
455- "_initializing" , False )):
456- lock = _get_module_lock (module_name )
457- try :
458- lock .acquire ()
459- except _DeadlockError :
460- if module_name == self ._name :
461- raise
462- # The parent is being initialised by a thread that
463- # is (transitively) waiting on a lock we hold.
464- # Apply the same policy as _lock_unlock_module():
465- # accept a partially-initialised parent for circular
466- # imports rather than failing the whole chain.
467- continue
468- self ._locks .append ((module_name , lock ))
469- except :
470- # __exit__ is not called when __enter__ raises (e.g. _DeadlockError
471- # on the leaf lock, or KeyboardInterrupt), so release whatever we
472- # already hold to avoid permanently leaking held module locks.
473- for module_name , lock in reversed (self ._locks ):
474- lock .release ()
475- self ._locks .clear ()
476- raise
477- return self
478-
479- def __exit__ (self , * args , ** kwargs ):
480- for module_name , lock in reversed (self ._locks ):
481- lock .release ()
482- self ._locks .clear ()
483-
484-
485427# The following two functions are for consumption by Python/import.c.
486428
487429def _get_module_lock (name ):
@@ -1262,17 +1204,15 @@ def _sanity_check(name, package, level):
12621204
12631205def _find_and_load_unlocked (name , import_ , * , lazy_submodule = False ):
12641206 path = None
1265- sys .audit (
1266- "import" ,
1267- name ,
1268- path ,
1269- getattr (sys , "path" , None ),
1270- getattr (sys , "meta_path" , None ),
1271- getattr (sys , "path_hooks" , None )
1272- )
12731207 parent = name .rpartition ('.' )[0 ]
12741208 parent_spec = None
12751209 if parent :
1210+ # _resolve_parents() has normally imported the parent already. It can
1211+ # be missing again by now if its import failed in another thread or
1212+ # something removed it from sys.modules; it is then imported afresh,
1213+ # like any other module that is not in sys.modules. That import runs
1214+ # while holding name's lock, the lock order _resolve_parents() exists
1215+ # to avoid; it is tolerated here because the case is so rare.
12761216 if parent not in sys .modules :
12771217 _call_with_frames_removed (import_ , parent )
12781218 # Crazy side-effects!
@@ -1330,31 +1270,90 @@ def _find_and_load_unlocked(name, import_, *, lazy_submodule=False):
13301270_NEEDS_LOADING = object ()
13311271
13321272
1273+ # Return whether module (a sys.modules value) is still executing.
1274+ def _is_initializing (module ):
1275+ return getattr (getattr (module , '__spec__' , None ), '_initializing' , False )
1276+
1277+
1278+ # Import, or wait for, the parent packages of name.
1279+ #
1280+ # _find_and_load() calls this before taking name's module lock, so that no
1281+ # thread holds a submodule's lock while waiting for its parent. This avoids
1282+ # the deadlocks and import errors of gh-83065 and gh-159963.
1283+ def _resolve_parents (name , import_ ):
1284+ # Wait for each ancestor package that another thread is still
1285+ # initializing, outermost first. Importing beneath a package while its
1286+ # __init__ is part way through its own imports could give one of the two
1287+ # threads a partially initialized module. Missing ancestors are not
1288+ # imported: only the immediate parent has to exist.
1289+ parts = name .split ('.' )
1290+ for i in range (1 , len (parts ) - 1 ):
1291+ ancestor = '.' .join (parts [:i ])
1292+ if _is_initializing (sys .modules .get (ancestor )):
1293+ _lock_unlock_module (ancestor )
1294+ # Import the immediate parent, or wait for it likewise.
1295+ parent = name .rpartition ('.' )[0 ]
1296+ if parent :
1297+ module = sys .modules .get (parent , _NEEDS_LOADING )
1298+ if module is _NEEDS_LOADING or _is_initializing (module ):
1299+ _call_with_frames_removed (import_ , parent )
1300+
1301+
13331302def _find_and_load (name , import_ , * , lazy_submodule = False ):
13341303 """Find and load the module."""
13351304
13361305 # Optimization: we avoid unneeded module locking if the module
13371306 # already exists in sys.modules and is fully initialized.
13381307 module = sys .modules .get (name , _NEEDS_LOADING )
1339- if (module is _NEEDS_LOADING or
1340- getattr (getattr (module , "__spec__" , None ), "_initializing" , False )):
1341-
1342- if '.' in name :
1343- lock_manager = _HierarchicalLockManager (name )
1344- else :
1345- lock_manager = _ModuleLockManager (name )
1346-
1347- with lock_manager :
1348- module = sys .modules .get (name , _NEEDS_LOADING )
1349- if module is _NEEDS_LOADING :
1350- return _find_and_load_unlocked (
1351- name , import_ , lazy_submodule = lazy_submodule )
1352-
1353- # Optimization: only call _bootstrap._lock_unlock_module() if
1354- # module.__spec__._initializing is True.
1355- # NOTE: because of this, initializing must be set *before*
1356- # putting the new module in sys.modules.
1357- _lock_unlock_module (name )
1308+ if module is _NEEDS_LOADING or _is_initializing (module ):
1309+ if module is not _NEEDS_LOADING :
1310+ # Wait for the thread executing the module.
1311+ # NOTE: this relies on __spec__._initializing being set *before*
1312+ # the module is put in sys.modules.
1313+ lock = _get_module_lock (name )
1314+ try :
1315+ lock .acquire ()
1316+ except _DeadlockError :
1317+ # A circular import split across threads: accept the
1318+ # partially initialized module.
1319+ pass
1320+ else :
1321+ # The module is gone if the thread loading it failed. Look
1322+ # while still holding the lock, so that another thread cannot
1323+ # have begun loading it again.
1324+ try :
1325+ module = sys .modules .get (name , _NEEDS_LOADING )
1326+ finally :
1327+ lock .release ()
1328+
1329+ if module is _NEEDS_LOADING :
1330+ sys .audit (
1331+ "import" ,
1332+ name ,
1333+ None ,
1334+ getattr (sys , "path" , None ),
1335+ getattr (sys , "meta_path" , None ),
1336+ getattr (sys , "path_hooks" , None )
1337+ )
1338+ _resolve_parents (name , import_ )
1339+ lock = _get_module_lock (name )
1340+ try :
1341+ lock .acquire ()
1342+ except _DeadlockError :
1343+ # A circular import split across threads. As in
1344+ # _lock_unlock_module(), accept a partially initialized
1345+ # module if there is one.
1346+ module = sys .modules .get (name , _NEEDS_LOADING )
1347+ if module is _NEEDS_LOADING :
1348+ raise
1349+ else :
1350+ try :
1351+ module = sys .modules .get (name , _NEEDS_LOADING )
1352+ if module is _NEEDS_LOADING :
1353+ return _find_and_load_unlocked (
1354+ name , import_ , lazy_submodule = lazy_submodule )
1355+ finally :
1356+ lock .release ()
13581357 else :
13591358 # Verify the module is still in sys.modules. Another thread may have
13601359 # removed it (due to import failure) between our sys.modules.get()
0 commit comments