Skip to content

Commit e1b1363

Browse files
committed
fix: extract only the active wrapper during spawn
1 parent b83fec4 commit e1b1363

3 files changed

Lines changed: 55 additions & 44 deletions

File tree

src/generic_wrap.rs

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ macro_rules! Wrap {
1212
#[derive(Debug)]
1313
pub struct $name {
1414
command: $command,
15-
wrappers: ::indexmap::IndexMap<::std::any::TypeId, Box<dyn $wrapper>>,
15+
wrappers: ::indexmap::IndexMap<::std::any::TypeId, ::std::cell::RefCell<::std::option::Option<Box<dyn $wrapper>>>>,
1616
}
1717

1818
impl $name {
@@ -62,70 +62,72 @@ macro_rules! Wrap {
6262
/// Returns `&mut self` for chaining.
6363
pub fn wrap<W: $wrapper + 'static>(&mut self, wrapper: W) -> &mut Self {
6464
let typeid = ::std::any::TypeId::of::<W>();
65-
let mut wrapper = Some(Box::new(wrapper));
65+
let boxed: Box<(dyn $wrapper + 'static)> = Box::new(wrapper);
66+
let mut wrapper = Some(::std::cell::RefCell::new(Some(boxed)));
6667
let extant = self
6768
.wrappers
6869
.entry(typeid)
6970
.or_insert_with(|| wrapper.take().unwrap());
7071
if let Some(wrapper) = wrapper {
71-
extant.extend(wrapper);
72+
// UNWRAPs: we've just created those so we know they're Somes
73+
extant.get_mut().as_mut().unwrap().extend(wrapper.into_inner().unwrap());
7274
}
7375

7476
self
7577
}
7678

77-
// poor man's try..finally block
78-
#[inline]
79-
fn spawn_inner(
80-
&self,
81-
command: &mut $command,
82-
wrappers: &mut ::indexmap::IndexMap<::std::any::TypeId, Box<dyn $wrapper>>,
83-
) -> ::std::io::Result<Box<dyn $childer>> {
84-
for (id, wrapper) in wrappers.iter_mut() {
79+
/// Spawn the command, returning a `Child` that can be interacted with.
80+
///
81+
/// In order, this runs all the `pre_spawn` hooks, then spawns the command, then runs
82+
/// all the `post_spawn` hooks, then stacks all the `wrap_child`s. As it returns a boxed
83+
/// trait object, only the methods from the trait are available directly; however you
84+
/// may downcast to the concrete type of the last applied wrapper if you need to.
85+
pub fn spawn(&mut self) -> ::std::io::Result<Box<dyn $childer>> {
86+
// for each loop, we extract the active wrapper from its cell
87+
// so we can use it mutably independently from the self borrow
88+
// then we re-insert it; this happens regardless of the result
89+
90+
for (id, cell) in &self.wrappers {
8591
#[cfg(feature = "tracing")]
8692
::tracing::debug!(?id, "pre_spawn");
87-
wrapper.pre_spawn(command, self)?;
93+
if let Some(mut wrapper) = cell.take() {
94+
let mut command = ::std::mem::replace(&mut self.command, <$command>::new(""));
95+
let ret = wrapper.pre_spawn(&mut command, self);
96+
self.command = command;
97+
cell.replace(Some(wrapper));
98+
ret?;
99+
}
88100
}
89101

90-
let mut child = command.spawn()?;
91-
for (id, wrapper) in wrappers.iter_mut() {
102+
let mut child = self.command.spawn()?;
103+
for (id, cell) in &self.wrappers {
92104
#[cfg(feature = "tracing")]
93105
::tracing::debug!(?id, "post_spawn");
94-
wrapper.post_spawn(&mut child, self)?;
106+
if let Some(mut wrapper) = cell.take() {
107+
let ret = wrapper.post_spawn(&mut child, self);
108+
cell.replace(Some(wrapper));
109+
ret?;
110+
}
95111
}
96112

97113
let mut child = Box::new(
98114
#[allow(clippy::redundant_closure_call)]
99115
$first_child_wrapper(child),
100116
) as Box<dyn $childer>;
101117

102-
for (id, wrapper) in wrappers.iter_mut() {
118+
for (id, cell) in &self.wrappers {
103119
#[cfg(feature = "tracing")]
104120
::tracing::debug!(?id, "wrap_child");
105-
child = wrapper.wrap_child(child, self)?;
121+
if let Some(mut wrapper) = cell.take() {
122+
let ret = wrapper.wrap_child(child, self);
123+
cell.replace(Some(wrapper));
124+
child = ret?;
125+
}
106126
}
107127

108128
Ok(child)
109129
}
110130

111-
/// Spawn the command, returning a `Child` that can be interacted with.
112-
///
113-
/// In order, this runs all the `pre_spawn` hooks, then spawns the command, then runs
114-
/// all the `post_spawn` hooks, then stacks all the `wrap_child`s. As it returns a boxed
115-
/// trait object, only the methods from the trait are available directly; however you
116-
/// may downcast to the concrete type of the last applied wrapper if you need to.
117-
pub fn spawn(&mut self) -> ::std::io::Result<Box<dyn $childer>> {
118-
let mut command = ::std::mem::replace(&mut self.command, <$command>::new(""));
119-
let mut wrappers = ::std::mem::take(&mut self.wrappers);
120-
121-
let res = self.spawn_inner(&mut command, &mut wrappers);
122-
123-
self.command = command;
124-
self.wrappers = wrappers;
125-
126-
res
127-
}
128-
129131
/// Check if a wrapper of a given type is present.
130132
pub fn has_wrap<W: $wrapper + 'static>(&self) -> bool {
131133
let typeid = ::std::any::TypeId::of::<W>();
@@ -139,14 +141,23 @@ macro_rules! Wrap {
139141
///
140142
/// Returns `None` if the wrapper is not present. To merely check if a wrapper is
141143
/// present, use `has_wrap` instead.
142-
pub fn get_wrap<W: $wrapper + 'static>(&self) -> Option<&W> {
144+
///
145+
/// Note that calling `.get_wrap()` to retrieve the current wrapper while within that
146+
/// wrapper's hooks will return `None`. As this is useless (you should trivially have
147+
/// access to the current wrapper), this is not considered a bug.
148+
pub fn get_wrap<W: $wrapper + 'static>(&self) -> Option<::std::cell::Ref<W>> {
143149
let typeid = ::std::any::TypeId::of::<W>();
144-
self.wrappers.get(&typeid).map(|w| {
145-
let w_any = w as &dyn ::std::any::Any;
146-
w_any
147-
.downcast_ref()
148-
.expect("downcasting is guaranteed to succeed due to wrap()'s internals")
149-
})
150+
self.wrappers.get(&typeid)
151+
.and_then(|cell| cell.try_borrow().ok()
152+
.and_then(|borrow| ::std::cell::Ref::filter_map(
153+
borrow, |opt| opt.as_ref().map(|w| {
154+
let w_any = w as &dyn ::std::any::Any;
155+
w_any
156+
.downcast_ref()
157+
.expect("downcasting is guaranteed to succeed due to wrap()'s internals")
158+
})
159+
).ok())
160+
)
150161
}
151162
}
152163

src/std/job_object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl StdCommandWrapper for JobObject {
4040
fn pre_spawn(&mut self, command: &mut Command, core: &StdCommandWrap) -> Result<()> {
4141
let mut flags = CREATE_SUSPENDED;
4242
#[cfg(feature = "creation-flags")]
43-
if let Some(CreationFlags(user_flags)) = core.get_wrap::<CreationFlags>() {
43+
if let Some(CreationFlags(user_flags)) = core.get_wrap::<CreationFlags>().as_deref() {
4444
flags |= *user_flags;
4545
}
4646

src/tokio/job_object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl TokioCommandWrapper for JobObject {
4141
fn pre_spawn(&mut self, command: &mut Command, core: &TokioCommandWrap) -> Result<()> {
4242
let mut flags = CREATE_SUSPENDED;
4343
#[cfg(feature = "creation-flags")]
44-
if let Some(CreationFlags(user_flags)) = core.get_wrap::<CreationFlags>() {
44+
if let Some(CreationFlags(user_flags)) = core.get_wrap::<CreationFlags>().as_deref() {
4545
flags |= *user_flags;
4646
}
4747

0 commit comments

Comments
 (0)