@@ -713,37 +713,22 @@ pub mod module {
713713 }
714714
715715 fn py_os_after_fork_child ( vm : & VirtualMachine ) {
716- // Reset low-level state before any Python code runs in the child.
717- // Signal triggers from the parent must not fire in the child.
716+ // Phase 1: Reset all internal locks FIRST.
717+ // After fork(), locks held by dead parent threads would deadlock
718+ // if we try to acquire them. This must happen before anything else.
719+ #[ cfg( feature = "threading" ) ]
720+ reinit_locks_after_fork ( vm) ;
721+
722+ // Phase 2: Reset low-level atomic state (no locks needed).
718723 crate :: signal:: clear_after_fork ( ) ;
719724 crate :: stdlib:: signal:: _signal:: clear_wakeup_fd_after_fork ( ) ;
720725
721726 // Reset weakref stripe locks that may have been held during fork.
722727 #[ cfg( feature = "threading" ) ]
723728 crate :: object:: reset_weakref_locks_after_fork ( ) ;
724729
725- // Force-unlock all global VM locks that may have been held by
726- // threads that no longer exist in the child process after fork.
727- // SAFETY: After fork, only the forking thread survives. Any lock
728- // held by another thread is permanently stuck. The forking thread
729- // does not hold these locks during fork() (a high-level Python op).
730- unsafe {
731- vm. ctx . string_pool . force_unlock_after_fork ( ) ;
732- vm. state . codec_registry . force_unlock_after_fork ( ) ;
733- force_unlock_mutex_after_fork ( & vm. state . atexit_funcs ) ;
734- force_unlock_mutex_after_fork ( & vm. state . before_forkers ) ;
735- force_unlock_mutex_after_fork ( & vm. state . after_forkers_child ) ;
736- force_unlock_mutex_after_fork ( & vm. state . after_forkers_parent ) ;
737- force_unlock_mutex_after_fork ( & vm. state . global_trace_func ) ;
738- force_unlock_mutex_after_fork ( & vm. state . global_profile_func ) ;
739- crate :: gc_state:: gc_state ( ) . force_unlock_after_fork ( ) ;
740-
741- // Import lock (ReentrantMutex) — was previously not reinit'd
742- #[ cfg( feature = "threading" ) ]
743- crate :: stdlib:: imp:: reinit_imp_lock_after_fork ( ) ;
744- }
745-
746- // Mark all other threads as done before running Python callbacks
730+ // Phase 3: Clean up thread state. Locks are now reinit'd so we can
731+ // acquire them normally instead of using try_lock().
747732 #[ cfg( feature = "threading" ) ]
748733 crate :: stdlib:: thread:: after_fork_child ( vm) ;
749734
@@ -752,18 +737,45 @@ pub mod module {
752737 vm. signal_handlers
753738 . get_or_init ( crate :: signal:: new_signal_handlers) ;
754739
740+ // Phase 4: Run Python-level at-fork callbacks.
755741 let after_forkers_child: Vec < PyObjectRef > = vm. state . after_forkers_child . lock ( ) . clone ( ) ;
756742 run_at_forkers ( after_forkers_child, false , vm) ;
757743 }
758744
759- /// Force-unlock a PyMutex if held by a dead thread after fork.
745+ /// Reset all parking_lot-based locks in the interpreter state after fork() .
760746 ///
761- /// # Safety
762- /// Must only be called after fork() in the child process.
763- unsafe fn force_unlock_mutex_after_fork < T > ( mutex : & crate :: common:: lock:: PyMutex < T > ) {
764- if mutex. try_lock ( ) . is_none ( ) {
765- // SAFETY: Lock is held by a dead thread after fork.
766- unsafe { mutex. force_unlock ( ) } ;
747+ /// After fork(), only the calling thread survives. Any locks held by other
748+ /// (now-dead) threads would cause deadlocks. We unconditionally reset them
749+ /// to unlocked by zeroing the raw lock bytes.
750+ #[ cfg( all( unix, feature = "threading" ) ) ]
751+ fn reinit_locks_after_fork ( vm : & VirtualMachine ) {
752+ use rustpython_common:: lock:: reinit_mutex_after_fork;
753+
754+ unsafe {
755+ // PyGlobalState PyMutex locks
756+ reinit_mutex_after_fork ( & vm. state . before_forkers ) ;
757+ reinit_mutex_after_fork ( & vm. state . after_forkers_child ) ;
758+ reinit_mutex_after_fork ( & vm. state . after_forkers_parent ) ;
759+ reinit_mutex_after_fork ( & vm. state . atexit_funcs ) ;
760+ reinit_mutex_after_fork ( & vm. state . global_trace_func ) ;
761+ reinit_mutex_after_fork ( & vm. state . global_profile_func ) ;
762+
763+ // PyGlobalState parking_lot::Mutex locks
764+ reinit_mutex_after_fork ( & vm. state . thread_frames ) ;
765+ reinit_mutex_after_fork ( & vm. state . thread_handles ) ;
766+ reinit_mutex_after_fork ( & vm. state . shutdown_handles ) ;
767+
768+ // Context-level RwLock
769+ vm. ctx . string_pool . reinit_after_fork ( ) ;
770+
771+ // Codec registry RwLock
772+ vm. state . codec_registry . reinit_after_fork ( ) ;
773+
774+ // GC state (multiple Mutex + RwLock)
775+ crate :: gc_state:: gc_state ( ) . reinit_after_fork ( ) ;
776+
777+ // Import lock (RawReentrantMutex<RawMutex, RawThreadId>)
778+ crate :: stdlib:: imp:: reinit_imp_lock_after_fork ( ) ;
767779 }
768780 }
769781
0 commit comments