@@ -706,41 +706,69 @@ mod _multiprocessing { |
706 | 706 |
|
707 | 707 | // if (res < 0 && errno == EAGAIN && blocking) |
708 | 708 | 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 | + |
710 | 715 | #[cfg(not(target_vendor = "apple"))] |
711 | 716 | { |
| 717 | + let mut saved_errno; |
712 | 718 | loop { |
713 | 719 | let sem_ptr = self.handle.as_ptr(); |
714 | 720 | // 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 | + }) |
717 | 733 | } 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 | + }) |
719 | 745 | }; |
| 746 | + res = r; |
| 747 | + saved_errno = e; |
720 | 748 |
|
721 | 749 | if res >= 0 { |
722 | 750 | break; |
723 | 751 | } |
724 | | - let err = Errno::last(); |
725 | | - if err == Errno::EINTR { |
| 752 | + if saved_errno == Errno::EINTR { |
726 | 753 | vm.check_signals()?; |
727 | 754 | continue; |
728 | 755 | } |
729 | 756 | break; |
730 | 757 | } |
| 758 | + if res < 0 { |
| 759 | + return handle_wait_error(vm, saved_errno); |
| 760 | + } |
731 | 761 | } |
732 | 762 | #[cfg(target_vendor = "apple")] |
733 | 763 | { |
734 | 764 | // macOS: use polled fallback since sem_timedwait is not available |
735 | 765 | if let Some(ref dl) = deadline { |
736 | 766 | match sem_timedwait_polled(self.handle.as_ptr(), dl, vm) { |
737 | | - Ok(()) => res = 0, |
| 767 | + Ok(()) => {} |
738 | 768 | Err(SemWaitError::Timeout) => { |
739 | | - // Timeout occurred - return false directly |
740 | 769 | return Ok(false); |
741 | 770 | } |
742 | 771 | Err(SemWaitError::SignalException(exc)) => { |
743 | | - // Propagate the original exception (e.g., KeyboardInterrupt) |
744 | 772 | return Err(exc); |
745 | 773 | } |
746 | 774 | Err(SemWaitError::OsError(e)) => { |
@@ -749,31 +777,42 @@ mod _multiprocessing { |
749 | 777 | } |
750 | 778 | } else { |
751 | 779 | // No timeout: use sem_wait (available on macOS) |
| 780 | + let mut saved_errno; |
752 | 781 | loop { |
753 | 782 | 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; |
755 | 796 | if res >= 0 { |
756 | 797 | break; |
757 | 798 | } |
758 | | - let err = Errno::last(); |
759 | | - if err == Errno::EINTR { |
| 799 | + if saved_errno == Errno::EINTR { |
760 | 800 | vm.check_signals()?; |
761 | 801 | continue; |
762 | 802 | } |
763 | 803 | break; |
764 | 804 | } |
| 805 | + if res < 0 { |
| 806 | + return handle_wait_error(vm, saved_errno); |
| 807 | + } |
765 | 808 | } |
766 | 809 | } |
767 | | - } |
768 | | - |
769 | | - // result handling: |
770 | | - if res < 0 { |
| 810 | + } else if res < 0 { |
| 811 | + // Non-blocking path failed, or blocking=false |
771 | 812 | let err = Errno::last(); |
772 | 813 | match err { |
773 | 814 | Errno::EAGAIN | Errno::ETIMEDOUT => return Ok(false), |
774 | 815 | 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 |
777 | 816 | return vm.check_signals().map(|_| false); |
778 | 817 | } |
779 | 818 | _ => return Err(os_error(vm, err)), |
@@ -1081,6 +1120,14 @@ mod _multiprocessing { |
1081 | 1120 | CString::new(full).map_err(|_| vm.new_value_error("embedded null character")) |
1082 | 1121 | } |
1083 | 1122 |
|
| 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 | + |
1084 | 1131 | fn os_error(vm: &VirtualMachine, err: Errno) -> PyBaseExceptionRef { |
1085 | 1132 | // _PyMp_SetError maps to PyErr_SetFromErrno |
1086 | 1133 | let exc_type = match err { |
|