Search code examples
multithreadingrustmutex

Rust "future cannot be sent between threads safely"


I'm invoking an async implemented method:

let mut safebrowsing: MutexGuard<Safebrowsing> = self.safebrowsing.lock().unwrap();
safebrowsing.is_safe(input: &message.content).await;

The is_safe-Method:

pub async fn is_safe(&mut self, input: &str) {
    let links = self.finder.links(input);

    for link in links {
        match reqwest::get("url").await {
            Ok(response) => {
                println!(
                    "{}",
                    response.text().await.expect("response has no content")
                );
            }
            Err(_) => {
                println!("Unable to get safebrowsing-response")
            }
        }
    }
}

But unfortunately by invoking the is_safe-Method asynchronously, the compiler tells me that threads cannot be sent safely. The error is about:

future cannot be sent between threads safely
within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, Safebrowsing>`
required for the cast to the object type `dyn std::future::Future<Output = ()> + std::marker::Send`

handler.rs(31, 9): future is not `Send` as this value is used across an await
           ^-- safebrowsing.is_safe(input: &message.content).await;
---

future cannot be sent between threads safely
the trait `std::marker::Send` is not implemented for `(dyn for<'r> Fn(&'r [u8]) -> Option<usize> + 'static)`
required for the cast to the object type `dyn std::future::Future<Output = ()> + std::marker::Send`

safebrowsing.rs(22, 19): future is not `Send` as this value is used across an await
               ^-- match reqwest::get("url").await

I already tried to implement the Send-Trait to my Safebrowsing-Struct, but that does also not work. Is there something I need to do to get it working? Because I have no clue why that is appearing


Solution

  • The key of this error is that MutexGuard<T> is not Send. This means that you are trying to do an await while the mutex is locked, and that is usually a bad idea, if you think about it: await may wait, in principle, indefinitely, but by waiting so long with the mutex held, any other thread that tries to lock the mutex will block, also indefinitely (unless you set a timeout, of course).

    So, as a rule of thumb, you should never sleep with a mutex locked. For example your code could be rewritten as (totally untested):

    pub async fn is_safe(this: &Mutex<Safebrowsing>, input: &str) {
        //lock, find, unlock
        let links = this.lock().unwrap().finder.links(input);
        //now we can await safely
        for link in links {
            match reqwest::get("url").await {
                Ok(response) => {
                    println!(
                        "{}",
                        response.text().await.expect("response has no content")
                    );
                }
                Err(_) => {
                    println!("Unable to get safebrowsing-response")
                }
            }
        }
    }
    

    If you need to lock the Mutex later in the function, beware of the races! It may have been modified by other thread, maybe that input is no longer a thing.