Search code examples
rustactix-web

Actix: what's the right way to move shared data into multiple threads?


I have a config Struct that I'm sharing across my actix app like so:

pub fn run(addr: &str, pg_pool: PgPool, config: Settings) -> Result<Server, std::io::Error> {
    let pool = web::Data::new(pg_pool); 
    let arc_config = web::Data::new(Arc::new(config)); // <---

    let server = HttpServer::new(move || {
        App::new()
            .service(exhaust)
            .app_data(pool.clone())
            .app_data(arc_config.clone()) // <---
    })
    .bind(addr)?
    .run();

I then have a handler that is trying to spawn multiple threads and pass that config struct into each:

#[get("/exhaust")]
pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Arc<Settings>>) -> impl Responder {
    for _ in 1..16 {
        let handle = thread::spawn(move || {
            let inner_config = Arc::clone(&config);
            get_single_tweet(inner_config.as_ref().deref(), "1401287393228038149");
        });
    }

    HttpResponse::Ok()
}

My thinking was that because config is already wrapped in an Arc() I should be able to just Arc::clone() it inside of each thread and then deref into the underlying variable.

But I'm getting this error:

error[E0382]: use of moved value: `config`
  --> src/twitter/routes/pull.rs:63:36
   |
58 | pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Arc<Settings>>) -> impl Responder {
   |                                               ------ move occurs because `config` has type `actix_web::web::Data<Arc<Settings>>`, which does not implement the `Copy` trait
...
63 |         let handle = thread::spawn(move || {
   |                                    ^^^^^^^ value moved into closure here, in previous iteration of loop
64 |             let inner_config = Arc::clone(&config);
   |                                            ------ use occurs due to use in closure

I'm struggling to understand why this fails. If the config is inside an Arc, then why does the compiler think I'm trying to move it instead of incrementing the reference count?

I've also tried a number of other approaches, all unsuccessful:

  • Removing the move in front of the closure - compiler complains the borrowed value doesn't live long enough
  • Dereferencing config and wrapping it in a fresh Arc() - similar error as original

What's the right way to do this?


Solution

  • You need to clone it before it's moved. Otherwise your first iteration will necessarily need to take it (since there is no guarantee config will still exist to be cloned when the task runs). Then you get the error you see for the second iteration, since it too necessarily needs to move config; that's what it means by "value moved into closure here, in previous iteration of loop".

    #[get("/exhaust")]
    pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Arc<Settings>>) -> impl Responder {
        for _ in 1..16 {
            let handle = thread::spawn({
                // Clone this here, so the closure gets its very own to move-capture.
                let inner_config = config.get_ref().clone();
                move || {
                    get_single_tweet(inner_config.as_ref().deref(), "1401287393228038149");
                });
            }
        }
    
        HttpResponse::Ok()
    }
    

    Note that web::Data itself is already just a wrapper around Arc, so you have some redundancy with the nested Arc. You may just want:

    #[get("/exhaust")]
    pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Settings>) -> impl Responder {
        for _ in 1..16 {
            let handle = thread::spawn({
                let inner_config = config.clone();
                move || {
                    get_single_tweet(inner_config.as_ref().deref(), "1401287393228038149");
                });
            }
        }
    
        HttpResponse::Ok()
    }