Skip to content

Commit e91da59

Browse files
committed
Experiment with fixing wait_with_pipe()
1 parent 171690e commit e91da59

File tree

2 files changed

+86
-3
lines changed

2 files changed

+86
-3
lines changed

src/child.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,17 @@ impl FunChildren {
115115
}
116116
}
117117

118-
/// Waits for the children processes to exit completely, pipe content will be processed by
119-
/// provided function.
118+
/// Pipes stdout from the last child in the pipeline to the given function, which runs in
119+
/// **the current thread**, then waits for all of the children to exit.
120+
///
121+
/// <div class=warning>
122+
///
123+
/// # Bugs
124+
///
125+
/// The exit status of the last child is **ignored**. If the function returns early, without
126+
/// reading from stdout until the last child exits, then the last child may be killed instead
127+
/// of being waited for. To avoid these limitations, use [`Self::wait_with_stdout_thread`].
128+
/// </div>
120129
pub fn wait_with_pipe(&mut self, f: &mut dyn FnMut(Box<dyn Read>)) -> CmdResult {
121130
let child = self.children.pop().unwrap();
122131
let stderr_thread =
@@ -143,6 +152,22 @@ impl FunChildren {
143152
CmdChildren::wait_children(&mut self.children)
144153
}
145154

155+
/// Pipes stdout from the last child in the pipeline to the given function, which runs in
156+
/// **a new thread**, then waits for all of the children to exit.
157+
pub fn wait_with_pipe_thread(
158+
&mut self,
159+
f: impl FnOnce(Box<dyn Read + Send>) + Send + 'static,
160+
) -> CmdResult {
161+
if let Some(stdout) = self.children.last_mut().unwrap().stdout.take() {
162+
let thread = std::thread::spawn(|| f(Box::new(stdout)));
163+
let wait_res = self.wait_with_output().map(|_| ());
164+
thread.join().expect("stdout thread panicked");
165+
return wait_res;
166+
}
167+
168+
Ok(())
169+
}
170+
146171
/// Returns the OS-assigned process identifiers associated with these children processes.
147172
pub fn pids(&self) -> Vec<u32> {
148173
self.children.iter().filter_map(|x| x.pid()).collect()

tests/test_macros.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ fn test_tls_set() {
134134
}
135135

136136
#[test]
137-
fn test_pipe() {
137+
fn test_pipe() -> CmdResult {
138138
assert!(run_cmd!(echo "xx").is_ok());
139139
assert_eq!(run_fun!(echo "xx").unwrap(), "xx");
140140
assert!(run_cmd!(echo xx | wc).is_ok());
@@ -271,6 +271,64 @@ fn test_pipe() {
271271
}
272272

273273
assert!(ok);
274+
275+
// test that illustrates the bugs in wait_with_pipe()
276+
// FIXME: make set_pipefail() thread safe, then move this to a separate test function
277+
assert!(spawn_with_output!(false)?.wait_with_all().0.is_err());
278+
assert!(spawn_with_output!(false)?.wait_with_output().is_err());
279+
assert!(spawn_with_output!(false)?
280+
.wait_with_raw_output(&mut vec![])
281+
.is_err());
282+
283+
// wait_with_pipe() can’t check the exit status of the last child
284+
assert!(spawn_with_output!(false)?
285+
.wait_with_pipe(&mut |_stdout| {})
286+
.is_ok());
287+
288+
// wait_with_pipe() kills the last child when the provided function returns
289+
assert!(spawn_with_output!(sh -c "while :; do :; done")?
290+
.wait_with_pipe(&mut |_stdout| {})
291+
.is_ok());
292+
293+
// wait_with_pipe_thread() checks the exit status of the last child, even if pipefail is disabled
294+
set_pipefail(false);
295+
assert!(spawn_with_output!(true | false)?
296+
.wait_with_pipe_thread(|_stdout| {})
297+
.is_err());
298+
assert!(spawn_with_output!(true | true)?
299+
.wait_with_pipe_thread(|_stdout| {})
300+
.is_ok());
301+
assert!(spawn_with_output!(false)?
302+
.wait_with_pipe_thread(|_stdout| {})
303+
.is_err());
304+
assert!(spawn_with_output!(true)?
305+
.wait_with_pipe_thread(|_stdout| {})
306+
.is_ok());
307+
set_pipefail(true);
308+
// wait_with_pipe_thread() checks the exit status of the other children, unless pipefail is disabled
309+
set_pipefail(false);
310+
assert!(spawn_with_output!(false | true)?
311+
.wait_with_pipe_thread(|_stdout| {})
312+
.is_ok());
313+
set_pipefail(true);
314+
assert!(spawn_with_output!(false | true)?
315+
.wait_with_pipe_thread(|_stdout| {})
316+
.is_err());
317+
assert!(spawn_with_output!(true | true)?
318+
.wait_with_pipe_thread(|_stdout| {})
319+
.is_ok());
320+
// wait_with_pipe_thread() handles `ignore`
321+
assert!(spawn_with_output!(ignore false | true)?
322+
.wait_with_pipe_thread(|_stdout| {})
323+
.is_ok());
324+
assert!(spawn_with_output!(ignore true | false)?
325+
.wait_with_pipe_thread(|_stdout| {})
326+
.is_ok());
327+
assert!(spawn_with_output!(ignore false)?
328+
.wait_with_pipe_thread(|_stdout| {})
329+
.is_ok());
330+
331+
Ok(())
274332
}
275333

276334
#[test]

0 commit comments

Comments
 (0)