@@ -489,7 +489,7 @@ def add_item(i):
489489 self .assertEqual (bag_states ['key' + str (i )].items , [])
490490
491491 @staticmethod
492- def _run_reset_state_concurrent_teardown (use_asyncio ):
492+ def _run_reset_state_concurrent_teardown (use_asyncio , reset_started ):
493493 dofn = BasicDofn (sleep_time = 0.5 )
494494 async_dofn = async_lib .AsyncWrapper (dofn , use_asyncio = use_asyncio )
495495 async_dofn .setup ()
@@ -502,15 +502,26 @@ def _run_reset_state_concurrent_teardown(use_asyncio):
502502
503503 # Verify that calling reset_state() while background tasks are actively running
504504 # completes cleanly without causing lock-ordering deadlocks.
505+ reset_started .set ()
505506 async_lib .AsyncWrapper .reset_state ()
506507
507508 def test_reset_state_concurrent_teardown (self ):
508509 # Verify concurrent teardown safety in a separate process to prevent any potential
509510 # regressions from freezing the main pytest process at exit.
511+ reset_started = multiprocessing .Event ()
510512 p = multiprocessing .Process (
511513 target = AsyncTest ._run_reset_state_concurrent_teardown ,
512- args = (self .use_asyncio , ))
514+ args = (self .use_asyncio , reset_started ))
513515 p .start ()
516+
517+ # Python 3.14 uses forkserver by default on POSIX. Wait for child startup
518+ # before measuring reset_state() so import and process startup time cannot
519+ # be mistaken for a teardown deadlock.
520+ if not reset_started .wait (timeout = 30.0 ):
521+ p .terminate ()
522+ p .join ()
523+ self .fail ("child process did not reach reset_state() before timeout" )
524+
514525 p .join (timeout = 10.0 )
515526
516527 if p .is_alive ():
0 commit comments