Skip to content

Commit bcfd7fd

Browse files
authored
feat: change child wrapper inner()s to return one layer down (#23)
1 parent e760d2d commit bcfd7fd

21 files changed

+309
-193
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- windows
1717
toolchain:
1818
- stable
19-
- 1.77.0
19+
- 1.86.0
2020
features:
2121
- tokio1
2222
- std

Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ readme = "README.md"
1414

1515
edition = "2021"
1616
exclude = ["/bin", "/.github"]
17-
rust-version = "1.77.0"
17+
rust-version = "1.86.0"
1818

1919
[dependencies]
2020
futures = { version = "0.3.30", optional = true }
@@ -41,9 +41,6 @@ tokio = { version = "1.38.2", features = ["io-util", "macros", "process", "rt",
4141
[features]
4242
default = ["creation-flags", "job-object", "kill-on-drop", "process-group", "process-session", "tracing"]
4343

44-
## Enable Any trait bound on the ChildWrapper traits
45-
downcasting = []
46-
4744
## Enable internal tracing logs
4845
tracing = ["dep:tracing"]
4946

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
- **[API documentation][docs]**.
88
- [Dual-licensed][copyright] with Apache 2.0 and MIT.
99
- Successor to [command-group](https://github.com/watchexec/command-group).
10-
- Minimum Supported Rust Version: 1.77.0.
10+
- Minimum Supported Rust Version: 1.86.0.
1111
- Only the latest stable rustc version is supported.
1212
- MSRV increases will not incur major version bumps.
1313

src/generic_wrap.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -230,20 +230,4 @@ macro_rules! Wrap {
230230
};
231231
}
232232

233-
macro_rules! MaybeAnyTrait {
234-
(
235-
$(#[$($attrss:tt)*])*
236-
$v:vis trait $name:ident $body:tt
237-
) => {
238-
#[cfg(feature = "downcasting")]
239-
$(#[$($attrss)*])*
240-
$v trait $name: std::fmt::Debug + Send + Sync + std::any::Any $body
241-
242-
#[cfg(not(feature = "downcasting"))]
243-
$(#[$($attrss)*])*
244-
$v trait $name: std::fmt::Debug + Send + Sync $body
245-
}
246-
}
247-
248-
pub(crate) use MaybeAnyTrait;
249233
pub(crate) use Wrap;

src/std.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! ```
1010
1111
#[doc(inline)]
12-
pub use core::{StdChild, StdChildWrapper, StdCommandWrap, StdCommandWrapper};
12+
pub use core::{StdChildWrapper, StdCommandWrap, StdCommandWrapper};
1313
#[cfg(all(windows, feature = "creation-flags"))]
1414
#[doc(inline)]
1515
pub use creation_flags::CreationFlags;

src/std/core.rs

Lines changed: 115 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
any::Any,
23
io::{Read, Result},
34
process::{Child, ChildStderr, ChildStdin, ChildStdout, Command, ExitStatus, Output},
45
};
@@ -15,22 +16,21 @@ crate::generic_wrap::Wrap!(
1516
StdCommandWrapper,
1617
Child,
1718
StdChildWrapper,
18-
StdChild // |child| StdChild(child)
19+
|child| child
1920
);
2021

21-
crate::generic_wrap::MaybeAnyTrait! {
2222
/// Wrapper for `std::process::Child`.
2323
///
2424
/// This trait exposes most of the functionality of the underlying [`Child`]. It is implemented for
25-
/// [`StdChild`] (a thin wrapper around [`Child`]) (because implementing directly on [`Child`] would
26-
/// loop) and by wrappers.
25+
/// [`Child`] and by wrappers.
2726
///
2827
/// The required methods are `inner`, `inner_mut`, and `into_inner`. That provides access to the
29-
/// underlying `Child` and allows the wrapper to be dropped and the `Child` to be used directly if
30-
/// necessary.
28+
/// lower layer and ultimately allows the wrappers to be unwrap and the `Child` to be used directly
29+
/// if necessary. There are convenience `inner_child`, `inner_child_mut` and `into_inner_child`
30+
/// methods on the trait object.
3131
///
3232
/// It also makes it possible for all the other methods to have default implementations. Some are
33-
/// direct passthroughs to the underlying `Child`, while others are more complex.
33+
/// direct passthroughs to the lower layers, while others are more complex.
3434
///
3535
/// Here's a simple example of a wrapper:
3636
///
@@ -42,32 +42,32 @@ crate::generic_wrap::MaybeAnyTrait! {
4242
/// pub struct YourChildWrapper(Child);
4343
///
4444
/// impl StdChildWrapper for YourChildWrapper {
45-
/// fn inner(&self) -> &Child {
45+
/// fn inner(&self) -> &dyn StdChildWrapper {
4646
/// &self.0
4747
/// }
4848
///
49-
/// fn inner_mut(&mut self) -> &mut Child {
49+
/// fn inner_mut(&mut self) -> &mut dyn StdChildWrapper {
5050
/// &mut self.0
5151
/// }
5252
///
53-
/// fn into_inner(self: Box<Self>) -> Child {
54-
/// (*self).0
53+
/// fn into_inner(self: Box<Self>) -> Box<dyn StdChildWrapper> {
54+
/// Box::new((*self).0)
5555
/// }
5656
/// }
5757
/// ```
58-
pub trait StdChildWrapper {
59-
/// Obtain a reference to the underlying `Child`.
60-
fn inner(&self) -> &Child;
58+
pub trait StdChildWrapper: Any + std::fmt::Debug + Send {
59+
/// Obtain a reference to the wrapped child.
60+
fn inner(&self) -> &dyn StdChildWrapper;
6161

62-
/// Obtain a mutable reference to the underlying `Child`.
63-
fn inner_mut(&mut self) -> &mut Child;
62+
/// Obtain a mutable reference to the wrapped child.
63+
fn inner_mut(&mut self) -> &mut dyn StdChildWrapper;
6464

65-
/// Consume the wrapper and return the underlying `Child`.
65+
/// Consume the current wrapper and return the wrapped child.
6666
///
67-
/// Note that this may disrupt whatever the wrappers were doing. However, wrappers must ensure
68-
/// that the `Child` is in a consistent state when this is called or they are dropped, so that
69-
/// this is always safe.
70-
fn into_inner(self: Box<Self>) -> Child;
67+
/// Note that this may disrupt whatever the current wrapper was doing. However, wrappers must
68+
/// ensure that the wrapped child is in a consistent state when this is called or they are
69+
/// dropped, so that this is always safe.
70+
fn into_inner(self: Box<Self>) -> Box<dyn StdChildWrapper>;
7171

7272
/// Obtain a clone if possible.
7373
///
@@ -79,23 +79,23 @@ pub trait StdChildWrapper {
7979

8080
/// Obtain the `Child`'s stdin.
8181
///
82-
/// By default this is a passthrough to the underlying `Child`.
82+
/// By default this is a passthrough to the wrapped child.
8383
fn stdin(&mut self) -> &mut Option<ChildStdin> {
84-
&mut self.inner_mut().stdin
84+
self.inner_mut().stdin()
8585
}
8686

8787
/// Obtain the `Child`'s stdout.
8888
///
89-
/// By default this is a passthrough to the underlying `Child`.
89+
/// By default this is a passthrough to the wrapped child.
9090
fn stdout(&mut self) -> &mut Option<ChildStdout> {
91-
&mut self.inner_mut().stdout
91+
self.inner_mut().stdout()
9292
}
9393

9494
/// Obtain the `Child`'s stderr.
9595
///
96-
/// By default this is a passthrough to the underlying `Child`.
96+
/// By default this is a passthrough to the wrapped child.
9797
fn stderr(&mut self) -> &mut Option<ChildStderr> {
98-
&mut self.inner_mut().stderr
98+
self.inner_mut().stderr()
9999
}
100100

101101
/// Obtain the `Child`'s process ID.
@@ -127,15 +127,7 @@ pub trait StdChildWrapper {
127127
/// library uses it to provide a consistent API across both std and Tokio (and because it's a
128128
/// generally useful API).
129129
fn start_kill(&mut self) -> Result<()> {
130-
#[cfg(unix)]
131-
{
132-
self.signal(Signal::SIGKILL as _)
133-
}
134-
135-
#[cfg(not(unix))]
136-
{
137-
self.inner_mut().kill()
138-
}
130+
self.inner_mut().start_kill()
139131
}
140132

141133
/// Check if the `Child` has exited without blocking, and if so, return its exit status.
@@ -204,6 +196,51 @@ pub trait StdChildWrapper {
204196
/// was introduced by command-group to abstract over the signal behaviour between process groups
205197
/// and unwrapped processes.
206198
#[cfg(unix)]
199+
fn signal(&self, sig: i32) -> Result<()> {
200+
self.inner().signal(sig)
201+
}
202+
}
203+
204+
impl StdChildWrapper for Child {
205+
fn inner(&self) -> &dyn StdChildWrapper {
206+
self
207+
}
208+
fn inner_mut(&mut self) -> &mut dyn StdChildWrapper {
209+
self
210+
}
211+
fn into_inner(self: Box<Self>) -> Box<dyn StdChildWrapper> {
212+
self
213+
}
214+
fn stdin(&mut self) -> &mut Option<ChildStdin> {
215+
&mut self.stdin
216+
}
217+
fn stdout(&mut self) -> &mut Option<ChildStdout> {
218+
&mut self.stdout
219+
}
220+
fn stderr(&mut self) -> &mut Option<ChildStderr> {
221+
&mut self.stderr
222+
}
223+
fn id(&self) -> u32 {
224+
Child::id(self)
225+
}
226+
fn start_kill(&mut self) -> Result<()> {
227+
#[cfg(unix)]
228+
{
229+
self.signal(Signal::SIGKILL as _)
230+
}
231+
232+
#[cfg(not(unix))]
233+
{
234+
Child::kill(self)
235+
}
236+
}
237+
fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
238+
Child::try_wait(self)
239+
}
240+
fn wait(&mut self) -> Result<ExitStatus> {
241+
Child::wait(self)
242+
}
243+
#[cfg(unix)]
207244
fn signal(&self, sig: i32) -> Result<()> {
208245
kill(
209246
Pid::from_raw(i32::try_from(self.id()).map_err(std::io::Error::other)?),
@@ -212,25 +249,51 @@ pub trait StdChildWrapper {
212249
.map_err(std::io::Error::from)
213250
}
214251
}
215-
}
216252

217-
/// A thin wrapper around [`Child`].
218-
///
219-
/// This is used only because implementing [`StdChildWrapper`] directly on std's [`Child`] creates
220-
/// loops in the type system. It is not intended to be used directly, but only to be used internally
221-
/// by the library.
222-
#[derive(Debug)]
223-
pub struct StdChild(pub Child);
224-
225-
impl StdChildWrapper for StdChild {
226-
fn inner(&self) -> &Child {
227-
&self.0
253+
impl dyn StdChildWrapper {
254+
fn downcast_ref<T: 'static>(&self) -> Option<&T> {
255+
(self as &dyn Any).downcast_ref()
228256
}
229-
fn inner_mut(&mut self) -> &mut Child {
230-
&mut self.0
257+
258+
fn is_raw_child(&self) -> bool {
259+
self.downcast_ref::<Child>().is_some()
260+
}
261+
262+
/// Obtain a reference to the underlying [`Child`].
263+
pub fn inner_child(&self) -> &Child {
264+
let mut inner = self;
265+
while !inner.is_raw_child() {
266+
inner = inner.inner();
267+
}
268+
269+
// UNWRAP: we've just checked that it's Some with is_raw_child()
270+
inner.downcast_ref().unwrap()
271+
}
272+
273+
/// Obtain a mutable reference to the underlying [`Child`].
274+
///
275+
/// Modifying the raw child may be unsound depending on the layering of wrappers.
276+
pub unsafe fn inner_child_mut(&mut self) -> &mut Child {
277+
let mut inner = self;
278+
while !inner.is_raw_child() {
279+
inner = inner.inner_mut();
280+
}
281+
282+
// UNWRAP: we've just checked that with is_raw_child()
283+
(inner as &mut dyn Any).downcast_mut().unwrap()
231284
}
232-
fn into_inner(self: Box<Self>) -> Child {
233-
(*self).0
285+
286+
/// Obtain the underlying [`Child`].
287+
///
288+
/// Unwrapping everything may be unsound depending on the state of the wrappers.
289+
pub unsafe fn into_inner_child(self: Box<Self>) -> Child {
290+
let mut inner = self;
291+
while !inner.is_raw_child() {
292+
inner = inner.into_inner();
293+
}
294+
295+
// UNWRAP: we've just checked that with is_raw_child()
296+
*(inner as Box<dyn Any>).downcast().unwrap()
234297
}
235298
}
236299

src/std/job_object.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
io::Result,
33
os::windows::{io::AsRawHandle, process::CommandExt},
4-
process::{Child, Command, ExitStatus},
4+
process::{Command, ExitStatus},
55
time::Duration,
66
};
77

@@ -66,7 +66,7 @@ impl StdCommandWrapper for JobObject {
6666
#[cfg(feature = "tracing")]
6767
debug!(?create_suspended, "options from other wrappers");
6868

69-
let handle = HANDLE(inner.inner().as_raw_handle() as _);
69+
let handle = HANDLE(inner.inner_child().as_raw_handle() as _);
7070

7171
let job_port = make_job_object(handle, false)?;
7272

@@ -99,13 +99,13 @@ impl JobObjectChild {
9999
}
100100

101101
impl StdChildWrapper for JobObjectChild {
102-
fn inner(&self) -> &Child {
102+
fn inner(&self) -> &dyn StdChildWrapper {
103103
self.inner.inner()
104104
}
105-
fn inner_mut(&mut self) -> &mut Child {
105+
fn inner_mut(&mut self) -> &mut dyn StdChildWrapper {
106106
self.inner.inner_mut()
107107
}
108-
fn into_inner(self: Box<Self>) -> Child {
108+
fn into_inner(self: Box<Self>) -> Box<dyn StdChildWrapper> {
109109
// manually drop the completion port
110110
let its = std::mem::ManuallyDrop::new(self.job_port);
111111
unsafe { CloseHandle(its.completion_port.0) }.ok();
@@ -135,13 +135,13 @@ impl StdChildWrapper for JobObjectChild {
135135
let JobPort {
136136
completion_port, ..
137137
} = self.job_port;
138-
wait_on_job(completion_port, None)?;
138+
let _ = wait_on_job(completion_port, None)?;
139139
Ok(status)
140140
}
141141

142142
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self)))]
143143
fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
144-
wait_on_job(self.job_port.completion_port, Some(Duration::ZERO))?;
144+
let _ = wait_on_job(self.job_port.completion_port, Some(Duration::ZERO))?;
145145
self.inner.try_wait()
146146
}
147147
}

0 commit comments

Comments
 (0)