0

I am new to Rust, I want to know if it is possible to write a single-threaded concurrent application if non-blocking APIs are not available.

I have written a server-side code for a VPN using a UDP socket and tun device (virtual network interface) to work in Linux.

This code is intended to wait for something to be written in tun fd or packets to arrive on UDP socket.

    loop {
        tokio::select! {
            _ = async {
                log::debug!("reading from interface");
                tun.read(&mut buf1).unwrap();
            } => {
                     log::debug!("sending to client {}", &caddr);
                     socket.send_to(&buf1, &caddr).unwrap();     
                },
            _ = async {
                log::debug!("reading from client");
                let (_amt, addr) = socket.recv_from(&mut buf2).unwrap();
                caddr = addr.to_string();
            } => {
                    log::debug!("writting to interface");
                    tun.write(&mut buf2).unwrap();
            }
        };

I thought tokio::select! will fulfill my requirement as its official documentation states about macro -

Waits on multiple concurrent branches, returning when the first branch completes, canceling the remaining branches

Here is the output I got after running it

$ sudo RUST_LOG=debug target/debug/dprox server -l 8080
[2022-09-23T10:58:31Z INFO  dprox] Starting as server
[2022-09-23T10:58:31Z DEBUG dprox] reading from client

To my surprise, the debug log reading from interface did not get a chance to run and I guess it's due to blocking socket.recv_from(). I was unaware that tokio::select! will not switch if I will block it.

Is there a way to achieve intended behavior in a single thread if non-blocking APIs do not exist?

Edit:

I found libraries tokio::net::UdpSocket and tokio_tun which provide non-blocking APIs that seem to work for my usecase.

But I am still curious to know how to achieve single-threaded concurrency if non-blocking APIs do not exist?

Finomnis
  • 18,094
  • 1
  • 20
  • 27
shubmishra
  • 11
  • 2
  • Short version: Wrap your blocking calls in `tokio::spawn_blocking(...).await`. That should prevent that your executor gets blocked, as the actual blocking work will be done on a background thread. – Finomnis Sep 23 '22 at 11:48
  • Your actual answer to "Can I achieve this in a single thread" (if I read that as: without using `spawn_blocking`) is: **No**. If you only have one thread, and you block that thread, it will be blocked. Preempting the work that the thread is doing would require a second thread. Async by definition is `non-preemptive` and blocking synchronously will block the entire async executor. – Finomnis Sep 23 '22 at 11:52
  • See also `tokio`'s documentation about blocking calls: https://docs.rs/tokio/latest/tokio/index.html#cpu-bound-tasks-and-blocking-code – Finomnis Sep 23 '22 at 11:56
  • 1
    Thanks, @Finomnis, I think I got it now, as Async is non-preemptive, any blocking operation will block the thread to switch between tasks. So, spawning a blocking thread is a way to do such blocking operations. – shubmishra Sep 23 '22 at 12:44
  • @submishra Exactly :) – Finomnis Sep 23 '22 at 12:46

0 Answers0