Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ CommandWrap::with_new("watch", |command| { command.arg("ls"); })
requested flag, temporarily adds `CREATE_SUSPENDED` while assigning the process, and resumes it
after assignment unless the caller explicitly requested `CREATE_SUSPENDED`.

For the std frontend, add [`KillOnDrop`] to opt in to Windows' kill-on-job-close behavior. Without
the marker, the existing behavior is preserved and dropping the child wrapper does not terminate
the job. The marker and `JobObject` may be registered in either order:

```rust
use process_wrap::std::*;

let mut command = CommandWrap::with_new("watch", |command| { command.arg("ls"); });
command.wrap(JobObject).wrap(KillOnDrop);
let child = command.spawn()?;
drop(child); // terminates every process still in the job
```

### Process group

- Platforms: POSIX (Linux, Mac, BSDs...)
Expand Down Expand Up @@ -189,11 +202,13 @@ after assignment unless the caller explicitly requested `CREATE_SUSPENDED`.

### Kill on drop

- Platforms: all (Tokio-only)
- Platforms: all; std opt-in is Windows-only and requires `JobObject`
- Like command-group.
- Feature: `kill-on-drop` (default)

This is a shim to allow wrappers to handle the kill-on-drop flag, as it can't be read from Command.
For Tokio, this shim sets the command's kill-on-drop flag so wrappers can read it. For std on
Windows, it opts `JobObject` into `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`; it has no effect without
`JobObject`. The std job-object default remains unchanged when the marker is absent.

```rust
let child = CommandWrap::with_new("watch", |command| { command.arg("ls"); })
Expand Down Expand Up @@ -261,7 +276,7 @@ Both can exist at the same time, but generally you should use one or the other.

- `creation-flags`: **default**, enables the [creation flags](#creation-flags) wrapper.
- `job-object`: **default**, enables the [job object](#job-object) wrapper.
- `kill-on-drop`: **default**, enables the [kill on drop](#kill-on-drop) wrapper.
- `kill-on-drop`: **default**, enables the [kill on drop](#kill-on-drop) wrapper (including the std Windows `JobObject` opt-in).
- `process-group`: **default**, enables the [process group](#process-group) wrapper.
- `process-session`: **default**, enables the [process session](#process-session) wrapper.
- `reset-sigmask`: enables the [reset signal mask](#reset-signal-mask) wrapper.
22 changes: 15 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,30 @@
//! # KillOnDrop and CreationFlags
//!
//! The options set on an underlying `Command` are not queryable from library or user code. In most
//! cases this is not an issue; however on Windows, the `JobObject` wrapper needs to know the value
//! of `.kill_on_drop()` and any `.creation_flags()` set. The `KillOnDrop` and `CreationFlags` are
//! "shims" that _should_ be used instead of the aforementioned methods on `Command`. They will
//! internally set the values on the `Command` and also store them in the wrapper, so that wrappers
//! are able to access them.
//! cases this is not an issue; however on Windows, wrappers may need to know the value of
//! `.kill_on_drop()` and any `.creation_flags()` set. `KillOnDrop` and `CreationFlags` are shims
//! that store these options where wrappers can access them. Tokio's `KillOnDrop` also sets the
//! underlying command's kill-on-drop option. The std `KillOnDrop` is an opt-in for `JobObject`
//! kill-on-close behavior; without it, std `JobObject` keeps its existing behavior.
//!
//! In practice:
//!
//! ## Instead of `.kill_on_drop(true)` (Tokio-only):
//! ## Tokio: instead of `.kill_on_drop(true)`:
//!
//! ```rust
//! use process_wrap::tokio::*;
//! let mut command = CommandWrap::with_new("ls", |command| { command.arg("-l"); });
//! command.wrap(KillOnDrop);
//! ```
//!
//! ## std: opt in to Windows Job Object termination when the child wrapper is dropped:
//!
//! ```rust,ignore
//! use process_wrap::std::*;
//! let mut command = CommandWrap::with_new("watch", |command| { command.arg("ls"); });
//! command.wrap(JobObject).wrap(KillOnDrop);
//! ```
//!
//! ## Instead of `.creation_flags(CREATE_NO_WINDOW)` (Windows-only):
//!
//! ```rust,ignore
Expand Down Expand Up @@ -462,7 +470,7 @@
//!
//! - `creation-flags`: **default**, enables the creation flags wrapper (Windows-only).
//! - `job-object`: **default**, enables the job object wrapper (Windows-only).
//! - `kill-on-drop`: **default**, enables the kill on drop wrapper (Tokio-only).
//! - `kill-on-drop`: **default**, enables Tokio kill-on-drop and std Windows JobObject kill-on-close.
//! - `process-group`: **default**, enables the process group wrapper (Unix-only).
//! - `process-session`: **default**, enables the process session wrapper (Unix-only).
//! - `reset-sigmask`: enables the sigmask reset wrapper (Unix-only).
Expand Down
5 changes: 5 additions & 0 deletions src/std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub use creation_flags::CreationFlags;
#[cfg(all(windows, feature = "job-object"))]
#[doc(inline)]
pub use job_object::{JobObject, JobObjectChild};
#[cfg(all(windows, feature = "job-object", feature = "kill-on-drop"))]
#[doc(inline)]
pub use kill_on_drop::KillOnDrop;
#[cfg(all(unix, feature = "process-group"))]
#[doc(inline)]
pub use process_group::{ProcessGroup, ProcessGroupChild};
Expand All @@ -31,6 +34,8 @@ mod core;
mod creation_flags;
#[cfg(all(windows, feature = "job-object"))]
mod job_object;
#[cfg(all(windows, feature = "job-object", feature = "kill-on-drop"))]
mod kill_on_drop;
#[cfg(all(unix, feature = "process-group"))]
mod process_group;
#[cfg(all(unix, feature = "process-session"))]
Expand Down
9 changes: 8 additions & 1 deletion src/std/job_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use crate::{

#[cfg(feature = "creation-flags")]
use super::CreationFlags;
#[cfg(feature = "kill-on-drop")]
use super::KillOnDrop;
use super::{ChildWrapper, CommandWrap, CommandWrapper};

/// Wrapper which creates a job object context for a `Command`.
Expand Down Expand Up @@ -82,6 +84,11 @@ impl CommandWrapper for JobObject {
mut inner: Box<dyn ChildWrapper>,
core: &CommandWrap,
) -> Result<Box<dyn ChildWrapper>> {
#[cfg(feature = "kill-on-drop")]
let kill_on_drop = core.has_wrap::<KillOnDrop>();
#[cfg(not(feature = "kill-on-drop"))]
let kill_on_drop = false;

let policy = job_creation_flags(user_creation_flags(core));

#[cfg(feature = "tracing")]
Expand All @@ -103,7 +110,7 @@ impl CommandWrapper for JobObject {
}
};

let job_port = match make_job_object(handle, false) {
let job_port = match make_job_object(handle, kill_on_drop) {
Ok(job_port) => job_port,
Err(error) => {
terminate_child(&mut *inner);
Expand Down
12 changes: 12 additions & 0 deletions src/std/kill_on_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use super::CommandWrapper;

/// Opt in to terminating every process in a Windows [`super::JobObject`] when its child wrapper is
/// dropped.
///
/// This marker has no effect without [`super::JobObject`]. Without this marker, the std
/// [`super::JobObject`] keeps its existing behavior and closing its child wrapper does not
/// terminate the job.
#[derive(Clone, Copy, Debug)]
pub struct KillOnDrop;

impl CommandWrapper for KillOnDrop {}
Loading
Loading