Skip to content

Commit a47c97a

Browse files
committed
Save errno inside allow_threads in semaphore acquire
allow_threads may call attach_thread() on return, which can invoke syscalls that clobber errno. Capture errno inside the closure before it is lost.
1 parent 0355885 commit a47c97a

File tree

2 files changed

+68
-19
lines changed

2 files changed

+68
-19
lines changed

crates/stdlib/src/multiprocessing.rs

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {

crates/vm/src/builtins/tuple.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ impl PyPayload for PyTuple {
108108

109109
#[inline]
110110
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
111-
let len = unsafe { &*(obj as *const crate::Py<PyTuple>) }.elements.len();
111+
let len = unsafe { &*(obj as *const crate::Py<PyTuple>) }
112+
.elements
113+
.len();
112114
if len == 0 || len > TupleFreeList::MAX_SAVE_SIZE {
113115
return false;
114116
}

0 commit comments

Comments
 (0)