# Description

This is an attempt to improve the nushell situation with regard to issue
#247.

This PR implements:
- [X] spawning jobs: `job spawn { do_background_thing }`
Jobs will be implemented as threads and not forks, to maintain a
consistent behavior between unix and windows.

- [X] listing running jobs: `job list`
This should allow users to list what background tasks they currently
have running.

- [X] killing jobs: `job kill <id>`
- [X] interupting nushell code in the job's background thread
- [X] interrupting the job's currently-running process, if any.

Things that should be taken into consideration for implementation:
- [X] (unix-only) Handling `TSTP` signals while executing code and
turning the current program into a background job, and unfreezing them
in foreground `job unfreeze`.

- [X] Ensuring processes spawned by background jobs get distinct process
groups from the nushell shell itself

This PR originally aimed to implement some of the following, but it is
probably ideal to be left for another PR (scope creep)
- Disowning external process jobs (`job dispatch`)
- Inter job communication (`job send/recv`)

Roadblocks encountered so far:
- Nushell does some weird terminal sequence magics which make so that
when a background process or thread prints something to stderr and the
prompt is idle, the stderr output ends up showing up weirdly
This commit is contained in:
Renan Ribeiro
2025-02-25 14:09:52 -03:00
committed by GitHub
parent 9521b209d1
commit 9bb7f0c7dc
26 changed files with 1391 additions and 163 deletions

View File

@ -1,5 +1,6 @@
use nu_engine::command_prelude::*;
use std::process::{Command as CommandSys, Stdio};
use nu_system::build_kill_command;
use std::process::Stdio;
#[derive(Clone)]
pub struct Kill;
@ -56,70 +57,36 @@ impl Command for Kill {
let signal: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "signal")?;
let quiet: bool = call.has_flag(engine_state, stack, "quiet")?;
let mut cmd = if cfg!(windows) {
let mut cmd = CommandSys::new("taskkill");
if force {
cmd.arg("/F");
}
cmd.arg("/PID");
cmd.arg(pid.to_string());
// each pid must written as `/PID 0` otherwise
// taskkill will act as `killall` unix command
for id in &rest {
cmd.arg("/PID");
cmd.arg(id.to_string());
}
cmd
} else {
let mut cmd = CommandSys::new("kill");
if force {
if let Some(Spanned {
if cfg!(unix) {
if let (
true,
Some(Spanned {
item: _,
span: signal_span,
}) = signal
{
return Err(ShellError::IncompatibleParameters {
left_message: "force".to_string(),
left_span: call.get_flag_span(stack, "force").ok_or_else(|| {
ShellError::GenericError {
error: "Flag error".into(),
msg: "flag force not found".into(),
span: Some(call.head),
help: None,
inner: vec![],
}
})?,
right_message: "signal".to_string(),
right_span: Span::merge(
call.get_flag_span(stack, "signal").ok_or_else(|| {
ShellError::GenericError {
error: "Flag error".into(),
msg: "flag signal not found".into(),
span: Some(call.head),
help: None,
inner: vec![],
}
})?,
signal_span,
),
});
}
cmd.arg("-9");
} else if let Some(signal_value) = signal {
cmd.arg(format!("-{}", signal_value.item));
}),
) = (force, signal)
{
return Err(ShellError::IncompatibleParameters {
left_message: "force".to_string(),
left_span: call
.get_flag_span(stack, "force")
.expect("Had flag force, but didn't have span for flag"),
right_message: "signal".to_string(),
right_span: Span::merge(
call.get_flag_span(stack, "signal")
.expect("Had flag signal, but didn't have span for flag"),
signal_span,
),
});
}
cmd.arg(pid.to_string());
cmd.args(rest.iter().map(move |id| id.to_string()));
cmd
};
let mut cmd = build_kill_command(
force,
std::iter::once(pid).chain(rest),
signal.map(|spanned| spanned.item as u32),
);
// pipe everything to null
if quiet {
cmd.stdin(Stdio::null())