diff --git a/Cargo.lock b/Cargo.lock index e967f86..3f4325b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -464,6 +464,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "object" version = "0.36.7" @@ -481,6 +491,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -542,6 +558,7 @@ dependencies = [ "remoteprocess", "tokio", "tracing", + "tracing-subscriber", "windows", ] @@ -660,6 +677,15 @@ dependencies = [ "syn", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -713,6 +739,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.45.0" @@ -769,6 +805,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -793,6 +855,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 94e543e..b020bdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ windows = { version = "0.61.1", optional = true } [dev-dependencies] remoteprocess = "0.5.0" tokio = { version = "1.38.2", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] } +tracing-subscriber = "0.3.19" [features] default = ["creation-flags", "job-object", "kill-on-drop", "process-group", "process-session", "tracing"] diff --git a/src/generic_wrap.rs b/src/generic_wrap.rs index 6583c32..9209db7 100644 --- a/src/generic_wrap.rs +++ b/src/generic_wrap.rs @@ -12,7 +12,7 @@ macro_rules! Wrap { #[derive(Debug)] pub struct $name { command: $command, - wrappers: ::indexmap::IndexMap<::std::any::TypeId, Box>, + wrappers: ::indexmap::IndexMap<::std::any::TypeId, ::std::cell::RefCell<::std::option::Option>>>, } impl $name { @@ -62,36 +62,58 @@ macro_rules! Wrap { /// Returns `&mut self` for chaining. pub fn wrap(&mut self, wrapper: W) -> &mut Self { let typeid = ::std::any::TypeId::of::(); - let mut wrapper = Some(Box::new(wrapper)); + let boxed: Box<(dyn $wrapper + 'static)> = Box::new(wrapper); + let mut wrapper = Some(::std::cell::RefCell::new(Some(boxed))); let extant = self .wrappers .entry(typeid) - .or_insert_with(|| wrapper.take().unwrap()); + .or_insert_with(|| { + #[cfg(feature = "tracing")] + ::tracing::debug!(id=?typeid, "wrap"); + wrapper.take().unwrap() + }); if let Some(wrapper) = wrapper { - extant.extend(wrapper); + #[cfg(feature = "tracing")] + ::tracing::debug!(id=?typeid, "wrap extend"); + // UNWRAPs: we've just created those so we know they're Somes + extant.get_mut().as_mut().unwrap().extend(wrapper.into_inner().unwrap()); } self } - // poor man's try..finally block - #[inline] - fn spawn_inner( - &self, - command: &mut $command, - wrappers: &mut ::indexmap::IndexMap<::std::any::TypeId, Box>, - ) -> ::std::io::Result> { - for (id, wrapper) in wrappers.iter_mut() { + /// Spawn the command, returning a `Child` that can be interacted with. + /// + /// In order, this runs all the `pre_spawn` hooks, then spawns the command, then runs + /// all the `post_spawn` hooks, then stacks all the `wrap_child`s. As it returns a boxed + /// trait object, only the methods from the trait are available directly; however you + /// may downcast to the concrete type of the last applied wrapper if you need to. + pub fn spawn(&mut self) -> ::std::io::Result> { + // for each loop, we extract the active wrapper from its cell + // so we can use it mutably independently from the self borrow + // then we re-insert it; this happens regardless of the result + + for (id, cell) in &self.wrappers { #[cfg(feature = "tracing")] ::tracing::debug!(?id, "pre_spawn"); - wrapper.pre_spawn(command, self)?; + if let Some(mut wrapper) = cell.take() { + let mut command = ::std::mem::replace(&mut self.command, <$command>::new("")); + let ret = wrapper.pre_spawn(&mut command, self); + self.command = command; + cell.replace(Some(wrapper)); + ret?; + } } - let mut child = command.spawn()?; - for (id, wrapper) in wrappers.iter_mut() { + let mut child = self.command.spawn()?; + for (id, cell) in &self.wrappers { #[cfg(feature = "tracing")] ::tracing::debug!(?id, "post_spawn"); - wrapper.post_spawn(&mut child, self)?; + if let Some(mut wrapper) = cell.take() { + let ret = wrapper.post_spawn(&mut child, self); + cell.replace(Some(wrapper)); + ret?; + } } let mut child = Box::new( @@ -99,33 +121,19 @@ macro_rules! Wrap { $first_child_wrapper(child), ) as Box; - for (id, wrapper) in wrappers.iter_mut() { + for (id, cell) in &self.wrappers { #[cfg(feature = "tracing")] ::tracing::debug!(?id, "wrap_child"); - child = wrapper.wrap_child(child, self)?; + if let Some(mut wrapper) = cell.take() { + let ret = wrapper.wrap_child(child, self); + cell.replace(Some(wrapper)); + child = ret?; + } } Ok(child) } - /// Spawn the command, returning a `Child` that can be interacted with. - /// - /// In order, this runs all the `pre_spawn` hooks, then spawns the command, then runs - /// all the `post_spawn` hooks, then stacks all the `wrap_child`s. As it returns a boxed - /// trait object, only the methods from the trait are available directly; however you - /// may downcast to the concrete type of the last applied wrapper if you need to. - pub fn spawn(&mut self) -> ::std::io::Result> { - let mut command = ::std::mem::replace(&mut self.command, <$command>::new("")); - let mut wrappers = ::std::mem::take(&mut self.wrappers); - - let res = self.spawn_inner(&mut command, &mut wrappers); - - self.command = command; - self.wrappers = wrappers; - - res - } - /// Check if a wrapper of a given type is present. pub fn has_wrap(&self) -> bool { let typeid = ::std::any::TypeId::of::(); @@ -139,14 +147,27 @@ macro_rules! Wrap { /// /// Returns `None` if the wrapper is not present. To merely check if a wrapper is /// present, use `has_wrap` instead. - pub fn get_wrap(&self) -> Option<&W> { + /// + /// Note that calling `.get_wrap()` to retrieve the current wrapper while within that + /// wrapper's hooks will return `None`. As this is useless (you should trivially have + /// access to the current wrapper), this is not considered a bug. + pub fn get_wrap(&self) -> Option<::std::cell::Ref> { let typeid = ::std::any::TypeId::of::(); - self.wrappers.get(&typeid).map(|w| { - let w_any = w as &dyn ::std::any::Any; - w_any - .downcast_ref() - .expect("downcasting is guaranteed to succeed due to wrap()'s internals") - }) + #[cfg(feature = "tracing")] + ::tracing::debug!(id=?typeid, "get wrap"); + self.wrappers.get(&typeid) + .and_then(|cell| cell.try_borrow().ok()) + .and_then(|borrow| ::std::cell::Ref::filter_map( + borrow, |opt| opt.as_ref().map(|w: &Box| { + #[cfg(feature = "tracing")] + ::tracing::debug!(id=?typeid, "got wrap"); + let w_any = w as &dyn ::std::any::Any; + w_any + .downcast_ref() + .expect("downcasting is guaranteed to succeed due to wrap()'s internals") + }) + ).ok()) + } } diff --git a/src/std/job_object.rs b/src/std/job_object.rs index 96cbdd8..4843840 100644 --- a/src/std/job_object.rs +++ b/src/std/job_object.rs @@ -40,7 +40,7 @@ impl StdCommandWrapper for JobObject { fn pre_spawn(&mut self, command: &mut Command, core: &StdCommandWrap) -> Result<()> { let mut flags = CREATE_SUSPENDED; #[cfg(feature = "creation-flags")] - if let Some(CreationFlags(user_flags)) = core.get_wrap::() { + if let Some(CreationFlags(user_flags)) = core.get_wrap::().as_deref() { flags |= *user_flags; } diff --git a/src/tokio/job_object.rs b/src/tokio/job_object.rs index efaf8af..b3e454c 100644 --- a/src/tokio/job_object.rs +++ b/src/tokio/job_object.rs @@ -41,7 +41,7 @@ impl TokioCommandWrapper for JobObject { fn pre_spawn(&mut self, command: &mut Command, core: &TokioCommandWrap) -> Result<()> { let mut flags = CREATE_SUSPENDED; #[cfg(feature = "creation-flags")] - if let Some(CreationFlags(user_flags)) = core.get_wrap::() { + if let Some(CreationFlags(user_flags)) = core.get_wrap::().as_deref() { flags |= *user_flags; } diff --git a/tests/std_windows/mod.rs b/tests/std_windows/mod.rs index 6212670..9b1bd5b 100644 --- a/tests/std_windows/mod.rs +++ b/tests/std_windows/mod.rs @@ -1,7 +1,7 @@ mod prelude { pub use std::{ io::{Read, Result, Write}, - process::Stdio, + process::{Command, Stdio}, thread::sleep, time::Duration, }; @@ -11,10 +11,17 @@ mod prelude { pub const DIE_TIME: Duration = Duration::from_millis(1000); } +fn init() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .init(); +} + mod id_same_as_inner; mod inner_read_stdout; mod into_inner_write_stdin; mod kill_and_try_wait; +mod read_creation_flags; mod try_wait_after_die; mod wait_after_die; mod wait_twice; diff --git a/tests/std_windows/read_creation_flags.rs b/tests/std_windows/read_creation_flags.rs new file mode 100644 index 0000000..f50b96b --- /dev/null +++ b/tests/std_windows/read_creation_flags.rs @@ -0,0 +1,41 @@ +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Arc, +}; + +use windows::Win32::System::Threading::CREATE_NO_WINDOW; + +use super::prelude::*; + +#[derive(Clone, Debug, Default)] +pub struct FlagSpy { + pub flags: Arc, +} + +impl StdCommandWrapper for FlagSpy { + fn pre_spawn(&mut self, _command: &mut Command, core: &StdCommandWrap) -> Result<()> { + #[cfg(feature = "creation-flags")] + if let Some(CreationFlags(user_flags)) = core.get_wrap::().as_deref() { + self.flags.store(user_flags.0, Ordering::Relaxed); + } + + Ok(()) + } +} + +#[test] +fn retrieve_flags() -> Result<()> { + super::init(); + + let spy = FlagSpy::default(); + let _ = StdCommandWrap::with_new("powershell.exe", |command| { + command.arg("/C").arg("echo hello").stdout(Stdio::piped()); + }) + .wrap(CreationFlags(CREATE_NO_WINDOW)) + .wrap(spy.clone()) + .spawn()?; + + assert_eq!(spy.flags.load(Ordering::Relaxed), CREATE_NO_WINDOW.0); + + Ok(()) +}