Tauri Async Command Loop (tokio::spawn)

Hello,

I am trying to run a loop in an async tauri command.

Here is my code:

let state_cpy = state.clone();
    let handle = tokio::spawn(async move {
        loop {
            if !(state_cpy.running()) {
                break;
            }

            // get the jiffies first sample
            let jiffies_sample_1 = Jiffies::new();

            if let Ok(jiffies_sample_1) = jiffies_sample_1 {
                // wait to get the second sample and calculate the diff
                tokio::time::sleep(std::time::Duration::from_millis(frequency_time)).await;

                let jiffies_sample_2 = Jiffies::new();
                if let Ok(jiffies_sample_2) = jiffies_sample_2 {
                    let jiffies_diff = jiffies_sample_2 - jiffies_sample_1;
                    let total = jiffies_diff.total();
                    let busy = total - jiffies_diff.idle;
                    #[allow(clippy::cast_precision_loss)]
                    let percentage = (busy as f64 / total as f64) * 100.0;
                    let _ = on_event.send(percentage);
                }
            }
        }
    });

    *(state_cpy.handle.lock().unwrap()) = handle;

    Ok(())

But I get this build error:

error[E0521]: borrowed data escapes outside of function
   --> backend/modules/src/cpu.rs:114:18
    |
112 |   pub async fn get_cpu_usage_percent(on_event: Channel<f64>, frequency_time: u64, state: tauri::State<'_, State>) -> tauri::Result<()> {
    |                                                                                   -----               -- let's call the lifetime of this reference `'1`
    |                                                                                   |
    |                                                                                   `state` is a reference that is only valid in the function body
113 |       let state_cpy = state.clone();
114 |       let handle = tokio::spawn(async move {
    |  __________________^
115 | |         loop {
116 | |             if !(state_cpy.running()) {
117 | |                 break;
...   |
138 | |     });
    | |      ^
    | |      |
    | |______`state` escapes the function body here
    |        argument requires that `'1` must outlive `'static`

error[E0308]: mismatched types
   --> backend/modules/src/cpu.rs:147:90
    |
147 |   pub async fn stop_cpu_usage_percent(state: tauri::State<'_, State>) -> tauri::Result<()> {
    |  __________________________________________________________________________________________^
148 | |     let mut state = state.handle.lock().unwrap();
149 | | }
    | |_^ expected `Result<(), Error>`, found `()`
    |
    = note:   expected enum `std::result::Result<(), tauri::Error>`
            found unit type `()`

Some errors have detailed explanations: E0308, E0521.
For more information about an error, try `rustc --explain E0308`.
error: could not compile `modules` (lib) due to 2 previous errors

Thank you very much in advance for any help.

If I'm not mistaken, your state is not wrapped in Arc. This could be the problem, when sharing something over multiple threads.
According to tokio docs:

If a single piece of data must be accessible from more than one task concurrently, then it must be shared using synchronization primitives such as Arc.

Also, shouldn't this code:

*(state_cpy.handle.lock().unwrap()) = handle;

be changed to?

*(state.handle.lock().unwrap()) = handle;

You don't need to spawn this. When you call an async Rust function from JS, it just becomes a Promise, which isn't going to block anything there. Tauri already spawns it anyway.

If you need the handle so you can cancel the task, you could instead use something like CancellationToken, and replace the sleep with a timeout.

It looks like you could also replace your State parameter with AppHandle and retrieve the state from that while inside the task. This would be pretty simple if it works.

futures can run for an arbitrary amount of time exedding the function that created them so they generally cannot hold non static references.

you have 3 options,

  • you limit the lifetime of the future somehow so that it's always less than that of state which i don't think you can do with tokio.
  • you change the signature to accept only 'static states
  • you find an alternative to state that is 'static