Skip to content

Commit dda9d05

Browse files
authored
Rollup merge of #82943 - kornelski:threadstdio, r=joshtriplett
Demonstrate best practice for feeding stdin of a child processes Documentation change. It's possible to create a deadlock with stdin/stdout I/O on a single thread: * the child process may fill its stdout buffer, and have to wait for the parent process to read it, * but the parent process may be waiting until its stdin write finishes before reading the stdout. Therefore, the parent process should use separate threads for writing and reading. These examples are not deadlocking in practice, because they use short strings, but I think it's better to demonstrate code that works even for long writes. The problem is non-obvious and tricky to debug (it seems that even libstd has a similar issue: #45572). This also demonstrates how to use stdio with threads: it's not obvious that `.take()` can be used to avoid fighting with the borrow checker. I've checked that the modified examples run fine.
2 parents 9ce0820 + ce2d95c commit dda9d05

File tree

1 file changed

+18
-7
lines changed

1 file changed

+18
-7
lines changed

library/std/src/process.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,15 @@
7171
//! .spawn()
7272
//! .expect("failed to execute child");
7373
//!
74-
//! {
75-
//! // limited borrow of stdin
76-
//! let stdin = child.stdin.as_mut().expect("failed to get stdin");
74+
//! // If the child process fills its stdout buffer, it may end up
75+
//! // waiting until the parent reads the stdout, and not be able to
76+
//! // read stdin in the meantime, causing a deadlock.
77+
//! // Writing from another thread ensures that stdout is being read
78+
//! // at the same time, avoiding the problem.
79+
//! let mut stdin = child.stdin.take().expect("failed to get stdin");
80+
//! std::thread::spawn(move || {
7781
//! stdin.write_all(b"test").expect("failed to write to stdin");
78-
//! }
82+
//! });
7983
//!
8084
//! let output = child
8185
//! .wait_with_output()
@@ -1145,14 +1149,21 @@ impl Stdio {
11451149
/// .spawn()
11461150
/// .expect("Failed to spawn child process");
11471151
///
1148-
/// {
1149-
/// let stdin = child.stdin.as_mut().expect("Failed to open stdin");
1152+
/// let mut stdin = child.stdin.take().expect("Failed to open stdin");
1153+
/// std::thread::spawn(move || {
11501154
/// stdin.write_all("Hello, world!".as_bytes()).expect("Failed to write to stdin");
1151-
/// }
1155+
/// });
11521156
///
11531157
/// let output = child.wait_with_output().expect("Failed to read stdout");
11541158
/// assert_eq!(String::from_utf8_lossy(&output.stdout), "!dlrow ,olleH");
11551159
/// ```
1160+
///
1161+
/// Writing more than a pipe buffer's worth of input to stdin without also reading
1162+
/// stdout and stderr at the same time may cause a deadlock.
1163+
/// This is an issue when running any program that doesn't guarantee that it reads
1164+
/// its entire stdin before writing more than a pipe buffer's worth of output.
1165+
/// The size of a pipe buffer varies on different targets.
1166+
///
11561167
#[stable(feature = "process", since = "1.0.0")]
11571168
pub fn piped() -> Stdio {
11581169
Stdio(imp::Stdio::MakePipe)

0 commit comments

Comments
 (0)