X Tutup
Skip to content

Save errno inside allow_threads in semaphore acquire#7391

Merged
youknowone merged 1 commit intoRustPython:mainfrom
youknowone:multiprocessing
Mar 10, 2026
Merged

Save errno inside allow_threads in semaphore acquire#7391
youknowone merged 1 commit intoRustPython:mainfrom
youknowone:multiprocessing

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Mar 9, 2026

allow_threads may call attach_thread() on return, which can invoke syscalls that clobber errno. Capture errno inside the closure before it is lost.

Summary by CodeRabbit

  • Bug Fixes

    • Improved semaphore/wait error handling on Unix and macOS: preserves native errno from blocking waits to ensure correct interpretation of timeouts, resource availability, and interrupts.
    • Enhanced EINTR handling and retry logic during blocking operations; behavior and public APIs remain unchanged.
  • Refactor

    • Internal cleanup and minor code formatting consolidation with no observable behavioral or API changes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: b95302cb-3034-47fd-aee1-ce841c43661a

📥 Commits

Reviewing files that changed from the base of the PR and between 8ec74ef and a47c97a.

📒 Files selected for processing (2)
  • crates/stdlib/src/multiprocessing.rs
  • crates/vm/src/builtins/tuple.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/vm/src/builtins/tuple.rs

📝 Walkthrough

Walkthrough

Saves errno immediately inside vm.allow_threads-wrapped blocking semaphore waits and uses that saved value for subsequent error handling and retry logic on Unix and macOS; adds a centralized helper to translate saved errno. Also reformats unsafe tuple freelist access without behavior change.

Changes

Cohort / File(s) Summary
Semaphore errno handling
crates/stdlib/src/multiprocessing.rs
Blocking semaphore waits capture (result, saved_errno) inside the allow_threads block; subsequent logic (timed/non-timed, Unix and macOS paths) uses saved_errno for EINTR/EAGAIN/ETIMEDOUT handling and error reporting. Introduces handle_wait_error(vm, saved_errno) and adds explanatory comments.
Tuple freelist access formatting
crates/vm/src/builtins/tuple.rs
Reformatted unsafe tuple-element access in freelist_push to a multi-line chained extraction; no semantic change.

Sequence Diagram(s)

sequenceDiagram
    participant VM as VM (allow_threads)
    participant Thread as Worker Thread
    participant OS as OS semaphore (sem_wait / sem_timedwait)
    participant Signals as Signal subsystem

    rect rgba(200,200,255,0.5)
    Thread->>VM: enter vm.allow_threads (release GIL)
    end

    rect rgba(200,255,200,0.5)
    Thread->>OS: call sem_wait / sem_timedwait
    OS-->>Thread: return (r, errno)  // captured immediately as saved_errno
    end

    alt saved_errno == 0
        Thread->>VM: reacquire GIL and return success (true)
    else saved_errno == EAGAIN or ETIMEDOUT
        Thread->>VM: reacquire GIL and return false
    else saved_errno == EINTR
        Thread->>Signals: check pending signals
        Signals-->>Thread: signal handled? (may raise)
        Thread->>OS: retry or propagate
    else other errno
        Thread->>VM: reacquire GIL and return os_error(saved_errno)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I saved the errno before it fled,
Released the GIL and hopped ahead,
If signals call I pause and try,
Else errors bubble up tidy and spry,
Semaphore snug — carrot crumbs nearby.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: saving errno inside allow_threads during semaphore acquire operations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@youknowone youknowone marked this pull request as ready for review March 9, 2026 15:47
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/stdlib/src/multiprocessing.rs (1)

721-744: Extract the repeated wait/errno wrapper.

Lines 721-744 and 781-791 duplicate the same allow_threads + (res, errno) packaging. A tiny local helper would keep the platform branches focused on which wait primitive they call and reduce the chance of one path drifting from the others.

As per coding guidelines, "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code."

Also applies to: 781-791

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/stdlib/src/multiprocessing.rs` around lines 721 - 744, The
sem_wait/sem_timedwait branches duplicate the same vm.allow_threads wrapper and
(r, errno) packaging; create a small local helper (e.g., run_wait_and_errno or a
closure) that takes a zero-argument FnOnce calling the desired wait primitive
(call sites: libc::sem_timedwait and libc::sem_wait) and returns the (r, Errno)
tuple after running it inside vm.allow_threads, then replace both duplicated
blocks with calls to that helper so only the differing wait call remains in each
branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/stdlib/src/multiprocessing.rs`:
- Around line 714-748: The initial assignment let mut saved_errno =
Errno::UnknownErrno is never read because every code path that uses saved_errno
assigns it first; remove the dead initialization and declare saved_errno as an
uninitialized variable that is assigned in the branches that set it (or use
Option<Errno>), i.e., change the declaration near saved_errno and ensure the
non-Apple loop (where vm.allow_threads calls calling
libc::sem_timedwait/sem_wait), and the Apple non-deadline loop assign
saved_errno before it is used in the subsequent match; reference saved_errno,
Errno::UnknownErrno, vm.allow_threads, libc::sem_timedwait, libc::sem_wait and
res when making the adjustment.

---

Nitpick comments:
In `@crates/stdlib/src/multiprocessing.rs`:
- Around line 721-744: The sem_wait/sem_timedwait branches duplicate the same
vm.allow_threads wrapper and (r, errno) packaging; create a small local helper
(e.g., run_wait_and_errno or a closure) that takes a zero-argument FnOnce
calling the desired wait primitive (call sites: libc::sem_timedwait and
libc::sem_wait) and returns the (r, Errno) tuple after running it inside
vm.allow_threads, then replace both duplicated blocks with calls to that helper
so only the differing wait call remains in each branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 87fae19d-e9e9-4f6a-a4fe-1c4ebf7a9693

📥 Commits

Reviewing files that changed from the base of the PR and between 2266ba7 and bb5fa6b.

📒 Files selected for processing (1)
  • crates/stdlib/src/multiprocessing.rs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
crates/stdlib/src/multiprocessing.rs (1)

714-714: ⚠️ Potential issue | 🟡 Minor

Drop the dead saved_errno initializer.

Line 714 is overwritten on every path that can reach the later match, so the Errno::UnknownErrno assignment is never read and will keep clippy::unused_assignments noisy.

♻️ Proposed fix
-                let mut saved_errno = Errno::UnknownErrno;
+                let mut saved_errno = None;
@@
-                        saved_errno = e;
+                        saved_errno = Some(e);
@@
-                        if saved_errno == Errno::EINTR {
+                        if saved_errno == Some(Errno::EINTR) {
@@
-                            saved_errno = e;
+                            saved_errno = Some(e);
@@
-                            if saved_errno == Errno::EINTR {
+                            if saved_errno == Some(Errno::EINTR) {
@@
-                    match saved_errno {
+                    match saved_errno.expect("errno must be captured when sem_*wait fails") {

As per coding guidelines "Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints that are introduced by your changes".

Also applies to: 747-747, 752-752, 793-793, 797-797, 808-808

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/stdlib/src/multiprocessing.rs` at line 714, The local variable
saved_errno is being assigned Errno::UnknownErrno but that value is always
overwritten before use; remove the dead initializer and declare the variable
with its type only (e.g., change "let mut saved_errno = Errno::UnknownErrno;" to
"let mut saved_errno: Errno;") so the initial assignment is not emitted and
clippy's unused_assignments warning is resolved; make the same change for the
other occurrences of saved_errno in multiprocessing.rs (the other mentioned
spots) so each is declared with a type but without the unused default value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/stdlib/src/multiprocessing.rs`:
- Line 714: The local variable saved_errno is being assigned Errno::UnknownErrno
but that value is always overwritten before use; remove the dead initializer and
declare the variable with its type only (e.g., change "let mut saved_errno =
Errno::UnknownErrno;" to "let mut saved_errno: Errno;") so the initial
assignment is not emitted and clippy's unused_assignments warning is resolved;
make the same change for the other occurrences of saved_errno in
multiprocessing.rs (the other mentioned spots) so each is declared with a type
but without the unused default value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: cbadff0b-b4e7-40e7-a2e2-3a77e7645a7b

📥 Commits

Reviewing files that changed from the base of the PR and between bb5fa6b and cd304a5.

📒 Files selected for processing (1)
  • crates/stdlib/src/multiprocessing.rs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/stdlib/src/multiprocessing.rs (1)

783-795: Consider extracting the duplicated sem_wait closure pattern.

The vm.allow_threads closure wrapping sem_wait with errno capture (lines 783-793) is identical to the non-Apple version (lines 733-744). This could be extracted into a helper function to reduce duplication:

♻️ Suggested helper extraction
fn sem_wait_with_errno(
    vm: &VirtualMachine,
    sem_ptr: *mut sem_t,
) -> (libc::c_int, Errno) {
    vm.allow_threads(|| {
        let r = unsafe { libc::sem_wait(sem_ptr) };
        (r, if r < 0 { Errno::last() } else { Errno::from_raw(0) })
    })
}

Then both paths would call:

let (r, e) = sem_wait_with_errno(vm, sem_ptr);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/stdlib/src/multiprocessing.rs` around lines 783 - 795, Extract the
duplicated vm.allow_threads closure that calls libc::sem_wait and captures errno
into a helper function (e.g., fn sem_wait_with_errno(vm: &VirtualMachine,
sem_ptr: *mut sem_t) -> (libc::c_int, Errno)) and replace both occurrences with
let (r, e) = sem_wait_with_errno(vm, sem_ptr); so the logic inside
sem_wait_with_errno performs the unsafe libc::sem_wait(sem_ptr) call and returns
(r, if r < 0 { Errno::last() } else { Errno::from_raw(0) }) to eliminate the
duplicated closure in multiprocessing.rs while preserving VirtualMachine,
sem_ptr, and Errno behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/stdlib/src/multiprocessing.rs`:
- Around line 783-795: Extract the duplicated vm.allow_threads closure that
calls libc::sem_wait and captures errno into a helper function (e.g., fn
sem_wait_with_errno(vm: &VirtualMachine, sem_ptr: *mut sem_t) -> (libc::c_int,
Errno)) and replace both occurrences with let (r, e) = sem_wait_with_errno(vm,
sem_ptr); so the logic inside sem_wait_with_errno performs the unsafe
libc::sem_wait(sem_ptr) call and returns (r, if r < 0 { Errno::last() } else {
Errno::from_raw(0) }) to eliminate the duplicated closure in multiprocessing.rs
while preserving VirtualMachine, sem_ptr, and Errno behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2bf61cbb-6a55-4009-b155-70cbf3eb6c08

📥 Commits

Reviewing files that changed from the base of the PR and between cd304a5 and 8ec74ef.

📒 Files selected for processing (2)
  • crates/stdlib/src/multiprocessing.rs
  • crates/vm/src/builtins/tuple.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/vm/src/builtins/tuple.rs

allow_threads may call attach_thread() on return, which
can invoke syscalls that clobber errno. Capture errno
inside the closure before it is lost.
@youknowone youknowone merged commit 45d93f4 into RustPython:main Mar 10, 2026
13 checks passed
@youknowone youknowone deleted the multiprocessing branch March 10, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

X Tutup