config nu now adds frozen jobs to job table

Description:

`config nu` used to ignore the frozen wait job status response and did not
add processes to the job table when they were frozen.

This PR refactors the PostWaitCallback used in run_external and
allows frozen processes spawned by `config_.rs` to be added
to the job table.

Closes #15389
This commit is contained in:
Renan Ribeiro 2025-04-13 00:03:47 -03:00
parent 885b87a842
commit 45cb55438e
3 changed files with 59 additions and 23 deletions

View File

@ -1,5 +1,6 @@
use nu_cmd_base::util::get_editor;
use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
use nu_protocol::{process::PostWaitCallback, shell_error::io::IoError};
use nu_system::ForegroundChild;
#[derive(Clone)]
@ -61,7 +62,6 @@ pub(super) fn start_editor(
) -> Result<PipelineData, ShellError> {
// Find the editor executable.
use nu_protocol::shell_error::io::IoError;
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
@ -119,8 +119,17 @@ pub(super) fn start_editor(
)
})?;
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None);
// Wrap the output into a `PipelineData::ByteStream`.
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head, None)?;
let child = nu_protocol::process::ChildProcess::new(
child,
None,
false,
call.head,
Some(post_wait_callback),
)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,

View File

@ -3,12 +3,11 @@ use nu_engine::{command_prelude::*, env_to_strings};
use nu_path::{dots::expand_ndots_safe, expand_tilde, AbsolutePath};
use nu_protocol::{
did_you_mean,
engine::{FrozenJob, Job},
process::{ChildProcess, PostWaitCallback},
shell_error::io::IoError,
ByteStream, NuGlob, OutDest, Signals, UseAnsiColoring,
};
use nu_system::{kill_by_pid, ForegroundChild, ForegroundWaitStatus};
use nu_system::{kill_by_pid, ForegroundChild};
use nu_utils::IgnoreCaseExt;
use pathdiff::diff_paths;
#[cfg(windows)]
@ -312,9 +311,6 @@ impl Command for External {
})?;
}
let jobs = engine_state.jobs.clone();
let this_job = engine_state.current_thread_job.clone();
let is_interactive = engine_state.is_interactive;
let child_pid = child.pid();
// Wrap the output into a `PipelineData::ByteStream`.
@ -323,21 +319,10 @@ impl Command for External {
merged_stream,
matches!(stderr, OutDest::Pipe),
call.head,
// handle wait statuses for job control
Some(PostWaitCallback(Box::new(move |status| {
if let Some(this_job) = this_job {
this_job.remove_pid(child_pid);
}
if let ForegroundWaitStatus::Frozen(unfreeze) = status {
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
let job_id = jobs.add_job(Job::Frozen(FrozenJob { unfreeze }));
if is_interactive {
println!("\nJob {} is frozen", job_id.get());
}
}
}))),
Some(PostWaitCallback::for_job_control(
engine_state,
Some(child_pid),
)),
)?;
if matches!(stdout, OutDest::Pipe | OutDest::PipeSeparate)

View File

@ -1,4 +1,9 @@
use crate::{byte_stream::convert_file, shell_error::io::IoError, ShellError, Span};
use crate::{
byte_stream::convert_file,
engine::{EngineState, FrozenJob, Job},
shell_error::io::IoError,
ShellError, Span,
};
use nu_system::{ExitStatus, ForegroundChild, ForegroundWaitStatus};
use os_pipe::PipeReader;
@ -166,8 +171,45 @@ pub struct ChildProcess {
span: Span,
}
/// A wrapper for a closure that runs once the shell finishes waiting on the process.
pub struct PostWaitCallback(pub Box<dyn FnOnce(ForegroundWaitStatus) + Send>);
impl PostWaitCallback {
pub fn new<F>(f: F) -> Self
where
F: FnOnce(ForegroundWaitStatus) + Send + 'static,
{
PostWaitCallback(Box::new(f))
}
/// Creates a PostWaitCallback that checks creates a frozen job in the job table
/// if the incoming wait status indicates that the job was frozen.
///
/// If `child_pid` is provided, the returned callback will also remove
/// it from the pid list of the current running job.
pub fn for_job_control(engine_state: &EngineState, child_pid: Option<u32>) -> Self {
let this_job = engine_state.current_thread_job.clone();
let jobs = engine_state.jobs.clone();
let is_interactive = engine_state.is_interactive;
PostWaitCallback::new(move |status| {
if let (Some(this_job), Some(child_pid)) = (this_job, child_pid) {
this_job.remove_pid(child_pid);
}
if let ForegroundWaitStatus::Frozen(unfreeze) = status {
let mut jobs = jobs.lock().expect("jobs lock is poisoned!");
let job_id = jobs.add_job(Job::Frozen(FrozenJob { unfreeze }));
if is_interactive {
println!("\nJob {} is frozen", job_id.get());
}
}
})
}
}
impl Debug for PostWaitCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<wait_callback>")