Rust tokio_serial: async fn readable does not block execution. Runs with 100% CPU load

53 views Asked by At

I'm trying to implement something that shall handle a UART communication on a Linux PC with Rust. This is my first time writing anything in Rust, so I might be completely off.

Becasue the communication my protocol is somewhat asynchronous, I want to use tokio. I found the tokio_serial crate which seems to wrap a serial port into an async-usable API.

I tried the following code. It should receive data on the uart port and print it to the console and echo the bytes back. This works fine.

However, my program consumes 100% CPU load after the first byte is received. It works fine and does not consume CPU as long as no data has been received yet. I added the commented out println! statement and saw, that port.readable().await; actually does not block but completes without any actual readable data.

What am I doing wrong here?

use std::{env, io::Write, process, str};
use tokio_serial::{self, SerialPortBuilderExt};

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let baud_rate : u32 = 115200;
    let serial_port = env::args().nth(1).unwrap_or_else(|| {String::from("/dev/ttyUSB0")});
    println!("Open port {serial_port}");

    let mut port = tokio_serial::new(serial_port, baud_rate).open_native_async().unwrap_or_else(
        |e| {
            println!("Error: {:?}", e);
            process::exit(-1);
        }
    );
    

    let mut buf : [u8; 255] = [0;255];
    loop {
        let readable_res = port.readable().await;
        if let Ok(()) = readable_res {
            match port.try_read(&mut buf) {
                Ok(size) => {
                    println!("Got {} bytes: {}", size, str::from_utf8(&buf[0..size]).unwrap());
                    port.write(&buf[0..size]).unwrap_or_else(|e| {println!("{:?}", e); 0});
                },
                Err(e) => {
                    //println!("{:?}", e);
                }
            }
        } 
    }
}
1

There are 1 answers

2
cdhowie On BEST ANSWER

The docs for readable pretty clearly say: "The function may complete without the socket being readable. This is a false-positive..." In other words, a readable future is guaranteed to complete when the socket can be read, but it is not guaranteed to not complete if it can't be read.

If you're reading in a loop you should probably be using read (via AsyncReadExt) instead of readable + try_read. This is far simpler and is easier to get right.

There isn't really a reason to do non-blocking I/O in an async context anyway. In sync code you typically use non-blocking I/O so you can do other things while you wait for I/O to be ready, but in an async context you can already do other things while you wait via the async runtime (tasks, join!, select!, etc.).