@@ -707,6 +707,22 @@ pub mod module {
707707 #[ cfg( feature = "threading" ) ]
708708 crate :: object:: reset_weakref_locks_after_fork ( ) ;
709709
710+ // Force-unlock all global VM locks that may have been held by
711+ // threads that no longer exist in the child process after fork.
712+ // SAFETY: After fork, only the forking thread survives. Any lock
713+ // held by another thread is permanently stuck. The forking thread
714+ // does not hold these locks during fork() (a high-level Python op).
715+ unsafe {
716+ vm. ctx . string_pool . force_unlock_after_fork ( ) ;
717+ vm. state . codec_registry . force_unlock_after_fork ( ) ;
718+ force_unlock_mutex_after_fork ( & vm. state . atexit_funcs ) ;
719+ force_unlock_mutex_after_fork ( & vm. state . before_forkers ) ;
720+ force_unlock_mutex_after_fork ( & vm. state . after_forkers_child ) ;
721+ force_unlock_mutex_after_fork ( & vm. state . after_forkers_parent ) ;
722+ force_unlock_mutex_after_fork ( & vm. state . global_trace_func ) ;
723+ force_unlock_mutex_after_fork ( & vm. state . global_profile_func ) ;
724+ }
725+
710726 // Mark all other threads as done before running Python callbacks
711727 #[ cfg( feature = "threading" ) ]
712728 crate :: stdlib:: thread:: after_fork_child ( vm) ;
@@ -716,18 +732,24 @@ pub mod module {
716732 vm. signal_handlers
717733 . get_or_init ( crate :: signal:: new_signal_handlers) ;
718734
719- let after_forkers_child = match vm. state . after_forkers_child . try_lock ( ) {
720- Some ( guard) => guard. clone ( ) ,
721- None => {
722- // SAFETY: After fork in child process, only the current thread
723- // exists. The lock holder no longer exists.
724- unsafe { vm. state . after_forkers_child . force_unlock ( ) } ;
725- vm. state . after_forkers_child . lock ( ) . clone ( )
726- }
727- } ;
735+ let after_forkers_child: Vec < PyObjectRef > =
736+ vm. state . after_forkers_child . lock ( ) . clone ( ) ;
728737 run_at_forkers ( after_forkers_child, false , vm) ;
729738 }
730739
740+ /// Force-unlock a PyMutex if held by a dead thread after fork.
741+ ///
742+ /// # Safety
743+ /// Must only be called after fork() in the child process.
744+ unsafe fn force_unlock_mutex_after_fork < T > (
745+ mutex : & crate :: common:: lock:: PyMutex < T > ,
746+ ) {
747+ if mutex. try_lock ( ) . is_none ( ) {
748+ // SAFETY: Lock is held by a dead thread after fork.
749+ unsafe { mutex. force_unlock ( ) } ;
750+ }
751+ }
752+
731753 fn py_os_after_fork_parent ( vm : & VirtualMachine ) {
732754 let after_forkers_parent: Vec < PyObjectRef > = vm. state . after_forkers_parent . lock ( ) . clone ( ) ;
733755 run_at_forkers ( after_forkers_parent, false , vm) ;
0 commit comments