Skip to main content

ProcessHandle

Struct ProcessHandle 

Source
pub struct ProcessHandle<Stdout, Stderr = Stdout>
where Stdout: OutputStream, Stderr: OutputStream,
{ /* private fields */ }
Expand description

A handle to a spawned process with captured stdout/stderr streams.

This type provides methods for waiting on process completion, terminating the process, and accessing its output streams. By default, processes must be explicitly waited on or terminated before being dropped (see ProcessHandle::must_be_terminated).

If applicable, a process handle can be wrapped in a crate::TerminateOnDrop to be terminated automatically upon being dropped. Note that this requires a multithreaded runtime!

Implementations§

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub fn must_be_terminated(&mut self)

Sets a panic-on-drop mechanism for this ProcessHandle.

This method enables a safeguard that ensures that the process represented by this ProcessHandle is properly terminated or awaited before being dropped. If must_be_terminated is set and the ProcessHandle is dropped without successfully terminating, killing, waiting for, or explicitly detaching the process, an intentional panic will occur to prevent silent failure-states, ensuring that system resources are handled correctly.

You typically do not need to call this, as every ProcessHandle is marked by default. Call must_not_be_terminated to clear this safeguard to explicitly allow dropping the process without terminating it. Calling this method while the safeguard is already enabled is safe and has no effect beyond keeping the handle armed.

§Panic

If the ProcessHandle is dropped without being awaited or terminated successfully after calling this method, a panic will occur with a descriptive message to inform about the incorrect usage.

Source

pub fn must_not_be_terminated(&mut self)

Disables the kill/panic-on-drop safeguards for this handle.

Dropping the handle after calling this method will no longer signal, kill, or panic. However, this does not keep the library-owned stdio pipes alive. If the child still depends on stdin, stdout, or stderr being open, dropping the handle may still affect it.

Use plain tokio::process::Command directly when you need a child process that can outlive the original handle without depending on captured stdio pipes.

Also, the right opt-out after terminate returns an unrecoverable error and the caller chooses to accept the failure instead of retrying or escalating to kill.

Source

pub fn terminate_on_drop( self, shutdown: GracefulShutdown, ) -> TerminateOnDrop<Stdout, Stderr>

Wrap this process handle in a TerminateOnDrop instance, terminating the controlled process automatically when this handle is dropped.

shutdown carries the same per-platform graceful policy as Self::terminate; see GracefulShutdown for how to construct it.

SAFETY: This only works when your code is running in a multithreaded tokio runtime!

Prefer manual termination of the process or awaiting it and relying on the (automatically configured) must_be_terminated logic, raising a panic when a process was neither awaited nor terminated before being dropped.

Source§

impl<StdoutD, Stderr> ProcessHandle<BroadcastOutputStream<StdoutD, ReplayEnabled>, Stderr>
where StdoutD: Delivery, Stderr: OutputStream,

Source

pub fn seal_stdout_replay(&self)

Seals stdout replay history for future subscribers.

Source§

impl<StdoutD, Stderr> ProcessHandle<SingleSubscriberOutputStream<StdoutD, ReplayEnabled>, Stderr>
where StdoutD: Delivery, Stderr: OutputStream,

Source

pub fn seal_stdout_replay(&self)

Seals stdout replay history for future subscribers.

Source§

impl<Stdout, StderrD> ProcessHandle<Stdout, BroadcastOutputStream<StderrD, ReplayEnabled>>
where Stdout: OutputStream, StderrD: Delivery,

Source

pub fn seal_stderr_replay(&self)

Seals stderr replay history for future subscribers.

Source§

impl<Stdout, StderrD> ProcessHandle<Stdout, SingleSubscriberOutputStream<StderrD, ReplayEnabled>>
where Stdout: OutputStream, StderrD: Delivery,

Source

pub fn seal_stderr_replay(&self)

Seals stderr replay history for future subscribers.

Source§

impl<StdoutD, StderrD> ProcessHandle<BroadcastOutputStream<StdoutD, ReplayEnabled>, BroadcastOutputStream<StderrD, ReplayEnabled>>
where StdoutD: Delivery, StderrD: Delivery,

Source

pub fn seal_output_replay(&self)

Seals stdout and stderr replay history for replay-enabled streams.

Source§

impl<StdoutD, StderrD> ProcessHandle<BroadcastOutputStream<StdoutD, ReplayEnabled>, SingleSubscriberOutputStream<StderrD, ReplayEnabled>>
where StdoutD: Delivery, StderrD: Delivery,

Source

pub fn seal_output_replay(&self)

Seals stdout and stderr replay history for replay-enabled streams.

Source§

impl<StdoutD, StderrD> ProcessHandle<SingleSubscriberOutputStream<StdoutD, ReplayEnabled>, BroadcastOutputStream<StderrD, ReplayEnabled>>
where StdoutD: Delivery, StderrD: Delivery,

Source

pub fn seal_output_replay(&self)

Seals stdout and stderr replay history for replay-enabled streams.

Source§

impl<StdoutD, StderrD> ProcessHandle<SingleSubscriberOutputStream<StdoutD, ReplayEnabled>, SingleSubscriberOutputStream<StderrD, ReplayEnabled>>
where StdoutD: Delivery, StderrD: Delivery,

Source

pub fn seal_output_replay(&self)

Seals stdout and stderr replay history for replay-enabled streams.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub fn send_interrupt_signal(&mut self) -> Result<(), TerminationError>

Manually send SIGINT to this process’s process group via killpg.

SIGINT is the dedicated user-interrupt signal, distinct from the SIGTERM delivered by Self::send_terminate_signal. The signal targets the child’s process group, so any grandchildren the child has fork-execed are signaled together with the leader.

If the process has already exited, this reaps it and returns Ok(()) instead of attempting to signal a stale PID or process group. If the signal send fails because the child exited after the preflight check, this also reaps it and returns Ok(()).

Prefer to call terminate instead, if you want to make sure this process is terminated.

This method is Unix-only because Windows has no targetable SIGINT analogue: GenerateConsoleCtrlEvent only accepts CTRL_BREAK_EVENT for nonzero process groups. On Windows, use send_ctrl_break_signal instead.

§Notes

The post-failure exit-status probe is a single synchronous try_wait. On the rare race where the child has just exited but Tokio’s SIGCHLD reaper has not yet observed it, the probe returns “still running” and this method surfaces the OS-level signal-send error (typically EPERM on macOS or ESRCH on Linux) as TerminationError::SignalFailed. The synchronous shape is deliberate so the public send_*_signal API stays non-async; reach for Self::terminate when you need the bounded SIGCHLD-grace wait.

§Errors

Returns TerminationError if the process status could not be checked or if SIGINT could not be sent.

Source

pub fn send_terminate_signal(&mut self) -> Result<(), TerminationError>

Manually send SIGTERM to this process’s process group via killpg.

SIGTERM is the conventional “asked to terminate” signal sent by service supervisors and the operating system at shutdown. The signal targets the child’s process group, so any grandchildren the child has fork-execed are signaled together with the leader.

If the process has already exited, this reaps it and returns Ok(()) instead of attempting to signal a stale PID or process group. If the signal send fails because the child exited after the preflight check, this also reaps it and returns Ok(()).

Prefer to call terminate instead, if you want to make sure this process is terminated.

This method is Unix-only because Windows has no targetable SIGTERM analogue: GenerateConsoleCtrlEvent only accepts CTRL_BREAK_EVENT for nonzero process groups. On Windows, use send_ctrl_break_signal instead.

§Notes

The post-failure exit-status probe is a single synchronous try_wait. On the rare race where the child has just exited but Tokio’s SIGCHLD reaper has not yet observed it, the probe returns “still running” and this method surfaces the OS-level signal-send error (typically EPERM on macOS or ESRCH on Linux) as TerminationError::SignalFailed. The synchronous shape is deliberate so the public send_*_signal API stays non-async; reach for Self::terminate when you need the bounded SIGCHLD-grace wait.

§Errors

Returns TerminationError if the process status could not be checked or if SIGTERM could not be sent.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub async fn kill(&mut self) -> Result<(), TerminationError>

Forces the process to exit. Most users should call ProcessHandle::terminate instead.

This is equivalent to sending SIGKILL on Unix or calling TerminateProcess on Windows, followed by a wait for the process to be reaped. On other Tokio-supported platforms it forwards to tokio::process::Child::start_kill.

Any still-open stdin handle is closed before Tokio performs that kill-and-wait sequence, matching tokio::process::Child::kill semantics. A successful call waits for the child to exit and disarms the drop cleanup and panic guards, so the handle can be dropped safely afterward.

kill is a reasonable next step when terminate returns Err and the caller is not interested in further graceful escalation.

§Errors

Returns TerminationError if Tokio cannot kill or wait for the child process.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub async fn terminate( &mut self, shutdown: GracefulShutdown, ) -> Result<ExitStatus, TerminationError>

Terminates this process by dispatching the configured graceful-shutdown sequence first, then forcefully killing the process if it has not exited after the sequence completes.

The signature is the same on every supported platform; the shape of shutdown is platform-conditional. See GracefulShutdown for how to construct one and UnixGracefulShutdown for guidance on choosing the Unix sequence.

  • On Unix the configured UnixGracefulShutdown dispatches one or more graceful signals in order; each phase’s timeout bounds how long to wait for the child to exit before escalating to the next phase. After the last configured phase, SIGKILL runs as the implicit forceful fallback.
  • On Windows this is a 2-phase termination: CTRL_BREAK_EVENT -> wait shutdown.windows.timeout -> TerminateProcess. Only one CTRL_BREAK_EVENT is ever sent. GenerateConsoleCtrlEvent can only target a child’s process group with CTRL_BREAK_EVENT (sending CTRL_C_EVENT would require dwProcessGroupId = 0 and broadcast to the parent), so a second graceful send would be the same event and cannot do more than the first send already did.

The forceful kill fallback adds one fixed 3-second wait on top of the graceful timeouts.

§Timeouts are upper bounds, not delays

Each per-phase timeout bounds the post-signal wait of its phase. The wait future resolves the instant Tokio’s SIGCHLD reaper observes the child exit, so handler-less children (children that have no handler installed for the signal we send) typically die in microseconds via the kernel’s default disposition (Term) and the configured timeout never fires for them. The timeout only matters when the child has installed a handler that takes time to complete.

§What signal should I send?

See UnixGracefulShutdown for the recommended single-signal sequences and a discussion of why mixing SIGINT and SIGTERM does not cover children with unknown signal handlers.

§Windows interop note

tokio::signal::ctrl_c() on Windows registers only for CTRL_C_EVENT; it does not catch CTRL_BREAK_EVENT. A child Rust binary that listens only on the cross-platform tokio::signal::ctrl_c() will not respond to this graceful step on Windows and will be terminated forcefully after timeout. To interoperate, such a child should additionally listen on tokio::signal::windows::ctrl_break(), or expose another shutdown channel (stdin sentinel, IPC, or a command protocol).

§Per-phase timeout semantics

Each per-phase timeout in shutdown bounds the post-signal wait of its phase:

  • Signal send succeeds: wait up to the user-supplied timeout, then escalate.
  • Signal send fails: replace the user timeout with a fixed 100 ms grace so Tokio’s reaper can catch up to a child that just exited (the OS rejects signals to a not-yet- reaped process group with EPERM on macOS or ESRCH on Linux). Real permission denials still surface as an error after the grace elapses.

Duration::from_secs(0) disables the post-signal wait entirely and effectively forces the call into the forceful kill (SIGKILL on Unix, TerminateProcess on Windows). Prefer small but non-zero values (e.g. 100 ms to a few seconds).

§Drop guards on Ok vs Err

On Ok, the drop cleanup and panic guards are disarmed and the handle can be dropped safely. On Err (or if the future is canceled), the guards stay armed: the library cannot verify cleanup from the outside, so dropping would leak a process. Recover by retrying terminate, escalating to kill, calling must_not_be_terminated to accept the failure, or propagating the error and letting the panic-on-drop surface the leak.

§Errors

Returns TerminationError if signalling or waiting for process termination fails.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub fn wait_for_completion( &mut self, timeout: Duration, ) -> WaitForCompletion<'_, Stdout, Stderr, NoOutput, NoTerminate>

Begin a staged wait for the process to run to completion within timeout.

.await the returned builder to wait without collecting output. Chain .with_line_output(...) or .with_raw_output(...) to also drain stdout / stderr into a ProcessOutput, and / or .or_terminate(...) to force graceful cleanup if the wait times out. Output collection must be configured before termination.

Any still-open stdin handle is closed before the terminal wait begins, matching tokio::process::Child::wait and helping avoid deadlocks where the child is waiting for input while the parent is waiting for exit.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub fn name(&self) -> &str

Returns the process name configured at spawn time.

The name is set by the Process builder via either an explicit string or an AutoName / AutoNameSettings derivation, and is used in diagnostics and error messages to identify the child.

Source

pub fn stdin(&mut self) -> &mut Stdin

Returns a mutable reference to the (potentially already closed) stdin stream.

Use this to write data to the child process’s stdin. The stdin stream implements tokio::io::AsyncWrite, allowing you to use methods like write_all() and flush().

§Example
    AutoName, DEFAULT_MAX_BUFFERED_CHUNKS, DEFAULT_READ_CHUNK_SIZE, Process,
};
// The stream backend does not make a difference here.
let mut process = Process::new(Command::new("cat"))
    .name(AutoName::program_only())
    .stdout_and_stderr(|stream| {
        stream
            .broadcast()
            .lossy_without_backpressure()
            .no_replay()
            .read_chunk_size(DEFAULT_READ_CHUNK_SIZE)
            .max_buffered_chunks(DEFAULT_MAX_BUFFERED_CHUNKS)
    })
    .spawn()
    .unwrap();

// Write to stdin.
if let Some(stdin) = process.stdin().as_mut() {
    stdin.write_all(b"Hello, process!\n").await.unwrap();
    stdin.flush().await.unwrap();
}

// Close stdin to signal EOF.
process.stdin().close();
Source

pub fn stdout(&self) -> &Stdout

Returns a reference to the stdout stream.

For BroadcastOutputStream, this allows creating multiple concurrent consumers. For SingleSubscriberOutputStream, only one active consumer can exist at a time; concurrent attempts return StreamConsumerError::ActiveConsumer from consume(...) / consume_async(...) / wait_for_line(...) rather than panicking.

Source

pub fn stderr(&self) -> &Stderr

Returns a reference to the stderr stream.

For BroadcastOutputStream, this allows creating multiple concurrent consumers. For SingleSubscriberOutputStream, only one active consumer can exist at a time; concurrent attempts return StreamConsumerError::ActiveConsumer from consume(...) / consume_async(...) / wait_for_line(...) rather than panicking.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub fn id(&self) -> Option<u32>

Returns the OS process ID if the process hasn’t exited yet.

Once this process has been polled to completion this will return None.

Source

pub fn is_running(&mut self) -> RunningState

Checks if the process is currently running.

Returns RunningState::Running if the process is still running, RunningState::Terminated if it has exited, or RunningState::Uncertain if the state could not be determined. Each call re-runs the underlying try_wait, so a transient probing failure observed once does not become permanent.

This is a pure status query: it does not disarm the drop-cleanup or panic guards, even when it observes that the process has exited. A handle whose status reads RunningState::Terminated still panics on drop until one of the lifecycle methods has closed it. Use Self::wait_for_completion (the staged builder), Self::terminate, or Self::kill to close the lifecycle through a successful terminal call, or call Self::must_not_be_terminated explicitly to detach the handle without termination.

Source§

impl<Stdout, Stderr> ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source

pub fn into_inner(self) -> (Child, Stdin, Stdout, Stderr)

Consumes this handle to provide the wrapped tokio::process::Child instance, stdin handle, and stdout/stderr output streams.

The returned Child no longer owns its stdin field because this crate separates piped stdin into Stdin. Keep the returned Stdin alive to keep the child’s stdin pipe open. Dropping Stdin::Open closes the pipe, so the child may observe EOF and exit or otherwise change behavior.

Trait Implementations§

Source§

impl<Stdout, Stderr> Debug for ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream + Debug, Stderr: OutputStream + Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Stdout, Stderr> Drop for ProcessHandle<Stdout, Stderr>
where Stdout: OutputStream, Stderr: OutputStream,

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl<Stdout, Stderr> Freeze for ProcessHandle<Stdout, Stderr>
where Stdout: Freeze, Stderr: Freeze,

§

impl<Stdout, Stderr = Stdout> !RefUnwindSafe for ProcessHandle<Stdout, Stderr>

§

impl<Stdout, Stderr> Send for ProcessHandle<Stdout, Stderr>
where Stdout: Send, Stderr: Send,

§

impl<Stdout, Stderr> Sync for ProcessHandle<Stdout, Stderr>
where Stdout: Sync, Stderr: Sync,

§

impl<Stdout, Stderr> Unpin for ProcessHandle<Stdout, Stderr>
where Stdout: Unpin, Stderr: Unpin,

§

impl<Stdout, Stderr> UnsafeUnpin for ProcessHandle<Stdout, Stderr>
where Stdout: UnsafeUnpin, Stderr: UnsafeUnpin,

§

impl<Stdout, Stderr = Stdout> !UnwindSafe for ProcessHandle<Stdout, Stderr>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> Sink for T
where T: Send + 'static,