@@ -706,41 +706,69 @@ mod _multiprocessing {
706706
707707 // if (res < 0 && errno == EAGAIN && blocking)
708708 if res < 0 && Errno :: last ( ) == Errno :: EAGAIN && blocking {
709- // Couldn't acquire immediately, need to block
709+ // Couldn't acquire immediately, need to block.
710+ //
711+ // Save errno inside the allow_threads closure, before
712+ // attach_thread() runs — matches CPython which saves
713+ // `err = errno` before Py_END_ALLOW_THREADS.
714+
710715 #[ cfg( not( target_vendor = "apple" ) ) ]
711716 {
717+ let mut saved_errno;
712718 loop {
713719 let sem_ptr = self . handle . as_ptr ( ) ;
714720 // Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS
715- res = if let Some ( ref dl) = deadline {
716- vm. allow_threads ( || unsafe { libc:: sem_timedwait ( sem_ptr, dl) } )
721+ let ( r, e) = if let Some ( ref dl) = deadline {
722+ vm. allow_threads ( || {
723+ let r = unsafe { libc:: sem_timedwait ( sem_ptr, dl) } ;
724+ (
725+ r,
726+ if r < 0 {
727+ Errno :: last ( )
728+ } else {
729+ Errno :: from_raw ( 0 )
730+ } ,
731+ )
732+ } )
717733 } else {
718- vm. allow_threads ( || unsafe { libc:: sem_wait ( sem_ptr) } )
734+ vm. allow_threads ( || {
735+ let r = unsafe { libc:: sem_wait ( sem_ptr) } ;
736+ (
737+ r,
738+ if r < 0 {
739+ Errno :: last ( )
740+ } else {
741+ Errno :: from_raw ( 0 )
742+ } ,
743+ )
744+ } )
719745 } ;
746+ res = r;
747+ saved_errno = e;
720748
721749 if res >= 0 {
722750 break ;
723751 }
724- let err = Errno :: last ( ) ;
725- if err == Errno :: EINTR {
752+ if saved_errno == Errno :: EINTR {
726753 vm. check_signals ( ) ?;
727754 continue ;
728755 }
729756 break ;
730757 }
758+ if res < 0 {
759+ return handle_wait_error ( vm, saved_errno) ;
760+ }
731761 }
732762 #[ cfg( target_vendor = "apple" ) ]
733763 {
734764 // macOS: use polled fallback since sem_timedwait is not available
735765 if let Some ( ref dl) = deadline {
736766 match sem_timedwait_polled ( self . handle . as_ptr ( ) , dl, vm) {
737- Ok ( ( ) ) => res = 0 ,
767+ Ok ( ( ) ) => { }
738768 Err ( SemWaitError :: Timeout ) => {
739- // Timeout occurred - return false directly
740769 return Ok ( false ) ;
741770 }
742771 Err ( SemWaitError :: SignalException ( exc) ) => {
743- // Propagate the original exception (e.g., KeyboardInterrupt)
744772 return Err ( exc) ;
745773 }
746774 Err ( SemWaitError :: OsError ( e) ) => {
@@ -749,31 +777,42 @@ mod _multiprocessing {
749777 }
750778 } else {
751779 // No timeout: use sem_wait (available on macOS)
780+ let mut saved_errno;
752781 loop {
753782 let sem_ptr = self . handle . as_ptr ( ) ;
754- res = vm. allow_threads ( || unsafe { libc:: sem_wait ( sem_ptr) } ) ;
783+ let ( r, e) = vm. allow_threads ( || {
784+ let r = unsafe { libc:: sem_wait ( sem_ptr) } ;
785+ (
786+ r,
787+ if r < 0 {
788+ Errno :: last ( )
789+ } else {
790+ Errno :: from_raw ( 0 )
791+ } ,
792+ )
793+ } ) ;
794+ res = r;
795+ saved_errno = e;
755796 if res >= 0 {
756797 break ;
757798 }
758- let err = Errno :: last ( ) ;
759- if err == Errno :: EINTR {
799+ if saved_errno == Errno :: EINTR {
760800 vm. check_signals ( ) ?;
761801 continue ;
762802 }
763803 break ;
764804 }
805+ if res < 0 {
806+ return handle_wait_error ( vm, saved_errno) ;
807+ }
765808 }
766809 }
767- }
768-
769- // result handling:
770- if res < 0 {
810+ } else if res < 0 {
811+ // Non-blocking path failed, or blocking=false
771812 let err = Errno :: last ( ) ;
772813 match err {
773814 Errno :: EAGAIN | Errno :: ETIMEDOUT => return Ok ( false ) ,
774815 Errno :: EINTR => {
775- // EINTR should be handled by the check_signals() loop above
776- // If we reach here, check signals again and propagate any exception
777816 return vm. check_signals ( ) . map ( |_| false ) ;
778817 }
779818 _ => return Err ( os_error ( vm, err) ) ,
@@ -1081,6 +1120,14 @@ mod _multiprocessing {
10811120 CString :: new ( full) . map_err ( |_| vm. new_value_error ( "embedded null character" ) )
10821121 }
10831122
1123+ fn handle_wait_error ( vm : & VirtualMachine , saved_errno : Errno ) -> PyResult < bool > {
1124+ match saved_errno {
1125+ Errno :: EAGAIN | Errno :: ETIMEDOUT => Ok ( false ) ,
1126+ Errno :: EINTR => vm. check_signals ( ) . map ( |_| false ) ,
1127+ _ => Err ( os_error ( vm, saved_errno) ) ,
1128+ }
1129+ }
1130+
10841131 fn os_error ( vm : & VirtualMachine , err : Errno ) -> PyBaseExceptionRef {
10851132 // _PyMp_SetError maps to PyErr_SetFromErrno
10861133 let exc_type = match err {
0 commit comments