@@ -463,20 +463,70 @@ def catch_exception():
463463
464464 self .assertIsInstance (caught_exception , InjectedException )
465465
466+ @requires_subprocess ()
466467 def test_limbo_cleanup (self ):
467- # Issue 7481: Failure to start thread should cleanup the limbo map.
468- def fail_new_thread (* args , ** kwargs ):
469- raise threading .ThreadError ()
470- _start_joinable_thread = threading ._start_joinable_thread
471- threading ._start_joinable_thread = fail_new_thread
472- try :
473- t = threading .Thread (target = lambda : None )
474- self .assertRaises (threading .ThreadError , t .start )
475- self .assertFalse (
476- t in threading ._limbo ,
477- "Failed to cleanup _limbo map on failure of Thread.start()." )
478- finally :
479- threading ._start_joinable_thread = _start_joinable_thread
468+ # Issue 7481: Failure to start a thread must clean up the limbo map.
469+ code = """if 1:
470+ import builtins
471+ import sys
472+ import threading
473+
474+ error = getattr(builtins, sys.argv[1])()
475+ def audit(event, args):
476+ if event == '_thread.start_joinable_thread':
477+ raise error
478+
479+ sys.addaudithook(audit)
480+ thread = threading.Thread()
481+ try:
482+ thread.start()
483+ except BaseException as caught:
484+ assert caught is error
485+ else:
486+ raise AssertionError('Thread.start() did not raise')
487+
488+ assert thread._os_thread_handle.ident == 0
489+ assert not thread._started.is_set()
490+ assert thread not in threading._limbo
491+ assert thread not in threading.enumerate()
492+ """
493+ for exception in ('RuntimeError' , 'KeyboardInterrupt' , 'SystemExit' ):
494+ with self .subTest (exception = exception ):
495+ assert_python_ok ('-c' , code , exception )
496+
497+ @requires_subprocess ()
498+ def test_start_interrupted_after_native_start (self ):
499+ code = """if 1:
500+ import sys
501+ import threading
502+ from test.support import SHORT_TIMEOUT, threading_helper
503+
504+ def interrupt(frame, event, arg):
505+ if event == 'c_return' and arg is threading._start_joinable_thread:
506+ raise KeyboardInterrupt
507+
508+ completed = threading.Event()
509+ thread = threading.Thread(target=completed.set)
510+ # Hold the child before it signals successful startup.
511+ with thread._started._cond:
512+ sys.setprofile(interrupt)
513+ try:
514+ thread.start()
515+ except KeyboardInterrupt:
516+ pass
517+ else:
518+ raise AssertionError('Thread.start() did not raise')
519+ finally:
520+ sys.setprofile(None)
521+ assert thread._os_thread_handle.ident
522+ assert thread in threading._limbo
523+
524+ assert thread._started.wait(SHORT_TIMEOUT)
525+ threading_helper.join_thread(thread)
526+ assert completed.is_set()
527+ """
528+ _ , _ , stderr = assert_python_ok ('-c' , code )
529+ self .assertEqual (stderr , b'' )
480530
481531 def test_finalize_running_thread (self ):
482532 # Issue 1402: the PyGILState_Ensure / _Release functions may be called
0 commit comments