Skip to content

Commit fe582f2

Browse files
committed
Preserve signal startup dispositions and callback frames
Keep ignored and foreign native SIGINT handlers during initialization and release replaced Python handlers outside the table borrow. Share exact native disposition classification and pass the current complete frame to normal signal callback dispatch, retaining errors when a registered object ceases to be callable. No signal ownership, shutdown, or interrupt_main producer redesign is included. Existing expected-failure markers remain pending runtime tests. Assisted-by: OpenAI Codex:model version not exposed
1 parent c632c81 commit fe582f2

2 files changed

Lines changed: 48 additions & 19 deletions

File tree

‎crates/vm/src/signal.rs‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
140140
.signal_handlers
141141
.get()
142142
.expect("should never fail since we check above");
143+
let frame = crate::frame::current_complete_frame_for_trace(vm);
143144

144145
for (signum, trigger) in TRIGGERS.iter().enumerate().skip(1) {
145146
let triggered = trigger.swap(false, Ordering::Relaxed);
@@ -155,10 +156,19 @@ fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
155156
// mutably, and a live read borrow turns that into a panic.
156157
let handler = signal_handlers.borrow()[signum].clone();
157158

158-
if let Some(handler) = handler
159-
&& let Some(callable) = handler.to_callable()
160-
&& let Err(exc) = callable.invoke((signum.as_i32(), vm.ctx.none()), vm)
161-
{
159+
let Some(handler) = handler.filter(|handler| !vm.is_none(handler)) else {
160+
continue;
161+
};
162+
#[cfg(feature = "host_env")]
163+
if crate::stdlib::_signal::_signal::handler_disposition(&handler, vm).is_some() {
164+
continue;
165+
}
166+
167+
// A handler can cease to be callable after registration. Invoke it
168+
// normally so that this raises TypeError instead of losing a signal.
169+
// Keep the interrupted complete frame alive across every callback,
170+
// including handlers which re-enter signal dispatch.
171+
if let Err(exc) = handler.call((signum.as_i32(), frame.clone()), vm) {
162172
// On error, re-schedule a call to trigger_signals() for the
163173
// remaining signals and the pending wakeup errors.
164174
set_triggered();

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

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub(crate) mod _signal {
1515

1616
cfg_select! {
1717
any(unix, windows, target_os = "wasi") => {
18-
use crate::{builtins::PyInt, convert::IntoPyException};
18+
use crate::convert::IntoPyException;
1919
use rustpython_host_env::signal as host_signal;
2020
}
2121
_ => {}
@@ -204,20 +204,42 @@ pub(crate) mod _signal {
204204
// SAFETY: Trust `SignalNum::VALID_RANGE`
205205
let signum = unsafe { SignalNum::new_unchecked(signum) };
206206

207-
vm.signal_handlers
208-
.get_or_init(SignalHandlers::default)
209-
.borrow_mut()[signum] = py_handler;
207+
let signal_handlers = vm.signal_handlers.get_or_init(SignalHandlers::default);
208+
let old_handler =
209+
core::mem::replace(&mut signal_handlers.borrow_mut()[signum], py_handler);
210+
// Replacing a handler can run its destructor, which may install
211+
// another handler. Release the table borrow before dropping it.
212+
drop(old_handler);
210213
}
211214

212-
let int_handler = module
213-
.get_attr("default_int_handler", vm)
214-
.expect("_signal does not have this attr?");
215-
216-
signal(SignalNum::SIGINT.into(), int_handler, vm)
217-
.expect("Failed to set sigint handler");
215+
let default_sigint = vm
216+
.signal_handlers
217+
.get()
218+
.and_then(|handlers| handlers.borrow()[SignalNum::SIGINT].clone())
219+
.is_some_and(|handler| handler_disposition(&handler, vm) == Some(SIG_DFL));
220+
if default_sigint {
221+
let int_handler = module
222+
.get_attr("default_int_handler", vm)
223+
.expect("_signal does not have this attr?");
224+
225+
// An inherited ignored or foreign native handler is not ours
226+
// to replace, even when installing Python's default handlers.
227+
signal(SignalNum::SIGINT.into(), int_handler, vm)
228+
.expect("Failed to set sigint handler");
229+
}
218230
}
219231
}
220232

233+
pub(crate) fn handler_disposition(
234+
handler: &crate::PyObject,
235+
vm: &VirtualMachine,
236+
) -> Option<sighandler_t> {
237+
handler
238+
.downcast_ref_if_exact::<crate::builtins::PyInt>(vm)
239+
.and_then(|value| usize::try_from(value.as_bigint()).ok())
240+
.filter(|&value| matches!(value, SIG_DFL | SIG_IGN))
241+
}
242+
221243
#[cfg(not(any(unix, windows, target_os = "wasi")))]
222244
#[pyfunction]
223245
pub fn signal(
@@ -261,11 +283,8 @@ pub(crate) mod _signal {
261283
const MSG: &str =
262284
"signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object";
263285

264-
let disposition = handler
265-
.downcast_ref_if_exact::<PyInt>(vm)
266-
.and_then(|value| value.try_to_primitive::<usize>(vm).ok())
267-
.filter(|&v| matches!(v, SIG_DFL | SIG_IGN))
268-
.ok_or_else(|| vm.new_type_error(MSG))?;
286+
let disposition =
287+
handler_disposition(&handler, vm).ok_or_else(|| vm.new_type_error(MSG))?;
269288
cfg_select! {
270289
target_os = "wasi" => {
271290
if disposition == SIG_DFL {

0 commit comments

Comments
 (0)