Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Feb 4, 2026

Summary by CodeRabbit

  • Improvements
    • Better Windows signal handling so SIGINT now notifies native event objects for smoother interrupts.
    • Improved asynchronous I/O on Windows with richer result reporting, more accurate completion/pending state, and more robust cancelation behavior.
    • Enhanced error signaling and interrupt handling in batched/wait operations for more reliable multi-threaded behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Adds Windows-only SIGINT event storage and getter/setter, integrates SIGINT event signaling into signal delivery on Windows, and substantially refactors Windows stdlib winapi overlapped I/O, event creation, and wait APIs to return richer result shapes and handle more error cases.

Changes

Cohort / File(s) Summary
SIGINT Event Mechanism
crates/vm/src/signal.rs, crates/vm/src/stdlib/signal.rs
Adds a Windows-only atomic SIGINT event handle plus set_sigint_event()/get_sigint_event() APIs; run_signal now sets the Windows event for SIGINT (if available) before existing wakeup_fd handling.
Windows API Refactoring (overlapped, events, waits)
crates/vm/src/stdlib/winapi.rs
Major refactor: Overlapped gains write_buffer; GetOverlappedResult now returns (u32,u32) and handles additional error codes; getbuffer/cancel semantics tightened; CreateEventW returns Option<WinHandle>; WriteFile, batched waits, and wait functions now use PyObjectRef for richer signaling and integrate SIGINT/thread-interrupt handling.

Sequence Diagram

sequenceDiagram
    participant Handler as Signal Handler
    participant SigMod as crates::vm::signal
    participant WinAPI as stdlib::winapi
    participant WakeFD as wakeup_fd

    Handler->>SigMod: deliver(SIGINT)
    SigMod->>SigMod: run_signal logic
    SigMod->>SigMod: get_sigint_event()
    alt event handle exists
        SigMod->>WinAPI: SetEvent(handle)
        WinAPI-->>SigMod: success/err
    end
    SigMod->>WakeFD: write wakeup byte (if configured)
    WakeFD-->>SigMod: acknowledged
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Suggested Reviewers

  • ShaharNaveh

Poem

🐰 I dug a hole through Windows code,

SIGINT found a comfy node,
Overlapped naps now wake with care,
Events and waits all dance in pair,
Hopping bytes and errors shared.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Align winapi with CPython behavior' is vague and generic, using non-descriptive language that doesn't convey meaningful information about the specific changes in the changeset. Consider a more specific title that describes the actual changes, such as 'Add Windows signal event support and enhance overlapped I/O error handling' or 'Implement Windows SIGINT event handling and improve winapi error semantics'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin winapi

@youknowone youknowone marked this pull request as ready for review February 4, 2026 04:07
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

🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/winapi.rs`:
- Around line 1210-1218: The error handling around CreateEventW is wrong:
replace the check for INVALID_HANDLE_VALUE with a null check first — if
handle.is_null() return Err(vm.new_last_os_error()); otherwise wrap the non-null
handle in Ok(Some(WinHandle(handle))). Remove the INVALID_HANDLE_VALUE branch
entirely so we don’t silently return Ok(None); ensure the return sequence in the
function that constructs WinHandle uses is_null() first and only returns
Ok(Some(...)) for non-null handles.
🧹 Nitpick comments (2)
crates/vm/src/stdlib/winapi.rs (2)

1369-1386: Potential race condition on SIGINT event initialization.

The get_sigint_event().unwrap_or_else(...) pattern at lines 1373-1379 can race: if two threads call BatchedWaitForMultipleObjects simultaneously on first use, both might create separate events, and only one will be stored via set_sigint_event. The other event handle would leak.

However, since this only runs on the main thread (checked at line 1371-1372), this race is unlikely in practice. Consider using an atomic compare-and-swap pattern for robustness if this becomes a concern.


1374-1378: CreateEventW failure not checked before calling set_sigint_event.

If CreateEventW returns NULL (failure), this code stores 0 via set_sigint_event(0). While the subsequent if handle == 0 { None } check at line 1380 handles this, it silently swallows the error. Consider logging or at least not calling set_sigint_event on failure:

♻️ Proposed improvement
                 let handle = crate::signal::get_sigint_event().unwrap_or_else(|| {
                     let handle = unsafe { WinCreateEventW(null(), 1, 0, null()) };
-                    if !handle.is_null() {
-                        crate::signal::set_sigint_event(handle as isize);
+                    if handle.is_null() {
+                        return 0;
                     }
+                    crate::signal::set_sigint_event(handle as isize);
                     handle as isize
                 });

Comment on lines +1210 to +1218
if handle == INVALID_HANDLE_VALUE {
return Err(vm.new_last_os_error());
}

Ok(WinHandle(handle))
if handle.is_null() {
return Ok(None);
}

Ok(Some(WinHandle(handle)))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Windows API CreateEventW return value failure NULL documentation

💡 Result:

CreateEventW returns a HANDLE.

  • Success: returns a non-NULL handle to the event object. If the named event already existed, it still returns a valid handle, and GetLastError() returns ERROR_ALREADY_EXISTS. [1]
  • Failure: returns NULL. For extended error information, call GetLastError() (immediately after the failure). [1]

Documentation: Microsoft Learn “CreateEventW function (synchapi.h)”. [1]

Sources
[1] Microsoft Learn: CreateEventW function (synchapi.h) – Win32 apps, return value section: (learn.microsoft.com)

Citations:


🏁 Script executed:

# Let's examine the actual code in the file
cd crates/vm/src/stdlib && head -n 1230 winapi.rs | tail -n 50

Repository: RustPython/RustPython

Length of output: 1527


Fix CreateEventW error handling: it returns NULL on failure, not INVALID_HANDLE_VALUE.

The CreateEventW Windows API returns NULL on failure (documented in Microsoft Learn), not INVALID_HANDLE_VALUE. The current code incorrectly checks for INVALID_HANDLE_VALUE (which CreateEventW never returns) and then silently returns Ok(None) when the handle is null, losing the error information. The correct logic should check is_null() first and return an error:

Fix
-        if handle == INVALID_HANDLE_VALUE {
+        if handle.is_null() {
             return Err(vm.new_last_os_error());
         }

-        if handle.is_null() {
-            return Ok(None);
-        }
-
         Ok(Some(WinHandle(handle)))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if handle == INVALID_HANDLE_VALUE {
return Err(vm.new_last_os_error());
}
Ok(WinHandle(handle))
if handle.is_null() {
return Ok(None);
}
Ok(Some(WinHandle(handle)))
if handle.is_null() {
return Err(vm.new_last_os_error());
}
Ok(Some(WinHandle(handle)))
🤖 Prompt for AI Agents
In `@crates/vm/src/stdlib/winapi.rs` around lines 1210 - 1218, The error handling
around CreateEventW is wrong: replace the check for INVALID_HANDLE_VALUE with a
null check first — if handle.is_null() return Err(vm.new_last_os_error());
otherwise wrap the non-null handle in Ok(Some(WinHandle(handle))). Remove the
INVALID_HANDLE_VALUE branch entirely so we don’t silently return Ok(None);
ensure the return sequence in the function that constructs WinHandle uses
is_null() first and only returns Ok(Some(...)) for non-null handles.

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/vm/src/stdlib/winapi.rs (2)

1555-1577: ⚠️ Potential issue | 🟠 Major

Fix off-by-one error in SIGINT event detection.

WaitForMultipleObjects returns WAIT_OBJECT_0 + index where index is 0-based. Since the sigint_event is appended as the last item at index len - 1, the condition should compare against WAIT_OBJECT_0 + (len - 1), not WAIT_OBJECT_0 + len. The current code will never detect the interrupt.

Proposed fix
-            } else if sigint_event.is_some()
-                && result == WAIT_OBJECT_0 + thread_handles_raw.len() as u32
+            } else if sigint_event.is_some()
+                && result == WAIT_OBJECT_0 + (thread_handles_raw.len() as u32 - 1)
             {
                 Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT)
             } else {

1010-1035: ⚠️ Potential issue | 🟠 Major

Handle ConnectNamedPipe success in overlapped mode; current code ignores the return value.

For overlapped ConnectNamedPipe, a nonzero return indicates immediate success and requires signaling the event. The current code discards the return value and unconditionally calls GetLastError(), which treats successful synchronous completion as an error. The synchronous path (lines 45–50) correctly checks the return value first; apply the same pattern to the overlapped path.

Proposed fix
-            let _ret = {
+            let ret = {
                 let mut inner = ov.inner.lock();
                 unsafe {
                     windows_sys::Win32::System::Pipes::ConnectNamedPipe(
                         handle.0,
                         &mut inner.overlapped,
                     )
                 }
             };
 
-            let err = unsafe { GetLastError() };
-            match err {
-                ERROR_IO_PENDING => {
-                    let mut inner = ov.inner.lock();
-                    inner.pending = true;
-                }
-                ERROR_PIPE_CONNECTED => {
-                    let inner = ov.inner.lock();
-                    unsafe {
-                        windows_sys::Win32::System::Threading::SetEvent(inner.overlapped.hEvent);
-                    }
-                }
-                _ => {
-                    return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm));
-                }
-            }
+            if ret != 0 {
+                let inner = ov.inner.lock();
+                unsafe {
+                    windows_sys::Win32::System::Threading::SetEvent(inner.overlapped.hEvent);
+                }
+            } else {
+                let err = unsafe { GetLastError() };
+                match err {
+                    ERROR_IO_PENDING => {
+                        let mut inner = ov.inner.lock();
+                        inner.pending = true;
+                    }
+                    ERROR_PIPE_CONNECTED => {
+                        let inner = ov.inner.lock();
+                        unsafe {
+                            windows_sys::Win32::System::Threading::SetEvent(
+                                inner.overlapped.hEvent
+                            );
+                        }
+                    }
+                    _ => {
+                        return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm));
+                    }
+                }
+            }

@youknowone youknowone merged commit 5662fa0 into RustPython:main Feb 4, 2026
13 checks passed
@youknowone youknowone deleted the winapi branch February 4, 2026 06:02
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