Hi all,
I want to schedule a task every X minutes (say 5). I know that if I'm in a tokio task I can do something like this
let mut interval = time::interval(Duration::from(10));
let _maintainance = tokio::task::spawn(async {
loop {
interval.tick().await;
do_something_now().await;
}
});
this will guarantee that I can call do_something_now() every 5 minutes independently from the function duration.
Now, since my function can take 50-60 secs to complete the execution, I am using spawn_blocking instead. I wanted to ask what's the idiomatic way to achieve the same?
Right now I'm measuring the time of the function and performing a sleep with a duration that is 5-last_run. But I believe there must be a better way.. any idea?
sorry I've clarified in the post, the function I'm calling after the sleep can take up to 1 min to complete so I want to make sure that the next round is in 5 min minus the duration of the heavy function if that makes sense?
thanks for this suggestion! would the spaw_blocking inside a spawn have any counter effect? I want to isolate this task from the standard spawns where all my requests are flowing in the service
from my understanding when you spawn_blocking the code is executed on a separate pool, I was just wondering if the blocking is spawned inside a normal task whether that creates some form of dependency?
This sounds more like how block_in_place works. spawn_blocking just sends a function to the blocking threadpool. If you don't abort _maintainance or the tokio runtime, then it'll keep looping forever.
Also, the task calling spawn_blocking does not have to wait for it to complete. spawn_blocking returns a JoinHandle that implements Future, and you choose whether to await that Future or not. So there is no dependency unless you choose to create one.
I see thanks folks.
I will do some tests but I'm correct to say that no matter whether or not I await the spawn_blocking, it will be called exactly every 5 minutes independently from the duration of the spawn_blocking task?
If you use Interval and not sleep then yes. Unless the spawn_blocking task itself takes more time than 5 minutes. In that case it depends on your missed tick behaviour.
If that's the only background task in your program just spawn a thread, loop, sleep.
If you have many such tasks then set the timer in tokio and submit the blocking work to a thread pool.
And unless you need the precise 5 minute interval it's usually good practice to schedule things with a delay between tasks, rather than on a schedule. That way you don't build up a growing backlog when something goes wrong and the task takes longer than the schedule.
I like java's ScheduledThreadPoolExecutor for that, which has scheduleWithFixedDelay​. Not sure if there's a crate like that.
sounds good folks thanks for all your help! since we briefly talked about this I just want to make sure I'm doing the right thing with the spawn_blocking.
My spawn_blocking is executing few copies of in memory objects (pretty big objects around 10gb) and in total that takes around 40-50 sec. Since the same service is also serving async requests I noticed that moving that maintainance task in a spawn blocking doesn't create too much noise with the incoming API I'm receiving on the regular tokio tasks.
However, I've see some of you suggesting to schedule tasks into a dedicated threadpool. I just wanted to doublecheck whether this is effectively the same than invoke spawn_blocking or there is any other advantage?
let _maintainance = tokio::task::spawn(async move {
let start = Instant::now() + Duration::from_secs(ROTATION_TIME_SECS);
let mut interval = time::interval_at(start, Duration::from_secs(ROTATION_TIME_SECS));
loop {
interval.tick().await;
// Spawning the task, awaiting the results to ensure
// there will never be more than 1 execution happening
// at the same time.
let _ = tokio::task::spawn_blocking(do_something_now).await;
}
});