why not panic when task timeout? - rust

I set the timeout to 1s, but the task executes to 3s, but no panic occurs.
#code
#[should_panic]
fn test_timeout() {
let rt = create_runtime();
let timeout_duration = StdDuration::from_secs(1);
let sleep_duration = StdDuration::from_secs(3);
let _guard = rt.enter();
let timeout = time::timeout(timeout_duration, async {
log("timeout running");
thread::sleep(sleep_duration);
log("timeout finsihed");
"Ding!".to_string()
});
rt.block_on(timeout).unwrap();
}

Using thread::sleep in asynchronous code is almost always wrong.
Conceptually, the timeout works like this:
tokio spawns a timer which would wake up after the specified duration.
tokio spawns your future. If it returns Poll::Ready, timer is thrown away and the future succeeds. If it returns Poll::Pending, tokio waits for the next event, i.e. for wakeup of either your future or the timer.
If the future wakes up, tokio polls it again. If it returns Poll::Ready - again, timer is thrown away, future succeeds.
If the timer wakes up, tokio polls the future one last time; if it's still Poll::Pending, it times out and is not polled anymore, and timeout returns an error.
In your case, however, future do not return Poll::Pending - it blocks inside the thread::sleep. So, even though the timer could fire after one second has passed, tokio has no way to react - it waits for the future to return, future returns only after the thread is unblocked, and, since there's no await inside the block, it returns Poll::Ready - so the timer isn't even checked.
To fix this, you're expected to use tokio::time::sleep for any pauses inside async code. With it, the future times out properly. To illustrate this claim, let's see the self-contained example equivalent to your original code:
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
std::thread::sleep(sleep_duration);
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
Playground
As you've already noticed, this fails - unwrap_err panics when called on Ok, and timeout returns Ok since the future didn't time out properly.
But when replacing std::thread::sleep(...) with tokio::time::sleep(...).await...
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
tokio::time::sleep(sleep_duration).await;
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
...we get the expected behavior - playground.

Related

How to terminate a blocking tokio task?

In my application I have a blocking task that synchronically reads messages from a queue and feeds them to a running task.
All of this works fine, but the problem that I'm having is that the process does not terminate correctly, since the queue_reader task does not stop.
I've constructed a small example based on the tokio documentation at: https://docs.rs/tokio/1.20.1/tokio/task/fn.spawn_blocking.html
use tokio::sync::mpsc;
use tokio::task;
#[tokio::main]
async fn main() {
let (incoming_tx, mut incoming_rx) = mpsc::channel(2);
// Some blocking task that never ends
let queue_reader = task::spawn_blocking(move || {
loop {
// Stand in for receiving messages from queue
incoming_tx.blocking_send(5).unwrap();
}
});
let mut acc = 0;
// Some complex condition that determines whether the job is done
while acc < 95 {
tokio::select! {
Some(v) = incoming_rx.recv() => {
acc += v;
}
}
}
assert_eq!(acc, 95);
println!("Finalizing thread");
queue_reader.abort(); // This doesn't seem to terminate the queue_reader task
queue_reader.await.unwrap(); // <-- The process hangs on this task.
println!("Done");
}
At first I expected that queue_reader.abort() should terminate the task, however it doesn't. My expectation is that tokio can only do this for tasks that use .await internally, because that will handle control over to tokio. Is this right?
In order to terminate the queue_reader task I introduced a oneshot channel, over which I signal the termination, as shown in the next snippet.
use tokio::task;
use tokio::sync::{oneshot, mpsc};
#[tokio::main]
async fn main() {
let (incoming_tx, mut incoming_rx) = mpsc::channel(2);
// A new channel to communicate when the process must finish.
let (term_tx, mut term_rx) = oneshot::channel();
// Some blocking task that never ends
let queue_reader = task::spawn_blocking(move || {
// As long as termination is not signalled
while term_rx.try_recv().is_err() {
// Stand in for receiving messages from queue
incoming_tx.blocking_send(5).unwrap();
}
});
let mut acc = 0;
// Some complex condition that determines whether the job is done
while acc < 95 {
tokio::select! {
Some(v) = incoming_rx.recv() => {
acc += v;
}
}
}
assert_eq!(acc, 95);
// Signal termination
term_tx.send(()).unwrap();
println!("Finalizing thread");
queue_reader.await.unwrap();
println!("Done");
}
My question is, is this the canonical/best way to do this, or are there better alternatives?
Tokio cannot terminate CPU-bound/blocking tasks.
It is technically possible to kill OS threads, but generally it is not a good idea, as it's expensive to create new threads and it can leave your program in an invalid state. Even if Tokio decided this was something worth implementing, it would serverely limit its implementation - it would be forced into a multithread model, just to support the possibility that you'd want to kill a blocking task before it's finished.
Your solution is pretty good; give your blocking task the responsibility for terminating itself and provide a way to tell it to do so. If this future was part of a library, you could abstract the mechanism away by returning a "handle" to the task that had a cancel() method.
Are there better alternatives? Maybe, but that would depend on other factors. Your solution is good and easily extended, for example if you later needed to send different types of signal to the task.

Rust: can tokio be understood as similar to Javascripts event loop or be used like it?

I'm not sure if tokio is similar to the event loop in Javascript, also a non-blocking runtime, or if it can be used to work in a similar way. In my understanding, tokio is an runtime for futures in Rust. Therefore it must implement some kind of userland threads or tasks, which can be achieved with an event loop (at least partly) to schedule new tasks.
Let's take the following Javascript code:
console.log('hello1');
setTimeout(() => console.log('hello2'), 0);
console.log('hello3');
setTimeout(() => console.log('hello4'), 0);
console.log('hello5');
The output will be
hello1
hello3
hello5
hello2
hello4
How can I do this in tokio? Is tokio meant to work like this overall? I tried the following code
async fn set_timeout(f: impl Fn(), ms: u64) {
tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
f()
}
#[tokio::main]
async fn main() {
println!("hello1");
tokio::spawn(async {set_timeout(|| println!("hello2"), 0)}).await;
println!("hello3");
tokio::spawn(async {set_timeout(|| println!("hello4"), 0)}).await;
println!("hello5");
}
The output is just
hello1
hello3
hello5
If I change the code to
println!("hello1");
tokio::spawn(async {set_timeout(|| println!("hello2"), 0)}.await).await;
println!("hello3");
tokio::spawn(async {set_timeout(|| println!("hello4"), 0)}.await).await;
println!("hello5");
The output is
hello1
hello2
hello3
hello4
hello5
but then I don't get the point of the whole async/await/future feature, because then my "async" set_timeout-tasks are actually blocking the other println statements..
In short: yes, Tokio is meant to work much like the JavaScript event loop. However, there are three problems with your first snippet.
First, it returns from main() before waiting for things to play out. Unlike your JavaScript code, which presumably runs in the browser, and runs the timeouts even after the code you typed in the console has finished running, the Rust code is in a short-lived executable which terminates after main(). Whatever things were scheduled to happen later won't occur if the executable stops running because it returned from main().
The second issue is that the anonymous async block that calls the set_timeout() async function doesn't do anything with its return value. An important difference between async functions in Rust and JavaScript is that in Rust you can't just call an async function and be done with it. In JavaScript an async function returns a promise, and if you don't await that promise, the event loop will still execute the code of the async function in the background. In Rust, an async function returns a future, but it is not associated with any event loop, it is just prepared for someone to run it. You then need to either await it with .await (with the same meaning as in JavaScript) or explicitly pass it to tokio::spawn() to execute in the background (with the same meaning as calling but not awaiting the function in JavaScript). Your async block does neither, so the invocation of set_timeout() is a no-op.
Finally, the code immediately awaits the task created by spawn(), which defeats the purpose of calling spawn() in the first place - tokio::spawn(foo()).await is functionally equivalent to foo().await for any foo().
The first issue can be resolved by adding a tiny sleep at the end of main. (This is not the proper fix, but will serve to demonstrate what happens.) The second issue can be fixed by removing the async block and just passing the return value of set_timeout() to tokio::spawn(). The third issue is resolved by removing the unnecessary .await of the task.
#[tokio::main]
async fn main() {
println!("hello1");
tokio::spawn(set_timeout(|| println!("hello2"), 0));
println!("hello3");
tokio::spawn(set_timeout(|| println!("hello4"), 0));
println!("hello5");
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
}
This code will print the "expected" 1, 3, 5, 4, 2 (although the order is not guaranteed in programs like this). Real code would not end with a sleep; instead, it would await the tasks it has created, as shown in Shivam's answer.
Unlike JavaScript, Rust does not start the execution of an async function until the future is awaited. It means set_timeout(|| println!("hello2"), 0) only creates a new future. It doesn't execute it at all. When you await it, only then it is executed. .await essentially blocks the current thread until the future is completed which is not "real asynchronous applications". To make your code concurrent like JavaScript, you can use join! macro:-
use tokio::join;
use tokio::time::*;
async fn set_timeout(f: impl Fn(), ms: u64) {
sleep(Duration::from_millis(ms)).await;
f()
}
#[tokio::main]
async fn main() {
println!("hello1");
let fut_1 = tokio::spawn(set_timeout(|| println!("hello2"), 0));
println!("hello3");
let fut_2 = tokio::spawn(set_timeout(|| println!("hello4"), 0));
println!("hello5");
join!(fut_1, fut_2);
}
You can use FuturesOrdered if want to take feel of Promise.all.
More info:-
https://news.ycombinator.com/item?id=21473777
https://rust-lang.github.io/async-book/06_multiple_futures/01_chapter.html

Why doesn't the second await get called on tokio::spawn?

I have the following function that connects to a database using sqlx):
async fn testConnect() -> anyhow::Result<PgPool> {
delay_for(Duration::from_millis(3000)).await;
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&"database_connection_string")
.await?;
Ok(pool)
}
And I run it on the tokio runtime:
let mut threaded_rt = runtime::Builder::new()
.threaded_scheduler()
.enable_all()
.build()
.unwrap();
threaded_rt.block_on(future::lazy(move |_| {
let handle = tokio::spawn(testConnect());
return handle;
}));
Any code after delay_for inside testConnect does not get executed. Why is this and how can I make both awaits run?
If I remove the delay_for line of code, the database connection code runs as expected.
I suspect that the following happens. This is analogue to starting a background worker thread and quit without joining it.
you spawn the task on tokio and return the handle
block_on drives the tokio reactor for a little while which is just enough for a normal connection, but not enough for the delay to expire
nothing drives the reactor anymore, so the result of the spawned task is just dropped and the program exits
If so, you can fix it simply by calling threaded_rt.block_on(testConnect()) directly, the spawn() part seems to be completely pointless.

What is the difference between tokio::spawn(my_future).await and just my_future.await?

Given an async function and it's corresponding future, lets say:
async fn foo() -> Result<i32, &'static str> {
// ...
}
let my_future = foo();
what is the difference between awaiting it using just .await other than using tokio::spawn().await?
// like this...
let result1 = my_future.await;
// ... and like this
let result2 = tokio::spawn(my_future).await;
One typically doesn't await a spawned task (or at least not right away). It's more common to simply write:
tokio::spawn(my_future);
Leave out the .await and the task will run in the background while the current task continues. Immediately calling .await blocks the current task. spawn(task).await is effectively no different than task.await. It's akin to creating a thread and immediately joining it, which is equally pointless.
Spawned tasks don't need to be awaited the way bare futures do. Awaiting them is optional. When might one want to await one, then? If you want to block the current task until the spawned task finishes.
let task = tokio::spawn(my_future);
// Do something else.
do_other_work();
// Now wait for the task to complete, if it hasn't already.
task.await;
Or if you need the result, but need to do work in between starting the task and collecting the result.
let task = tokio::spawn(my_future);
// Do something else.
do_other_work();
// Get the result.
let result = task.await;

Is it possible to force resume a sleeping thread?

Is it possible to force resume a sleeping thread which has been paused? For example, by calling sleep:
std::thread::sleep(std::time::Duration::from_secs(60 * 20));
I know that I can communicate between threads using std::sync::mpsc but if the thread is asleep, this does not force it to wake up before the time indicated.
I have thought that using std::sync::mpsc and maybe
Builder and .name associated with the thread, but I do not know how to get the thread to wake up.
If you want to be woken up by an event, thread::sleep() is not the correct function to use, as it's not supposed to be stopped.
There are other methods of waiting while being able to be woken up by an event (this is usually called blocking). Probably the easiest way is to use a channel together with Receiver::recv_timeout(). Often it's also sufficient to send () through the channel. That way we just communicate a signal, but don't send actual data.
If you don't want to wake up after a specific timeout, but only when a signal arrives, just use Receiver::recv().
Example with timeout:
use std::thread;
use std::sync::mpsc::{self, RecvTimeoutError};
use std::time::Duration;
use std::io;
fn main() {
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
loop {
match receiver.recv_timeout(Duration::from_secs(2)) {
Err(RecvTimeoutError::Timeout) => {
println!("Still waiting... I'm bored!");
// we'll try later...
}
Err(RecvTimeoutError::Disconnected) => {
// no point in waiting anymore :'(
break;
}
Ok(_) => {
println!("Finally got a signal! ♥♥♥");
// doing work now...
}
}
}
});
loop {
let mut s = String::new();
io::stdin().read_line(&mut s).expect("reading from stdin failed");
if s.trim() == "start" {
sender.send(()).unwrap();
}
}
}
Here, the second thread is woken up at least every two seconds (the timeout), but also earlier once something was sent through the channel.
park_timeout allows timed sleeps with wakeups from unpark, but it can also wake up early.
See std::thread module documentation

Resources