Why does BufReader hang when reading from stderr? - rust

I want to execute a command and then capture any potential output to stderr. Here's what I have:
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!"),
}
}
}
I see the output being printed but the program then hangs. I'm suspecting that this may be related to the child process exiting and BufReader is unable to read an eof. A work around was to maintain an let mut num_lines = 0; and then increment this per read. After x-number of reads, I break in the for-loop but this doesn't seem very clean. How can I get BufReader to finish reading properly?

Neither of these may solve your issue, but I'll offer the advice regardless:
Pipe-Wait-Read can deadlock
Calling child.wait() will block execution until the child has exited, returning the exit status.
Using Stdio::piped() creates a new pipe for the stdout/stderr streams in order to be processed by the application. Pipes are handled by the operating system and are not infinite; if one end of the pipe is writing data but the other side isn't reading it, it will eventually block those writes until something is read.
This code can deadlock because you're waiting on the child process to exit, but it may not be able to if it becomes blocked trying to write to an output pipe thats full and not being read from.
As an example, this deadlocks on my system (a fairly standard ubuntu system that has 64KiB buffers for pipes):
// 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
}
There are plenty of alternatives:
Just read the output without waiting. reader.lines() will stop iterating when it reaches the end of the stream. You can then call child.wait() if you want to know the exit status.
Use .output() instead of .spawn(). This will block until the child has exited and return an Output holding the full stdout/stderr streams as Vec<u8>s.
You can process the output streams in separate threads while you're waiting for the child to exit. If that sounds good consider using tokio::process::Command.
See How do I read the output of a child process without blocking in Rust? for more info.
Don't swallow errors from .lines()
reader.lines() returns an iterator that yields a result for each line. One of the error states that could be somewhat handled is if the line wasn't properly utf-8 encoded, which will return something like this:
Err(
Custom {
kind: InvalidData,
error: "stream did not contain valid UTF-8",
},
)
However, any other error would be directly from the underlying reader and you should probably not continue iterating. Any error you receive is unlikely to be recoverable, and certainly not by continuing to ask for more lines.

Related

How to create future that completes once tokio::process::Child has exited without closing stdin

How can I create a future that completes upon the termination of a tokio::process::Child without closing stdin. I know there is try_wait for testing if a process has terminated without closing stdin, but I want to have this behavior with future semantics.
I tried to prepare a MRE for this question where my code panics as a result of writing to stdin after calling wait, but what I observe does not match the behavior stated in the documentation for tokio::process::Child's wait method. I would expect to see that the line stdin.write_u8(24).await.unwrap(); crashes with a broken pipe since stdin should have been closed by wait.
use tokio::{time, io::AsyncWriteExt}; // 1.0.1
use std::time::Duration;
#[tokio::main]
pub async fn main() {
let mut child = tokio::process::Command::new("nano")
.stdin(std::process::Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
let tasklet = tokio::spawn(async move {
child.wait().await
});
// this delay should give tokio::spawn plenty of time to spin up
// and call `wait` on the child (closing stdin)
time::sleep(Duration::from_millis(1000)).await;
// write character 24 (CANcel, ^X) to stdin to close nano
stdin.write_u8(24).await.unwrap();
match tasklet.await {
Ok(exit_result) => match exit_result {
Ok(exit_status) => eprintln!("exit_status: {}", exit_status),
Err(terminate_error) => eprintln!("terminate_error: {}", terminate_error)
}
Err(join_error) => eprintln!("join_error: {}", join_error)
}
}
So the answer to this question is to Option::take ChildStdin out of tokio::process::Child as described in this Github issue. In this case, wait will not close stdin and the programmer is responsible for not causing deadlocks.
The MRE above doesn't fail for two reasons: (i) I took ChildStdin out of tokio::process::Child and (ii) even if I hadn't taken it out, it still would not have been closed due to a bug in the code that will be fixed in this pull request.

Runtime returning without completing all tasks despite using block_on in Tokio

I am writing a multi-threaded concurrent Kafka producer using Rust and Tokio. The project has 2 modes, an interactive mode that runs in an infinite loop and a file mode which takes a file as an argument and then reads the file and sends these messages to Kafka via multiple threads. Interactive mode works fine! but file mode has issues.
To achieve this, I had initially started with Rayon, but then switched to a more flexible runtime; tokio. Now, I am able to parallelize the task of sending data over a specified number of threads within tokio, however, it seems that runtime is getting dropped before all messages are produced. Here is my code:
pub fn worker(brokers: String, f: File, t: usize, topic: Arc<String>) {
let reader = BufReader::new(f);
let mut rt = runtime::Builder::new()
.threaded_scheduler()
.core_threads(t)
.build()
.unwrap();
let producers: Arc<Vec<Mutex<BaseProducer>>> = Arc::new(
(0..t)
.map(|_| get_producer(&brokers))
.collect::<Vec<Mutex<BaseProducer>>>(),
);
let acounter = atomic::AtomicUsize::new(0);
let _results: Vec<_> = reader
.lines()
.map(|line| line.unwrap())
.map(move |line| {
let prods = producers.clone();
let tp = topic.clone();
let cnt = acounter.swap(
(acounter.load(atomic::Ordering::SeqCst) + 1) % t,
atomic::Ordering::SeqCst,
);
rt.block_on(async move {
match prods[cnt]
.lock()
.unwrap()
.send(BaseRecord::to(&(*tp)).payload(&line).key(""))
{
Ok(_) => (),
Err(e) => eprintln!("{:?}", e),
};
})
})
.collect();
}
fn get_producer(brokers: &String) -> Mutex<BaseProducer> {
Mutex::new(
BaseProducer::from_config(
ClientConfig::new()
.set("bootstrap.servers", &brokers)
.set("message.timeout.ms", "5000"),
)
.expect("Producer creation error"),
)
}
As a high-level walkthrough: I create mutable producers equal to the number of threads specified and every task within this thread will use one of these producers. The file is read line by line sequentially and every line is moved into the closure that produces it as a message to Kafka.
The code works fine, for the most part, but there are issues related to the runtime exiting without completing all tasks, even when I am using the block_on function in the runtime. Which is supposed to block until the future is complete (Async block in my case here).
I believe the issue is that the issue is with runtime getting dropped without all the threading within Tokio exiting successfully.
I tried reading a file with this approach habing 100,000 records, on a single thread, I was able to produce 28,000 records. On 2 threads, close to 46,000 records. And while utilising all 8 cores of my CPU, I was getting 99,000-100,000 messages indeterministically.
I have checked several answers on SO, but none help in my case. I also read through the documentation of tokio::runtime::Runtime here and tried to use spawn and then use futures::future::join, but that didn't work either.
Any help is appreciated!

Why does Command.output() execution sometimes return None for status.code()

My rust project uses Command to execute a process.
Sometimes (low frequency) when I run this code the call to status.code() returns None. I am usually using Mac OS Catalina Beta 1, rustc 1.36.0 - but it happens in Travis too (will have to go and find logs of OS/rustc there).
I was treating this as an error but "randomly" it would cause local and travis builds to fail, so now I'm ignoring it - but it would be nice to understand what's causing it.
In failure cases, re-running immediately will cause it to succeed.
let output = Command::new(&command)
.args(command_args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::piped())
.output()
.chain_err(|| "Error while attempting to spawn command to compile and run flow")?;
match output.status.code() {
Some(0) => Ok("Flow ran to completion".to_string()),
Some(code) => {
error!(
"Process STDERR:\n{}",
String::from_utf8_lossy(&output.stderr)
);
bail!("Exited with status code: {}", code)
}
None => Ok("No return code - ignoring".to_string()),
}
My question is not why this could happen (I know that the docs say "terminated by signal") but why it is happening, as no-one AFAIK is sending a signal to it, I seriously doubt any OOM or other such issues.
Read the manual:
On Unix, this will return None if the process was terminated by a signal; std::os::unix provides an extension trait for extracting the signal and other details from the ExitStatus.
use std::os::unix::process::ExitStatusExt;
use std::process::Command;
fn main() {
let mut child = Command::new("sleep")
.args(&["10"])
.spawn()
.expect("failed to spawn child");
child.kill().expect("failed to kill on child");
let status = child.wait().expect("failed to wait on child");
match status.code() {
None => {
println!("{:?}", status.signal());
()
}
_ => (),
}
}
You could use from_c_int() to have a pretty print of the signal type.

How can I stop reading from a tokio::io::lines stream?

I want to terminate reading from a tokio::io::lines stream. I merged it with a oneshot future and terminated it, but tokio::run was still working.
use futures::{sync::oneshot, *}; // 0.1.27
use std::{io::BufReader, time::Duration};
use tokio::prelude::*; // 0.1.21
fn main() {
let (tx, rx) = oneshot::channel::<()>();
let lines = tokio::io::lines(BufReader::new(tokio::io::stdin()));
let lines = lines.for_each(|item| {
println!("> {:?}", item);
Ok(())
});
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(5000));
println!("system shutting down");
let _ = tx.send(());
});
let lines = lines.select2(rx);
tokio::run(lines.map(|_| ()).map_err(|_| ()));
}
How can I stop reading from this?
There's nothing wrong with your strategy, but it will only work with futures that don't execute a blocking operation via Tokio's blocking (the traditional kind of blocking should never be done inside a future).
You can test this by replacing the tokio::io::lines(..) future with a simple interval future:
let lines = Interval::new(Instant::now(), Duration::from_secs(1));
The problem is that tokio::io::Stdin internally uses tokio_threadpool::blocking .
When you use Tokio thread pool blocking (emphasis mine):
NB: The entire task that called blocking is blocked whenever the
supplied closure blocks, even if you have used future combinators such
as select - the other futures in this task will not make progress
until the closure returns. If this is not desired, ensure that
blocking runs in its own task (e.g. using
futures::sync::oneshot::spawn).
Since this will block every other future in the combinator, your Receiver will not be able to get a signal from the Senderuntil the blocking ends.
Please see How can I read non-blocking from stdin? or you can use tokio-stdin-stdout, which creates a channel to consume data from stdin thread. It also has a line-by-line example.
Thank you for your comment and correcting my sentences.
I tried to stop this non-blocking Future and succeeded.
let lines = Interval::new(Instant::now(), Duration::from_secs(1));
My understating is that it would work for this case to wrap the blocking Future with tokio threadpool::blocking.
I'll try it later.
Thank you very much.

How can I read non-blocking from stdin?

Is there a way to check whether data is available on stdin in Rust, or to do a read that returns immediately with the currently available data?
My goal is to be able to read the input produced for instance by cursor keys in a shell that is setup to return all read data immediately. For instance with an equivalent to: stty -echo -echok -icanon min 1 time 0.
I suppose one solution would be to use ncurses or similar libraries, but I would like to avoid any kind of large dependencies.
So far, I got only blocking input, which is not what I want:
let mut reader = stdin();
let mut s = String::new();
match reader.read_to_string(&mut s) {...} // this blocks :(
Converting OP's comment into an answer:
You can spawn a thread and send data over a channel. You can then poll that channel in the main thread using try_recv.
use std::io;
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::TryRecvError;
use std::{thread, time};
fn main() {
let stdin_channel = spawn_stdin_channel();
loop {
match stdin_channel.try_recv() {
Ok(key) => println!("Received: {}", key),
Err(TryRecvError::Empty) => println!("Channel empty"),
Err(TryRecvError::Disconnected) => panic!("Channel disconnected"),
}
sleep(1000);
}
}
fn spawn_stdin_channel() -> Receiver<String> {
let (tx, rx) = mpsc::channel::<String>();
thread::spawn(move || loop {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer).unwrap();
tx.send(buffer).unwrap();
});
rx
}
fn sleep(millis: u64) {
let duration = time::Duration::from_millis(millis);
thread::sleep(duration);
}
Most operating systems default to work with the standard input and output in a blocking way. No wonder then that the Rust library follows in stead.
To read from a blocking stream in a non-blocking way you might create a separate thread, so that the extra thread blocks instead of the main one. Checking whether a blocking file descriptor produced some input is similar: spawn a thread, make it read the data, check whether it produced any data so far.
Here's a piece of code that I use with a similar goal of processing a pipe output interactively and that can hopefully serve as an example. It sends the data over a channel, which supports the try_recv method - allowing you to check whether the data is available or not.
Someone has told me that mio might be used to read from a pipe in a non-blocking way, so you might want to check it out too. I suspect that passing the stdin file descriptor (0) to Receiver::from_raw_fd should just work.
You could also potentially look at using ncurses (also on crates.io) which would allow you read in raw mode. There are a few examples in the Github repository which show how to do this.

Resources