@@ -29,6 +29,7 @@ def _object_name(obj):
2929# Bootstrap-related code ######################################################
3030
3131# Modules injected manually by _setup()
32+ _behaviors = None
3233_thread = None
3334_warnings = None
3435_weakref = None
@@ -407,6 +408,69 @@ def __repr__(self):
407408 return f'_DummyModuleLock({ self .name !r} ) at { id (self )} '
408409
409410
411+ class _MultiInterpreterModuleLock :
412+ """A recursive lock implementation which is able to detect deadlocks
413+ across multiple subinterpreters.
414+ """
415+
416+ def __init__ (self , name ):
417+ self .lock = _behaviors .RLock ()
418+ self .wakeup = _behaviors .Lock ()
419+ self .name = name
420+ self .owner = None
421+ self .count = []
422+ self .waiters = []
423+
424+ def has_deadlock (self ):
425+ return _has_deadlocked (
426+ # get the thread id of the current subinterpreter
427+ target_id = _behaviors .get_ident (),
428+ seen_ids = set (),
429+ candidate_ids = [self .owner ],
430+ blocking_on = _blocking_on ,
431+ )
432+
433+ def acquire (self ):
434+ """
435+ Acquire the module lock. If a potential deadlock is detected,
436+ a _DeadlockError is raised.
437+ Otherwise, the lock is always acquired and True is returned.
438+ """
439+ bid = _behaviors .get_ident ()
440+ with _BlockingOnManager (bid , self ):
441+ while True :
442+ with self .lock :
443+ if self .count == [] or self .owner == bid :
444+ self .owner = bid
445+ self .count .append (True )
446+ return True
447+
448+ if self .has_deadlock ():
449+ raise _DeadlockError (f'deadlock detected by { self !r} ' )
450+
451+ if self .wakeup .acquire (False ):
452+ self .waiters .append (None )
453+
454+ self .wakeup .acquire ()
455+ self .wakeup .release ()
456+
457+ def release (self ):
458+ bid = _behaviors .get_ident ()
459+ with self .lock :
460+ if self .owner != bid :
461+ raise RuntimeError ('cannot release un-acquired lock' )
462+ assert len (self .count ) > 0
463+ self .count .pop ()
464+ if not len (self .count ):
465+ self .owner = None
466+ if len (self .waiters ) > 0 :
467+ self .waiters .pop ()
468+ self .wakeup .release ()
469+
470+ def __repr__ (self ):
471+ return f'_MultiInterpreterModuleLock({ self .name !r} ) at { id (self )} '
472+
473+
410474class _ModuleLockManager :
411475
412476 def __init__ (self , name ):
@@ -439,6 +503,8 @@ def _get_module_lock(name):
439503 if lock is None :
440504 if _thread is None :
441505 lock = _DummyModuleLock (name )
506+ elif _behaviors is not None and _behaviors .running ():
507+ lock = _MultiInterpreterModuleLock (name )
442508 else :
443509 lock = _ModuleLock (name )
444510
@@ -943,6 +1009,11 @@ def _load_unlocked(spec):
9431009 finally :
9441010 spec ._initializing = False
9451011
1012+ if _behaviors is not None and _behaviors .running ():
1013+ # all modules must be made immutable upon load
1014+ if not isimmutable (module ):
1015+ makeimmutable (module )
1016+
9461017 return module
9471018
9481019# A method used during testing of _load_unlocked() and by
@@ -1518,7 +1589,7 @@ def _setup(sys_module, _imp_module):
15181589
15191590 # Directly load built-in modules needed during bootstrap.
15201591 self_module = sys .modules [__name__ ]
1521- for builtin_name in ('_thread' , '_warnings' , '_weakref' ):
1592+ for builtin_name in ('_thread' , '_warnings' , '_weakref' , '_behaviors' ):
15221593 if builtin_name not in sys .modules :
15231594 builtin_module = _builtin_from_name (builtin_name )
15241595 else :
0 commit comments