@@ -4163,6 +4163,89 @@ def __init__(self, *args, **kwargs):
41634163 handler = logging .getHandlerByName ('custom' )
41644164 self .assertEqual (handler .custom_kwargs , custom_kwargs )
41654165
4166+ # See gh-91555 and gh-90321
4167+ @support .requires_subprocess ()
4168+ def test_deadlock_in_queue (self ):
4169+ queue = multiprocessing .Queue ()
4170+ handler = logging .handlers .QueueHandler (queue )
4171+ logger = multiprocessing .get_logger ()
4172+ level = logger .level
4173+ try :
4174+ logger .setLevel (logging .DEBUG )
4175+ logger .addHandler (handler )
4176+ logger .debug ("deadlock" )
4177+ finally :
4178+ logger .setLevel (level )
4179+ logger .removeHandler (handler )
4180+
4181+ def test_recursion_in_custom_handler (self ):
4182+ class BadHandler (logging .Handler ):
4183+ def __init__ (self ):
4184+ super ().__init__ ()
4185+ def emit (self , record ):
4186+ logger .debug ("recurse" )
4187+ logger = logging .getLogger ("test_recursion_in_custom_handler" )
4188+ logger .addHandler (BadHandler ())
4189+ logger .setLevel (logging .DEBUG )
4190+ logger .debug ("boom" )
4191+
4192+ @threading_helper .requires_working_threading ()
4193+ def test_thread_supression_noninterference (self ):
4194+ lock = threading .Lock ()
4195+ logger = logging .getLogger ("test_thread_supression_noninterference" )
4196+
4197+ # Block on the first call, allow others through
4198+ #
4199+ # NOTE: We need to bypass the base class's lock, otherwise that will
4200+ # block multiple calls to the same handler itself.
4201+ class BlockOnceHandler (TestHandler ):
4202+ def __init__ (self , barrier ):
4203+ super ().__init__ (support .Matcher ())
4204+ self .barrier = barrier
4205+
4206+ def createLock (self ):
4207+ self .lock = None
4208+
4209+ def handle (self , record ):
4210+ self .emit (record )
4211+
4212+ def emit (self , record ):
4213+ if self .barrier :
4214+ barrier = self .barrier
4215+ self .barrier = None
4216+ barrier .wait ()
4217+ with lock :
4218+ pass
4219+ super ().emit (record )
4220+ logger .info ("blow up if not supressed" )
4221+
4222+ barrier = threading .Barrier (2 )
4223+ handler = BlockOnceHandler (barrier )
4224+ logger .addHandler (handler )
4225+ logger .setLevel (logging .DEBUG )
4226+
4227+ t1 = threading .Thread (target = logger .debug , args = ("1" ,))
4228+ with lock :
4229+
4230+ # Ensure first thread is blocked in the handler, hence supressing logging...
4231+ t1 .start ()
4232+ barrier .wait ()
4233+
4234+ # ...but the second thread should still be able to log...
4235+ t2 = threading .Thread (target = logger .debug , args = ("2" ,))
4236+ t2 .start ()
4237+ t2 .join (timeout = 3 )
4238+
4239+ self .assertEqual (len (handler .buffer ), 1 )
4240+ self .assertTrue (handler .matches (levelno = logging .DEBUG , message = '2' ))
4241+
4242+ # The first thread should still be blocked here
4243+ self .assertTrue (t1 .is_alive ())
4244+
4245+ # Now the lock has been released the first thread should complete
4246+ t1 .join ()
4247+ self .assertEqual (len (handler .buffer ), 2 )
4248+ self .assertTrue (handler .matches (levelno = logging .DEBUG , message = '1' ))
41664249
41674250class ManagerTest (BaseTest ):
41684251 def test_manager_loggerclass (self ):
0 commit comments