Search code examples
asynchronousrustrust-tokio

How to run a blocking method in self in a blocking task using tokio?


I'm trying to turn a blocking function into an async function. The sync function on Repo takes &mut self, and blocks for a while, so I'd like to be able to use await on it.

The Repo is a member of RepoManager, which acts as an async interface to the Repo struct with a lot of additional functionality on top.

#[derive(Debug)]
struct RepoManager {
    repo: Mutex<Repo>,
    status: RwLock<ManagerStatus>,
    path: PathBuf,
}

impl RepoManager {
    async fn resync(&self) -> Result<(), SyncError> {
        let new_paths = vec!["a"];  // example paths

        let mut repo = self.repo.lock().await;
        tokio::task::spawn_blocking(move || repo.sync(new_paths))
            .await
            .expect("failed to join with thread that's batch-updating the database")?;

        Ok(())
    }
}

My hope is to move the execution of sync() to a separate thread, so the current thread can keep working on something else. So I created a blocking task using tokio and ran sync() there, then called .await on it to wait for its execution to stop.

However, when I try compiling it, I get an error:

error[E0521]: borrowed data escapes outside of associated function
  --> src-tauri\src\manager.rs:54:40
   |
45 |     async fn resync(&self) -> Result<(), SyncError> {
   |                     -----
   |                     |
   |                     `self` is a reference that is only valid in the associated function body
   |                     let's call the lifetime of this reference `'1`
...
54 |         let mut repo = self.repo.lock().await;
   |                                        ^^^^^^
   |                                        |
   |                                        `self` escapes the associated function body here
   |                                        argument requires that `'1` must outlive `'static`

I'm not sure I understand why it's saying the data outlives the function. Since I'm calling .await on the thread, the current function won't progress or end until the thread terminates.

How can I modify this to move the execution of sync() to a separate thread?

Other relevant structs and definitions:

#[derive(Debug)]
pub struct Repo {
    path: PathBuf,
    conn: rusqlite::Connection,
}

impl Repo {
    pub fn sync(
        &mut self,
        new_paths: impl IntoIterator<Item = RelativePathBuf>,
    ) -> Result<(), SyncError> {
        // (do something mutable with self, this blocks for a while!)
        Ok(())
    }
}

#[derive(Debug, Copy, Clone)]
enum ManagerStatus {
    Idle,
    ScanningDirectory,
    UpdatingRepo,
}

Solution

  • You can use block_in_place, which does not require a 'static lifetime on the closure.

    async fn resync(&self) -> Result<(), SyncError> {
            let new_paths = vec!["a"];  // example paths
    
            let mut repo = self.repo.lock().await;
            tokio::task::block_in_place(move || repo.sync(new_paths))?;
    
            Ok(())
        }
    

    Otherwise, having an Arc<Mutex<Repo>>, or even Arc<Self> might help.