rust-lang / rust-lang/rust

Command::spawn() does not clear stdout's buffer on child process

Open
#148,278 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-gub needs-triage
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Command::spawn() doesn't do any cleanup on the stdout buffer. This is normally not a problem, because usually the buffer is discarded when the subcommand is executed, however this can result in duplicated output when printing from pre_exec(), as demonstrated by the following example:

I tried this code:

use std::os::unix::process::CommandExt;
use std::process::Command;

fn main() {
    print!("hello ");
    unsafe { 
        Command::new("cat")
            .arg("/dev/null")
            .pre_exec(|| {
                println!("from child");
                Ok(())
            })
            .spawn()
            .expect("spawn failed")
    };
    println!("from parent");
}

I would expect the output to be similar to this:

from child
hello from parent

but instead the actual output is this:

hello from child
hello from parent

"hello " is repeated twice because the stdout buffer is preserved when the process is forked.

On a related note, forgetting to flush in pre_exec can also lead to the data printed in the child process to be lost:

use std::os::unix::process::CommandExt;
use std::process::Command;

fn main() {
    unsafe {
        Command::new("cat")
            .arg("/dev/null")
            .pre_exec(|| {
                print!("hello");
                Ok(())
            })
            .spawn()
            .expect("spawn failed")
    };
}

The code above prints nothing, while the following does print hello as expected:

use std::io::Write;
use std::io::stdout;
use std::os::unix::process::CommandExt;
use std::process::Command;
    
fn main() {
    unsafe {
        Command::new("cat")
            .arg("/dev/null")
            .pre_exec(|| {
                print!("hello");
                stdout().lock().flush()
            })
            .spawn()
            .expect("spawn failed")
    };
}

Given the unsafe/precarious nature of pre_exec() one might argue that this second issue (not flushing before exec) may be acceptable, but it's undocumented and I think it's a bit unexpected because println and print lead to very different outcomes.

(For context, this was discovered while working on #148274)

Meta

rustc --version --verbose:

rustc 1.90.0 (1159e78c4 2025-09-14)
binary: rustc
commit-hash: 1159e78c4747b02ef996e55082b704c09b970588
commit-date: 2025-09-14
host: x86_64-unknown-linux-gnu
release: 1.90.0
LLVM version: 20.1.8

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No files or tests are named. Start by running the provided Unix reproducer and tracing std::process::Command::spawn() with pre_exec(); done should address the duplicated buffered stdout behavior and decide or document the separate child-flush behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.