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