Skip to content

Commit 4a16d45

Browse files
committed
Report Windows signal wakeup socket errors [skip ci]
Capture Winsock send failures without allocation in the OS signal handler. Queue raw errors in bounded atomic storage, honor warn_on_full_buffer for WSAEWOULDBLOCK, and report through sys.unraisablehook on the main interpreter thread before invoking Python signal handlers. Keep Unix/WASI notification and Windows CRT wakeup writes unchanged. This socket-focused extraction is independent of the separate Unix wakeup-reporting candidate. Hosted Windows baseline proof: CPython 3.14.7 passed the full signal snippet; RustPython failed the intended first missing-unraisable assertion. Job: https://github.com/RustPython/RustPython/actions/runs/37934852290/job/113834235711 Normal hooks and independent source review passed. Local compilation is unavailable during the shared disk/build pause; patched native Windows execution remains pending ordinary PR CI. Assisted-by: OpenAI Codex:model identifier unavailable
1 parent c009082 commit 4a16d45

3 files changed

Lines changed: 96 additions & 10 deletions

File tree

‎crates/host_env/src/signal.rs‎

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,13 +395,28 @@ pub fn wakeup_fd_is_socket(fd: libc::SOCKET) -> io::Result<bool> {
395395
Ok(false)
396396
}
397397

398+
#[cfg(windows)]
399+
pub struct WakeupSendError(pub i32);
400+
401+
#[cfg(windows)]
402+
impl WakeupSendError {
403+
pub fn is_would_block(&self) -> bool {
404+
self.0 == windows_sys::Win32::Networking::WinSock::WSAEWOULDBLOCK
405+
}
406+
407+
// Only convert to an allocating error outside the OS signal handler.
408+
pub fn into_io_error(self) -> io::Error {
409+
io::Error::from_raw_os_error(self.0)
410+
}
411+
}
412+
398413
#[cfg(windows)]
399414
pub fn notify_signal(
400415
signum: i32,
401416
wakeup_fd: libc::SOCKET,
402417
wakeup_is_socket: bool,
403418
sigint_event: Option<isize>,
404-
) {
419+
) -> Option<WakeupSendError> {
405420
if signum == libc::SIGINT
406421
&& let Some(handle) = sigint_event
407422
{
@@ -411,23 +426,31 @@ pub fn notify_signal(
411426
}
412427

413428
if wakeup_fd == INVALID_SOCKET {
414-
return;
429+
return None;
415430
}
416431

417432
let sigbyte = signum as u8;
418433
if wakeup_is_socket {
419-
unsafe {
420-
let _ = windows_sys::Win32::Networking::WinSock::send(
434+
let result = unsafe {
435+
windows_sys::Win32::Networking::WinSock::send(
421436
wakeup_fd,
422437
&sigbyte as *const u8 as *const _,
423438
1,
424439
0,
425-
);
440+
)
441+
};
442+
if result < 0 {
443+
Some(WakeupSendError(unsafe {
444+
windows_sys::Win32::Networking::WinSock::WSAGetLastError()
445+
}))
446+
} else {
447+
None
426448
}
427449
} else {
428450
unsafe {
429451
let _ = libc::write(wakeup_fd as _, &sigbyte as *const u8 as *const _, 1);
430452
}
453+
None
431454
}
432455
}
433456

‎crates/vm/src/signal.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
103103
}
104104
let _guard = SignalHandlerGuard;
105105

106+
#[cfg(all(feature = "host_env", windows))]
107+
if vm.is_main_thread() && vm.state.is_main_interpreter() {
108+
// Report before a Python signal handler can raise out of the current
109+
// error-reporting scope.
110+
crate::stdlib::_signal::_signal::report_wakeup_send_errors(vm);
111+
} else if crate::stdlib::_signal::_signal::wakeup_send_errors_pending() {
112+
// A different interpreter/thread must leave the report for the main one.
113+
set_triggered();
114+
}
115+
106116
let signal_handlers = vm
107117
.signal_handlers
108118
.get()

‎crates/vm/src/stdlib/_signal.rs‎

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ pub(crate) mod _signal {
7373
}
7474
}
7575

76+
#[cfg(windows)]
77+
static WARN_ON_FULL_BUFFER: atomic::AtomicBool = atomic::AtomicBool::new(true);
78+
79+
// Bounded pending-call storage: signal handlers cannot allocate or lock.
80+
#[cfg(windows)]
81+
static WAKEUP_SEND_ERRORS: [atomic::AtomicI32; 32] = [const { atomic::AtomicI32::new(0) }; 32];
82+
7683
#[cfg(any(unix, windows, target_os = "wasi"))]
7784
#[allow(unused_imports)]
7885
pub use host_signal::SIG_ERR;
@@ -344,7 +351,7 @@ pub(crate) mod _signal {
344351

345352
#[pyfunction]
346353
fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult<i64> {
347-
// TODO: implement warn_on_full_buffer
354+
// TODO: implement warn_on_full_buffer for the remaining wakeup write paths.
348355
let _ = args.warn_on_full_buffer;
349356
let fd = cfg_select! {
350357
windows => args.fd.0,
@@ -383,7 +390,10 @@ pub(crate) mod _signal {
383390
let old_fd = WAKEUP.swap(fd, Ordering::Relaxed);
384391

385392
#[cfg(windows)]
386-
WAKEUP_IS_SOCKET.store(is_socket, Ordering::Relaxed);
393+
{
394+
WARN_ON_FULL_BUFFER.store(args.warn_on_full_buffer, Ordering::Relaxed);
395+
WAKEUP_IS_SOCKET.store(is_socket, Ordering::Relaxed);
396+
}
387397

388398
#[cfg(windows)]
389399
if old_fd == INVALID_WAKEUP {
@@ -532,14 +542,57 @@ pub(crate) mod _signal {
532542
signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed);
533543
signal::set_triggered();
534544

535-
host_signal::notify_signal(
545+
#[cfg(not(windows))]
546+
host_signal::notify_signal(signum, WAKEUP.load(Ordering::Relaxed));
547+
548+
#[cfg(windows)]
549+
let error = host_signal::notify_signal(
536550
signum,
537551
WAKEUP.load(Ordering::Relaxed),
538-
#[cfg(windows)]
539552
WAKEUP_IS_SOCKET.load(Ordering::Relaxed),
540-
#[cfg(windows)]
541553
signal::get_sigint_event(),
542554
);
555+
#[cfg(windows)]
556+
if let Some(error) = error
557+
&& (WARN_ON_FULL_BUFFER.load(Ordering::Relaxed) || !error.is_would_block())
558+
{
559+
for pending in &WAKEUP_SEND_ERRORS {
560+
if pending
561+
.compare_exchange(0, error.0, Ordering::Relaxed, Ordering::Relaxed)
562+
.is_ok()
563+
{
564+
// The VM may have handled the signal on another thread
565+
// before the failed wakeup send completed.
566+
signal::set_triggered();
567+
break;
568+
}
569+
}
570+
}
571+
}
572+
573+
#[cfg(windows)]
574+
pub(crate) fn wakeup_send_errors_pending() -> bool {
575+
WAKEUP_SEND_ERRORS
576+
.iter()
577+
.any(|error| error.load(Ordering::Relaxed) != 0)
578+
}
579+
580+
#[cfg(windows)]
581+
pub(crate) fn report_wakeup_send_errors(vm: &VirtualMachine) {
582+
for pending in &WAKEUP_SEND_ERRORS {
583+
let error = pending.swap(0, Ordering::Relaxed);
584+
if error != 0 {
585+
vm.run_unraisable(
586+
host_signal::WakeupSendError(error)
587+
.into_io_error()
588+
.into_pyexception(vm),
589+
Some(
590+
"Exception ignored while trying to send to the signal wakeup fd".to_owned(),
591+
),
592+
vm.ctx.none(),
593+
);
594+
}
595+
}
543596
}
544597

545598
/// Reset wakeup fd after fork in child process.

0 commit comments

Comments
 (0)