@@ -227,8 +227,6 @@ def f():
227227 tid = _thread .start_new_thread (f , ())
228228 done .wait ()
229229 self .assertEqual (ident [0 ], tid )
230- # Kill the "immortal" _DummyThread
231- del threading ._active [ident [0 ]]
232230
233231 # run with a small(ish) thread stack size (256 KiB)
234232 def test_various_ops_small_stack (self ):
@@ -256,32 +254,55 @@ def test_various_ops_large_stack(self):
256254
257255 def test_foreign_thread (self ):
258256 # Check that a "foreign" thread can use the threading module.
257+ dummy_thread = None
258+ error = None
259259 def f (mutex ):
260- # Calling current_thread() forces an entry for the foreign
261- # thread to get made in the threading._active map.
262- threading .current_thread ()
263- mutex .release ()
260+ try :
261+ nonlocal dummy_thread
262+ nonlocal error
263+ # Calling current_thread() forces an entry for the foreign
264+ # thread to get made in the threading._active map.
265+ dummy_thread = threading .current_thread ()
266+ tid = dummy_thread .ident
267+ self .assertIn (tid , threading ._active )
268+ self .assertIsInstance (dummy_thread , threading ._DummyThread )
269+ self .assertIs (threading ._active .get (tid ), dummy_thread )
270+ # gh-29376
271+ self .assertTrue (
272+ dummy_thread .is_alive (),
273+ 'Expected _DummyThread to be considered alive.'
274+ )
275+ self .assertIn ('_DummyThread' , repr (dummy_thread ))
276+ except BaseException as e :
277+ error = e
278+ finally :
279+ mutex .release ()
264280
265281 mutex = threading .Lock ()
266282 mutex .acquire ()
267283 with threading_helper .wait_threads_exit ():
268284 tid = _thread .start_new_thread (f , (mutex ,))
269285 # Wait for the thread to finish.
270286 mutex .acquire ()
271- self .assertIn (tid , threading ._active )
272- self .assertIsInstance (threading ._active [tid ], threading ._DummyThread )
273- #Issue 29376
274- self .assertTrue (threading ._active [tid ].is_alive ())
275- self .assertRegex (repr (threading ._active [tid ]), '_DummyThread' )
276-
287+ if error is not None :
288+ raise error
289+ self .assertEqual (tid , dummy_thread .ident )
277290 # Issue gh-106236:
278291 with self .assertRaises (RuntimeError ):
279- threading . _active [ tid ] .join ()
280- threading . _active [ tid ] ._started .clear ()
292+ dummy_thread .join ()
293+ dummy_thread ._started .clear ()
281294 with self .assertRaises (RuntimeError ):
282- threading ._active [tid ].is_alive ()
283-
284- del threading ._active [tid ]
295+ dummy_thread .is_alive ()
296+ # Busy wait for the following condition: after the thread dies, the
297+ # related dummy thread must be removed from threading._active.
298+ timeout = 5
299+ timeout_at = time .monotonic () + timeout
300+ while time .monotonic () < timeout_at :
301+ if threading ._active .get (dummy_thread .ident ) is not dummy_thread :
302+ break
303+ time .sleep (.1 )
304+ else :
305+ self .fail ('It was expected that the created threading._DummyThread was removed from threading._active.' )
285306
286307 # PyThreadState_SetAsyncExc() is a CPython-only gimmick, not (currently)
287308 # exposed at the Python level. This test relies on ctypes to get at it.
0 commit comments