Kill background jobs on interrupt (#16285)

# Description
This PR kills all background jobs on interrupt, as a fix for
https://github.com/nushell/nushell/issues/15947.

# User-Facing Changes
If you run the following: `job spawn { print "job spawned"; ^sleep
infinity }; ^sleep infinity`, then hit ctrl-c, the current behavior is
that the `sleep` process from the job will not be killed, it will
reparented to init. With this change, the process will be killed on
ctrl-c.

# Tests + Formatting
I was unsure of the best way to write a test for this.

# After Submitting

---------

Co-authored-by: 132ikl <132@ikl.sh>
This commit is contained in:
David Yamnitsky
2025-08-13 10:11:40 -07:00
committed by GitHub
parent 7133a04e2f
commit 31606a8fe1
2 changed files with 28 additions and 0 deletions

View File

@ -68,6 +68,19 @@ impl Handlers {
}) })
} }
/// Registers a new handler which persists for the entire process lifetime.
///
/// Only use this for handlers which should exist for the lifetime of the program.
/// You should prefer to use `register` with a `HandlerGuard` when possible.
pub fn register_unguarded(&self, handler: Handler) -> Result<(), ShellError> {
let id = self.next_id.next()?;
if let Ok(mut handlers) = self.handlers.lock() {
handlers.push((id, handler));
}
Ok(())
}
/// Runs all registered handlers. /// Runs all registered handlers.
pub fn run(&self, action: SignalAction) { pub fn run(&self, action: SignalAction) {
if let Ok(handlers) = self.handlers.lock() { if let Ok(handlers) = self.handlers.lock() {

View File

@ -9,6 +9,21 @@ pub(crate) fn ctrlc_protection(engine_state: &mut EngineState) {
engine_state.set_signals(Signals::new(interrupt.clone())); engine_state.set_signals(Signals::new(interrupt.clone()));
let signal_handlers = Handlers::new(); let signal_handlers = Handlers::new();
// Register a handler to kill all background jobs on interrupt.
signal_handlers
.register_unguarded({
let jobs = engine_state.jobs.clone();
Box::new(move |action| {
if action == SignalAction::Interrupt {
if let Ok(mut jobs) = jobs.lock() {
let _ = jobs.kill_all();
}
}
})
})
.expect("Failed to register interrupt signal handler");
engine_state.signal_handlers = Some(signal_handlers.clone()); engine_state.signal_handlers = Some(signal_handlers.clone());
ctrlc::set_handler(move || { ctrlc::set_handler(move || {