【问题标题】:Why does BufReader hang when reading from stderr?为什么从 stderr 读取时 BufReader 挂起?
【发布时间】:2020-10-17 04:00:57
【问题描述】:

我想执行一个命令,然后将任何潜在的输出捕获到 stderr。这是我所拥有的:

if let Ok(ref mut child) = Command::new("ssh")
            .args(&[
              "some_args",
              "more_args"
            ])
            .stderr(Stdio::piped())
            .spawn()
        {
            let output = child.wait().expect("ssh command not running");
            let reader = BufReader::new(child.stderr.take().expect("failed to capture stderr"));
            for line in reader.lines() {
                match line {
                    Ok(line_str) => println!("output: {}", line_str);
                    Err(e) => println!("output failed!"),
                }
            }
        }

我看到正在打印输出,但程序随后挂起。我怀疑这可能与子进程退出和 BufReader 无法读取 eof 有关。一种解决方法是维护let mut num_lines = 0;,然后在每次读取时增加它。在 x 次读取后,我中断了 for 循环,但这似乎不是很干净。如何让 BufReader 正确完成阅读?

【问题讨论】:

  • 您可以验证您的 ssh 命令是否退出?它对任何其他命令执行此操作吗?有很多错误输出吗?它实际上挂在reader.lines()child.wait() 上吗?我看到了一些陷阱,但我想确保我正在解决正确的问题
  • 实际上再读一遍,我不确定是否理解症状。如果等待孩子完成,那么出售的蒸汽将被关闭,并且 lines() 不应该挂起......如果可能的话,请解释一下制作一个 MCVE。
  • 您是否正在执行远程命令并注销参数?因为这是完全有可能的,如果不是,您只是拨打另一台计算机并且永远不会结束会话。

标签: rust


【解决方案1】:

这些都不能解决您的问题,但无论如何我都会提供建议:

Pipe-Wait-Read 可能会死锁

调用child.wait() 将阻塞执行,直到子进程退出,返回退出状态。

使用Stdio::piped() 为stdout/stderr 流创建一个新管道,以便由应用程序处理。管道由操作系统处理,不是无限的;如果管道的一端正在写入数据但另一端没有读取它,它最终会阻止这些写入,直到读取到某些内容。

此代码可能会死锁,因为您正在等待子进程退出,但如果它在尝试写入已满且未被读取的输出管道时被阻塞,则它可能无法死锁。

作为一个例子,我的系统(一个相当标准的 ubuntu 系统,有 64KiB 的管道缓冲区)上出现死锁:

// create a simple child proccess that sends 64KiB+1 random bytes to stdout
let mut child = Command::new("dd")
    .args(&["if=/dev/urandom", "count=65537", "bs=1", "status=none"])
    .stdout(Stdio::piped())
    .spawn()
    .expect("failed to execute dd");

let _status = child.wait(); // hangs indefinitely
let reader = BufReader::new(child.stdout.take().expect("failed to capture stdout"));
for _line in reader.lines() {
    // do something
}

有很多选择:

  • 无需等待即可读取输出。 reader.lines() 将在到达流的末尾时停止迭代。如果您想了解退出状态,可以拨打child.wait()

  • 使用.output() 而不是.spawn()。这将阻塞,直到孩子退出并返回一个Output,将完整的stdout/stderr 流保存为Vec<u8>s。

  • 您可以在等待子进程退出时在单独的线程中处理输出流。如果这听起来不错,请考虑使用tokio::process::Command

请参阅How do I read the output of a child process without blocking in Rust? 了解更多信息。

不要吞下来自.lines() 的错误

reader.lines() 返回一个迭代器,它为每一行产生一个结果。 可以处理的错误状态之一是,如果该行未正确 utf-8 编码,则会返回如下内容:

Err(
    Custom {
        kind: InvalidData,
        error: "stream did not contain valid UTF-8",
    },
)

但是,任何其他错误都将直接来自底层阅读器,您可能应该继续迭代。您收到的任何错误都不太可能恢复,当然也不能通过继续请求更多行来恢复。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-28
    相关资源
    最近更新 更多