Reputation: 2534
I'm trying to get clokwerk to schedule an asynchronous function to run every X seconds.
The docs show this example:
// Create a new scheduler
let mut scheduler = AsyncScheduler::new();
// Add some tasks to it
scheduler
.every(10.minutes())
.plus(30.seconds())
.run(|| async { println!("Simplest is just using an async block"); });
// Spawn a task to run it forever
tokio::spawn(async move {
loop {
scheduler.run_pending().await;
tokio::time::sleep(Duration::from_millis(100)).await;
}
});
My initial attempt:
let config2 = // define a Config struct, Config
let pg_pool2 = // get a sqlx connection pool, Pool<Postgres>
//I assume I need shared references so I use Arc
let pg_pool2 = Arc::new(pg_pool2);
let config2 = Arc::new(config2);
let mut scheduler = AsyncScheduler::new();
scheduler.every(5.seconds()).run(|| async {
println!("working!");
pull_from_main(pg_pool2.clone(), config2.clone()).await;
});
tokio::spawn(async move {
loop {
scheduler.run_pending().await;
tokio::time::sleep(Duration::from_millis(100)).await;
}
});
The compiler complains that pg_pool2
and config2
may outlive the borrowed value, and suggests to add move
. Fair. Let's try that.
My second attempt:
//rest the same
scheduler.every(5.seconds()).run(move || async {
//rest the same
This time I get back an error I can't decipher on my own:
error: captured variable cannot escape `FnMut` closure body
--> src/main.rs:80:46
|
75 | let pg_pool2 = Arc::new(pg_pool2);
| -------- variable defined here
...
80 | scheduler.every(5.seconds()).run(move || async {
| ____________________________________________-_^
| | |
| | inferred to be a `FnMut` closure
81 | | println!("working!");
82 | | pull_from_main(pg_pool2.clone(), config2.clone()).await;
| | -------- variable captured here
83 | | });
| |_____^ returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escape
Can someone help me understand what's wrong and how to fix it?
Note: I've seen this question asked before, but I'm struggling to apply the answers to my case.
I'm also a beginner so maybe they do apply, but I don't see the connection:)
Upvotes: 8
Views: 3497
Reputation: 15683
In order to understand what's going on, I'll reformat the code a bit in order to make it more clear and explicit:
Your original code:
scheduler
.every(5.seconds())
.run(move || async {
do_something(arc.clone());
});
Is equivalent to:
scheduler
.every(5.seconds())
.run(move || {
return async {
do_something(arc.clone());
}
});
So you create a closure, which is of type FnMut
(and which returns a type that implements Future
). That means that your closure can be called multiple times, and each invocation should produce a new future. But return async{}
moves your Arc
out of the closure, which means that it can be called only once. Imagine that you have a 10$ note in your wallet. If you take it out and spend it, then you will not be able to take it out and spend it for a second time, because it's simply not there anymore.
So how can we solve that ? It's pretty easy actually - you have to clone your Arc
before moving it to the async
block. Thus you will move only the clone:
let arc = Arc::new(whatever);
scheduler
.every(5.seconds())
.run(move || {
// Clone the arc and move the clone!!!
// The original arc will remain in the closure,
// so it can be called multiple times.
let x = arc.clone();
async move {
do_something(x);
}
});
Upvotes: 18