@@ -541,9 +541,16 @@ def scenario_fork_contended_mutex(iterations: int = 100) -> None:
541541 """Fork safety benchmark scenario:
542542 8 threads create/close Readers in a tight loop while the main thread
543543 forks 5× per iteration (500 total forks). Maximises the probability that
544- the registry Mutex is held at the instant of fork(). If pthread_atfork
545- didn't reinit the Rust mutex the first cimpl_free in the child would
546- deadlock; the Python guard is also exercised by child GC.
544+ the registry Mutex is held at the instant of fork(). Each fork inherits
545+ a Reader created by the main thread; the child explicitly closes it
546+ (then runs GC), so the PID guard is exercised on every fork — without
547+ the guard the close would call into the native library and could
548+ deadlock on a mutex left locked by a vanished worker thread. The parent
549+ closes the same Reader after the child exits (its own PID: real free).
550+
551+ Note: the workers' Readers are pinned by frozen thread frames in the
552+ child, so child gc.collect() alone would free nothing — hence the
553+ explicit close of an inherited object.
547554 """
548555 if not hasattr (os , "fork" ):
549556 return
@@ -562,7 +569,15 @@ def _worker():
562569 try :
563570 for _ in _iterate (iterations ):
564571 for _ in range (5 ):
565- _fork_wait (lambda : gc .collect ())
572+ with open (SIGNED_JPEG , "rb" ) as f :
573+ reader = Reader ("image/jpeg" , f )
574+
575+ def _child (r = reader ):
576+ r .close ()
577+ gc .collect ()
578+
579+ _fork_wait (_child )
580+ reader .close ()
566581 finally :
567582 stop .set ()
568583 for t in threads :
0 commit comments