Rust rayon tcp blocking - multithreading

I'm writing a program which executes a command on server using ssh and gets the output.
The part I don't understand is lower in the code.
If the function waits and then returns a string, it works as expected, but if work with TCP it starts performing very bad. I expect that using 100 threads on 100 hosts will perform 100 times faster,
because will open 100 sockets simultaneously.
In the sleep version, changing poolThreads directly affects the time of execution. In the version with TCP streams, changing pool from 1 to 100 with 100 hosts only speeds it up from 90 to 67 because some hosts are offline.
I read the documentation, but cannot find anything to help me.
use clap::{App, Arg};
use rayon::prelude::*;
use rayon::ThreadPoolBuilder;
use ssh2::Session;
use std::fmt::Display;
use std::io::prelude::*;
use std::io::{BufReader, Read};
use std::net::{TcpStream, ToSocketAddrs};
use std::thread::sleep;
use std::time::Duration;
fn process_host<A>(hostname: A) -> Result<String, String>
where
A: ToSocketAddrs + Display,
{
sleep(Duration::from_secs(1));
return Ok(hostname.to_string());
// here is the problem
// -----------------------------------------------------------
let tcp = match TcpStream::connect(&hostname) {
Ok(a) => a,
Err(e) => {
return Err(format!("{}:{}", hostname, e).to_string());
}
};
let mut sess = match Session::new() {
Ok(a) => a,
Err(e) => {
// todo logging
return Err(format!("{}:{}", hostname, e).to_string());
}
};
sess.set_tcp_stream(tcp);
match sess.handshake() {
Ok(a) => a,
Err(e) => {
return Err(format!("{}:{}", hostname, e).to_string());
}
};
Ok(format!("{}", hostname))
}
fn main() {
let hosts = vec!["aaaaa:22", "bbbbbbb:22"];
let pool = ThreadPoolBuilder::new()
.num_threads(10)
.build()
.expect("failed creating pool");
pool.install(|| {
hosts
.par_iter()
.map(|x| process_host(x))
.for_each(|x| println!("{:?}", x))
});
}

To debug a such a problem, you need to analysis, where your program wastes its time.
There're to ways:
profiling and analyzing of TCP connections.
I suggest you 2 way, cause it's much easier.
Dump traffic with wireshark, filter it by port 22.
After this, use conversations
tab. Here you can sort connections by time, and can see, that program doesn't speedup because of no time limit for ssh connection.

Related

Establish TCP connection to computer on different network without the recipient having to go into their router settings?

Overview
I'm trying to establish a connection between two computers on different networks without requiring the recipient to have to mess with their router's port forwarding settings
Scenario
I'm currently using a Rust script to establish a tcp connection between 2 ports on the same computer and all that it is supposed to do is print Hello, world! to the terminal running recipient.exe. It works perfectly.
Question
Say I distribute this application to a bunch of users. Each user would have both recipient.exe and sender.exe, and would run either depending on if they wanted to send or receive information to/from another user.
How do I let one user connect to the other without having to make them open their router settings?
Is there a different protocol I can use? Is there a universally-open port I can use? Can the machine running the program force the router to open a port temporarily?
I've wondered this for a while and I finally ran into actually needing to solve it
recipient.rs
use std::net::TcpStream;
use std::net::TcpListener;
use std::io::{Error, Read, Write};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
println!("Connection established!");
handler(stream).unwrap_or_else(|error| eprintln!("{:?}", error));
}
}
fn handler(mut stream: TcpStream) -> Result<(), Error> {
println!("Connection from {}", stream.peer_addr()?);
let mut buffer = [0; 128];
loop {
let nbytes = stream.read(&mut buffer)?;
if nbytes == 0 {
return Ok(());
}
print!("Received data: ");
for i in 0..nbytes {
print!("{}", buffer[i] as char);
}
println!("");
stream.flush()?;
}
}
sender.rs
use std::io::prelude::*;
use std::net::TcpStream;
fn main() -> std::io::Result<()> {
let mut stream = TcpStream::connect("127.0.0.1:7878")?;
stream.write(&"Hello, world!".as_bytes())?;
stream.read(&mut [0; 128])?;
Ok(())
}

Determining the End-of-stream

I made a loop for a webserver.
On a windows client I didn't have any problems but on a linux client the server didn't responding to requests.
The problem: I found out that if request_size % buffer_size == 0 then the loop runs once more waiting for more data.
The question: Is there an efficient way of reading data that takes into consideration slow connections, connections that drop packages. (Not just using non_blocking or nodelay.)
let listener = TcpListener::bind("127.0.0.1:80").unwrap();
while let Ok((mut stream, _)) = listener.accept() {
let mut data: Vec<u8> = Vec::new();
let mut buf = [0u8; 32];
while let Ok(size) = stream.read(&mut buf) {
data.extend(buf[..size].iter());
if size != buf.len() { break; }
}
// do something with the data
}
I could increase the buffer size but that wouldn't solve the problem.
First, to detect EOF reliably, you should test the returned size of Read::read against zero and not your buffer size, because if you have a 'slow connections' you might not get enough data to fill the entire buffer at once, causing your loop to quite early with an incomplete message in data.
There are essentially 3 ways to make sure you received the entire message:
Read until EOF
Read a fixed-sized message
Encode some 'content length' and read that many bytes
Notice, that only the last two variants allow your client to eventually send more data over the same stream. Also notice, that these two variants can be implemented comparably easy via Read::read_exact.
Besides notice, if you don't trust your client, it might be helpful to set up TcpStream::set_read_timeout with a reasonably long timeout (e.g. 2 min).
Read until EOF
This is probably the easiest and, according to your title and code, probably the method you are aiming for. However, to generate an EOF, the client must shutdown at least its write channel. So, if your server is stuck in read, I assume you forgot to shutdown your client (tho I have to guess here).
On the server side, if you really want to read until EOF, you don't need a loop yourself, you can simply use the Read::read_to_end utility function. Here is an example for a client & server with the client sending a single message terminated by EOF:
use std::io::Read;
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
// --- Client code
const SERVER_ADDR: &str = "localhost:1234";
pub fn client() {
let mut socket = TcpStream::connect(SERVER_ADDR).expect("Failed to connect");
// Send a 'single' message, the flushes kinda simulates a very slow connection
for _ in 0..3 {
socket.write(b"Hello").expect("Failed to send");
socket.flush().unwrap();
}
// Instead of shutdow, you can also drop(socket), but than you can't read.
socket.shutdown(std::net::Shutdown::Write).unwrap();
// go reading, or whatever
}
// --- Server code
const SERVER_BIND: &str = "127.0.0.1:1234";
pub fn server() {
let listener = TcpListener::bind(SERVER_BIND).expect("Failed to bind");
while let Ok((stream, _)) = listener.accept() {
let _ = handle_client(stream); // don't care if the client screwed up
}
}
pub fn handle_client(mut socket: TcpStream) -> std::io::Result<()> {
let mut data: Vec<u8> = Vec::new();
// Read all bytes until EOF
socket.read_to_end(&mut data)?;
println!("Data: {:?}", data); // or whatever
Ok(())
}

Spawning concurrent tokio tasks that reliably run

I implemented a toy UDP server that simply squirts out data over a set of sockets as fast as it can through several concurrent tasks. I naively thought this would allow efficient use of CPU resources by making use of the Tokio's threaded runtime:
use std::error::Error;
use std::io;
use tokio::net::UdpSocket;
struct Server {
socket: UdpSocket,
buf: Vec<u8>,
id: u32,
}
const T_LOOPS: usize = 100000;
impl Server {
async fn run(self) -> Result<(), io::Error> {
let Server {
mut socket,
buf,
id,
} = self;
let peer = "127.0.0.1:9876".to_string();
loop {
for _n in 0..T_LOOPS {
let _amt = socket.send_to(&buf[..], &peer).await?;
}
println!("server {} run {} loops", id, T_LOOPS);
}
}
}
#[tokio::main(max_threads=8)]
async fn main() -> Result<(), Box<dyn Error>> {
let addr = "0.0.0.0:0".to_string();
for n in 0u32..4u32 {
let socket = UdpSocket::bind(&addr).await?;
let server = Server {
socket: socket,
buf: vec![0; 1500],
id: n,
};
tokio::spawn(async move {
server.run().await
});
}
Ok(())
}
It practice, which tasks run seems to be fairly non deterministic. Generally the first spawned task runs, and any number of the other 3 runs (and not always in order). For example, the output might look something like:
server 0 run 100000 loops
server 0 run 100000 loops
server 0 run 100000 loops
or
server 1 run 100000 loops
server 0 run 100000 loops
server 2 run 100000 loops
server 1 run 100000 loops
server 2 run 100000 loops
server 0 run 100000 loops
but is not consistently what I need (which would show all 4 ids).
I can't help but feel I must be missing something here. I've played around with core-threads and max-threads with no useful result.
How do I make this type of system work reliably with Tokio (or async in general)?
Though this is a toy system, it's actually a stepping stone towards my desired use-case.

How can I asynchronously read from both stdout and stderr of a subprocess using Tokio? [duplicate]

I'm making a small ncurses application in Rust that needs to communicate with a child process. I already have a prototype written in Common Lisp. I'm trying to rewrite it because CL uses a huge amount of memory for such a small tool.
I'm having some trouble figuring out how to interact with the sub-process.
What I'm currently doing is roughly this:
Create the process:
let mut program = match Command::new(command)
.args(arguments)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(child) => child,
Err(_) => {
println!("Cannot run program '{}'.", command);
return;
}
};
Pass it to an infinite (until user exits) loop, which reads and handles input and listens for output like this (and writes it to the screen):
fn listen_for_output(program: &mut Child, output_viewer: &TextViewer) {
match program.stdout {
Some(ref mut out) => {
let mut buf_string = String::new();
match out.read_to_string(&mut buf_string) {
Ok(_) => output_viewer.append_string(buf_string),
Err(_) => return,
};
}
None => return,
};
}
The call to read_to_string however blocks the program until the process exits. From what I can see read_to_end and read also seem to block. If I try running something like ls which exits right away, it works, but with something that doesn't exit like python or sbcl it only continues once I kill the subprocess manually.
Based on this answer, I changed the code to use BufReader:
fn listen_for_output(program: &mut Child, output_viewer: &TextViewer) {
match program.stdout.as_mut() {
Some(out) => {
let buf_reader = BufReader::new(out);
for line in buf_reader.lines() {
match line {
Ok(l) => {
output_viewer.append_string(l);
}
Err(_) => return,
};
}
}
None => return,
}
}
However, the problem still remains the same. It will read all lines that are available, and then block. Since the tool is supposed to work with any program, there is no way to guess out when the output will end, before trying to read. There doesn't appear to be a way to set a timeout for BufReader either.
Streams are blocking by default. TCP/IP streams, filesystem streams, pipe streams, they are all blocking. When you tell a stream to give you a chunk of bytes it will stop and wait till it has the given amout of bytes or till something else happens (an interrupt, an end of stream, an error).
The operating systems are eager to return the data to the reading process, so if all you want is to wait for the next line and handle it as soon as it comes in then the method suggested by Shepmaster in Unable to pipe to or from spawned child process more than once (and also in his answer here) works.
Though in theory it doesn't have to work, because an operating system is allowed to make the BufReader wait for more data in read, but in practice the operating systems prefer the early "short reads" to waiting.
This simple BufReader-based approach becomes even more dangerous when you need to handle multiple streams (like the stdout and stderr of a child process) or multiple processes. For example, BufReader-based approach might deadlock when a child process waits for you to drain its stderr pipe while your process is blocked waiting on it's empty stdout.
Similarly, you can't use BufReader when you don't want your program to wait on the child process indefinitely. Maybe you want to display a progress bar or a timer while the child is still working and gives you no output.
You can't use BufReader-based approach if your operating system happens not to be eager in returning the data to the process (prefers "full reads" to "short reads") because in that case a few last lines printed by the child process might end up in a gray zone: the operating system got them, but they're not large enough to fill the BufReader's buffer.
BufReader is limited to what the Read interface allows it to do with the stream, it's no less blocking than the underlying stream is. In order to be efficient it will read the input in chunks, telling the operating system to fill as much of its buffer as it has available.
You might be wondering why reading data in chunks is so important here, why can't the BufReader just read the data byte by byte. The problem is that to read the data from a stream we need the operating system's help. On the other hand, we are not the operating system, we work isolated from it, so as not to mess with it if something goes wrong with our process. So in order to call to the operating system there needs to be a transition to "kernel mode" which might also incur a "context switch". That is why calling the operating system to read every single byte is expensive. We want as few OS calls as possible and so we get the stream data in batches.
To wait on a stream without blocking you'd need a non-blocking stream. MIO promises to have the required non-blocking stream support for pipes, most probably with PipeReader, but I haven't checked it out so far.
The non-blocking nature of a stream should make it possible to read data in chunks regardless of whether the operating system prefers the "short reads" or not. Because non-blocking stream never blocks. If there is no data in the stream it simply tells you so.
In the absense of a non-blocking stream you'll have to resort to spawning threads so that the blocking reads would be performed in a separate thread and thus won't block your primary thread. You might also want to read the stream byte by byte in order to react to the line separator immediately in case the operating system does not prefer the "short reads". Here's a working example: https://gist.github.com/ArtemGr/db40ae04b431a95f2b78.
P.S. Here's an example of a function that allows to monitor the standard output of a program via a shared vector of bytes:
use std::io::Read;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
/// Pipe streams are blocking, we need separate threads to monitor them without blocking the primary thread.
fn child_stream_to_vec<R>(mut stream: R) -> Arc<Mutex<Vec<u8>>>
where
R: Read + Send + 'static,
{
let out = Arc::new(Mutex::new(Vec::new()));
let vec = out.clone();
thread::Builder::new()
.name("child_stream_to_vec".into())
.spawn(move || loop {
let mut buf = [0];
match stream.read(&mut buf) {
Err(err) => {
println!("{}] Error reading from stream: {}", line!(), err);
break;
}
Ok(got) => {
if got == 0 {
break;
} else if got == 1 {
vec.lock().expect("!lock").push(buf[0])
} else {
println!("{}] Unexpected number of bytes: {}", line!(), got);
break;
}
}
}
})
.expect("!thread");
out
}
fn main() {
let mut cat = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("!cat");
let out = child_stream_to_vec(cat.stdout.take().expect("!stdout"));
let err = child_stream_to_vec(cat.stderr.take().expect("!stderr"));
let mut stdin = match cat.stdin.take() {
Some(stdin) => stdin,
None => panic!("!stdin"),
};
}
With a couple of helpers I'm using it to control an SSH session:
try_s! (stdin.write_all (b"echo hello world\n"));
try_s! (wait_forĖ¢ (&out, 0.1, 9., |s| s == "hello world\n"));
P.S. Note that await on a read call in async-std is blocking as well. It's just instead of blocking a system thread it only blocks a chain of futures (a stack-less green thread essentially). The poll_read is the non-blocking interface. In async-std#499 I've asked the developers whether there's a short read guarantee from these APIs.
P.S. There might be a similar concern in Nom: "we would want to tell the IO side to refill according to the parser's result (Incomplete or not)"
P.S. Might be interesting to see how stream reading is implemented in crossterm. For Windows, in poll.rs, they are using the native WaitForMultipleObjects. In unix.rs they are using mio poll.
Tokio's Command
Here is an example of using tokio 0.2:
use std::process::Stdio;
use futures::StreamExt; // 0.3.1
use tokio::{io::BufReader, prelude::*, process::Command}; // 0.2.4, features = ["full"]
#[tokio::main]
async fn main() {
let mut cmd = Command::new("/tmp/slow.bash")
.stdout(Stdio::piped()) // Can do the same for stderr
.spawn()
.expect("cannot spawn");
let stdout = cmd.stdout().take().expect("no stdout");
// Can do the same for stderr
// To print out each line
// BufReader::new(stdout)
// .lines()
// .for_each(|s| async move { println!("> {:?}", s) })
// .await;
// To print out each line *and* collect it all into a Vec
let result: Vec<_> = BufReader::new(stdout)
.lines()
.inspect(|s| println!("> {:?}", s))
.collect()
.await;
println!("All the lines: {:?}", result);
}
Tokio-Threadpool
Here is an example of using tokio 0.1 and tokio-threadpool. We start the process in a thread using the blocking function. We convert that to a stream with stream::poll_fn
use std::process::{Command, Stdio};
use tokio::{prelude::*, runtime::Runtime}; // 0.1.18
use tokio_threadpool; // 0.1.13
fn stream_command_output(
mut command: Command,
) -> impl Stream<Item = Vec<u8>, Error = tokio_threadpool::BlockingError> {
// Ensure that the output is available to read from and start the process
let mut child = command
.stdout(Stdio::piped())
.spawn()
.expect("cannot spawn");
let mut stdout = child.stdout.take().expect("no stdout");
// Create a stream of data
stream::poll_fn(move || {
// Perform blocking IO
tokio_threadpool::blocking(|| {
// Allocate some space to store anything read
let mut data = vec![0; 128];
// Read 1-128 bytes of data
let n_bytes_read = stdout.read(&mut data).expect("cannot read");
if n_bytes_read == 0 {
// Stdout is done
None
} else {
// Only return as many bytes as we read
data.truncate(n_bytes_read);
Some(data)
}
})
})
}
fn main() {
let output_stream = stream_command_output(Command::new("/tmp/slow.bash"));
let mut runtime = Runtime::new().expect("Unable to start the runtime");
let result = runtime.block_on({
output_stream
.map(|d| String::from_utf8(d).expect("Not UTF-8"))
.fold(Vec::new(), |mut v, s| {
print!("> {}", s);
v.push(s);
Ok(v)
})
});
println!("All the lines: {:?}", result);
}
There's numerous possible tradeoffs that can be made here. For example, always allocating 128 bytes isn't ideal, but it's simple to implement.
Support
For reference, here's slow.bash:
#!/usr/bin/env bash
set -eu
val=0
while [[ $val -lt 10 ]]; do
echo $val
val=$(($val + 1))
sleep 1
done
See also:
How do I synchronously return a value calculated in an asynchronous Future in stable Rust?
If Unix support is sufficient, you can also make the two output streams as non-blocking and poll over them as you would do it on TcpStream with the set_nonblocking function.
The ChildStdout and ChildStderr returned by the Command spawn are Stdio (and contain a file descriptor), you can modify directly the read behavior of these handle to make it non-blocking.
Based on the work of jcreekmore/timeout-readwrite-rs and anowell/nonblock-rs, I use this wrapper to modify the stream handles:
extern crate libc;
use std::io::Read;
use std::os::unix::io::AsRawFd;
use libc::{F_GETFL, F_SETFL, fcntl, O_NONBLOCK};
fn set_nonblocking<H>(handle: &H, nonblocking: bool) -> std::io::Result<()>
where
H: Read + AsRawFd,
{
let fd = handle.as_raw_fd();
let flags = unsafe { fcntl(fd, F_GETFL, 0) };
if flags < 0 {
return Err(std::io::Error::last_os_error());
}
let flags = if nonblocking{
flags | O_NONBLOCK
} else {
flags & !O_NONBLOCK
};
let res = unsafe { fcntl(fd, F_SETFL, flags) };
if res != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
You can manage the two streams as any other non-blocking stream. The following example is based on the polling crate which makes really easy to handle read event and BufReader for line reading:
use std::process::{Command, Stdio};
use std::path::PathBuf;
use std::io::{BufReader, BufRead};
use std::thread;
extern crate polling;
use polling::{Event, Poller};
fn main() -> Result<(), std::io::Error> {
let path = PathBuf::from("./worker.sh").canonicalize()?;
let mut child = Command::new(path)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to start worker");
let handle = thread::spawn({
let stdout = child.stdout.take().unwrap();
set_nonblocking(&stdout, true)?;
let mut reader_out = BufReader::new(stdout);
let stderr = child.stderr.take().unwrap();
set_nonblocking(&stderr, true)?;
let mut reader_err = BufReader::new(stderr);
move || {
let key_out = 1;
let key_err = 2;
let mut out_closed = false;
let mut err_closed = false;
let poller = Poller::new().unwrap();
poller.add(reader_out.get_ref(), Event::readable(key_out)).unwrap();
poller.add(reader_err.get_ref(), Event::readable(key_err)).unwrap();
let mut line = String::new();
let mut events = Vec::new();
loop {
// Wait for at least one I/O event.
events.clear();
poller.wait(&mut events, None).unwrap();
for ev in &events {
// stdout is ready for reading
if ev.key == key_out {
let len = match reader_out.read_line(&mut line) {
Ok(len) => len,
Err(e) => {
println!("stdout read returned error: {}", e);
0
}
};
if len == 0 {
println!("stdout closed (len is null)");
out_closed = true;
poller.delete(reader_out.get_ref()).unwrap();
} else {
print!("[STDOUT] {}", line);
line.clear();
// reload the poller
poller.modify(reader_out.get_ref(), Event::readable(key_out)).unwrap();
}
}
// stderr is ready for reading
if ev.key == key_err {
let len = match reader_err.read_line(&mut line) {
Ok(len) => len,
Err(e) => {
println!("stderr read returned error: {}", e);
0
}
};
if len == 0 {
println!("stderr closed (len is null)");
err_closed = true;
poller.delete(reader_err.get_ref()).unwrap();
} else {
print!("[STDERR] {}", line);
line.clear();
// reload the poller
poller.modify(reader_err.get_ref(), Event::readable(key_err)).unwrap();
}
}
}
if out_closed && err_closed {
println!("Stream closed, exiting process thread");
break;
}
}
}
});
handle.join().unwrap();
Ok(())
}
Additionally, used with a wrapper over an EventFd, it becomes possible to easily stop the process from another thread without blocking nor active polling and uses and only a single thread.
EDIT: It seems the polling crate sets automatically the polled handles in non-blocking mode following my tests. The set_nonblocking function is still useful in case you want to directly use the nix::poll object.
I have encountered enough use-cases where it was useful to interact with a subprocess over line-delimited text that I wrote a crate for it, interactive_process.
I expect the original problem has long since been solved, but I thought it might be helpful to others.

Delay future returns Error(shutdown) when polled

I'm rewriting the Forward future from a Stream to a Sink adding a timeout on the reception of new items.
I'm using a Delay future inside the Forward struct to keep track of the timeout, but the result is always an Error(Shutdown).
From the timer::Error documentation I see that this should return only if the future is dropped but it should not be the case since it's still contained in the main struct.
This is a minimal test example from which I get the same result.
extern crate tokio; // 0.1.20
use std::thread::sleep;
use std::time::{Duration, Instant};
use tokio::prelude::*;
use tokio::timer::Delay;
fn main() {
let mut delay = Delay::new(Instant::now() + Duration::from_millis(3000));
sleep(Duration::from_millis(1000));
match delay.poll() {
Ok(Async::NotReady) => println!("Not ready"),
Ok(Async::Ready(v)) => println!("Ready: {:?}", v),
Err(e) => println!("{:?}", e),
}
}
You can see a running example here
I'm expecting to either receive an Async:Ready(v) where v is ().
A Delay works together with a Timer and the error here indicates that it was unable to register with one. While the docs sometimes assume that you're working within a tokio context, they do mention this requirement:
These types must be used from within the context of the Runtime or a timer context must be setup explicitly. See the tokio-timer crate for more details on how to setup a timer context.
If you wanted to avoid the error, it would be enough to run it inside a task:
fn main() {
tokio::run(futures::future::lazy(|| {
let mut delay = Delay::new(Instant::now() + Duration::from_millis(3000));
sleep(Duration::from_millis(1000));
// "Not ready"
match delay.poll() {
Ok(Async::NotReady) => println!("Not ready"),
Ok(Async::Ready(v)) => println!("Ready: {:?}", v),
Err(e) => println!("{:?}", e),
}
Ok(())
}))
}
However this will not transition to ready even if you wait more than 3000 milliseconds. If you want to use the Runtime's Timer you'll have to spawn it as a task:
fn main() {
// "Ready: ()"
tokio::run(
Delay::new(Instant::now() + Duration::from_millis(3000))
.map(|v| println!("Ready: {:?}", v))
.map_err(|e| println!("{:?}", e)),
)
}

Resources