multithreadingasynchronousrustmutextauri

How to use a managed Tauri state variable inside a spawned tauri async runtime thread?


I am trying to build this app where I have a command that starts a thread that publishes many messages to RabbitMQ, and then emits an event once it is done publishing. I have a managed ConnectionMutex struct using tauri's state API, and I am also getting the window handle to emit events. the code looks like the following:

use tauri::async_runtime::Mutex;
pub struct ConnectionMutex(pub Mutex<Option<RabbitMqConnection>>);


#[tauri::command]
pub async fn publish_message(
    connection_mutex: tauri::State<'_, ConnectionMutex>,
    window: tauri::Window,
) -> Result<(), String> { 
    tauri::async_runtime::spawn(async move {
        let lock = connection_mutex
            .0
            .try_lock()
            .map_err(|_| String::from("cannot change connection while it is being used"))
            .unwrap();
        let conn = lock.as_ref().ok_or("no rabbitmq connection").unwrap();

        let _ = rabbitmq::publishing::publish(
            &conn.target,
            &conn.channel,
            window,
        )
        .await;
    });

    Ok(())
}

I get a compiler error saying: borrowed data escapes outside of function. this error makes sense to me, I understand why this would not be safe because the rust borrow checker could no longer track the borrowed variable and will be unable to free it at the correct time. However, I have no idea the correct way to achieve this kind of stuff in Rust.

I also tried setting the lifetime of the State variable to static,like this:

#[tauri::command]
pub async fn publish_message(
    connection_mutex: tauri::State<'static, ConnectionMutex>,
    window: tauri::Window,
) -> Result<(), String>

but I get a different error: __tauri_message__ does not live long enough


Solution

  • A common approach is to wrap the Mutex in Arc so that you can safely share references to the underlying Mutex across async tasks. (See also the example in the docs).

    So your ConnectionMutex could look like:

    pub struct ConnectionMutex(pub Arc<Mutex<Option<RabbitMqConnection>>>);
    

    Then, when you want to pass the connection when you spawn an async task, you first clone() the ConnectionMutex to give the task it's own copy of the reference.

    #[tauri::command]
    pub async fn publish_message(
        connection_mutex: tauri::State<'_, ConnectionMutex>,
        window: tauri::Window,
    ) -> Result<(), String> {
        let connection = Arc::clone(&connection_mutex.0);
    
        tauri::async_runtime::spawn(async move {
            let lock = connection
                .try_lock()
                .map_err(|_| String::from("cannot change connection while it is being used"))
                .unwrap();
    
            let conn = lock.as_ref().ok_or("no rabbitmq connection").unwrap();
    
            let _ = rabbitmq::publishing::publish(
                &conn.target,
                &conn.channel,
                window,
            )
            .await;
        });
    
        Ok(())
    }