Shared state across multiple threads in Actix-web

Let’s look at “state” in Actix-web

Shared state in Actix-web

HttpRequest::app_data

app_data is a method provided by actix-web, specifically for the App and HttpRequest types. Here’s how it works:

  • This method is used when setting up your App to store shared application state. You call it during the application configuration to register data that will be accessible across all routes and handlers.
  • Example:
    rust App::new() .app_data(Data::new(Mutex::new(MyData { counter: 0 })))
  • By calling app_data, you store a piece of data (like Data<Mutex<MyData>>) that can be shared across all incoming requests. Internally, actix-web stores this data in a way that makes it accessible throughout the application’s lifetime.
  • This method is used within a handler to retrieve the application data you stored earlier using App::app_data.
  • It allows you to access shared state, and it returns an Option<&T>, where T is the type you registered.
rust async fn index(req: HttpRequest) -> impl Responder { 
let data = req.app_data::<Data<Mutex<MyData>>>().unwrap(); 
let mut my_data = data.lock().unwrap(); my_data.counter += 1; 
HttpResponse::Ok().body(format!("Counter: {}\n", my_data.counter)) }

Summary:

  • App::app_data is used to set up shared state during application initialization.
  • HttpRequest::app_data is used to access that shared state within request handlers.
    These methods help in managing and sharing data across different parts of your actix-web application.

Example code

use std::sync::Mutex;
use actix_web::{App, HttpRequest, HttpResponse, Responder, HttpServer, web::{self, Data}};
struct MyData {
    counter: usize,
}
/// Use the `Data<T>` extractor to access data in a handler.
async fn index(data: Data<Mutex<MyData>>) -> impl Responder {
    let mut my_data = data.lock().unwrap();
    my_data.counter += 1;
    HttpResponse::Ok().body(format!("Counter: {}\n", my_data.counter))
}

Alternatively, use the HttpRequest::app_data method to access data in a handler.

/// Alternatively, use the `HttpRequest::app_data` method to access data in a handler.
async fn index_alt(req: HttpRequest) -> impl Responder {
    let data = req.app_data::<Data<Mutex<MyData>>>().unwrap();
    let mut my_data = data.lock().unwrap();
    my_data.counter += 1;
    HttpResponse::Ok().body(format!("Counter: {}\n", my_data.counter))
}

And here is the main function

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let data = Data::new(Mutex::new(MyData { counter: 0 }));

    HttpServer::new(move || {
        // Move data into the closure
        App::new()
            // Store `MyData` in application storage.
            .app_data(Data::clone(&data))
            .route("/index.html", web::get().to(index))
            .route("/index-alt.html", web::get().to(index_alt))
    })
    .bind(("127.0.0.1", 8080))? // Bind to 127.0.0.1:8080
    .run()
    .await
}

The line Data::clone(&data) is effectively cloning a reference to the Data<Mutex<MyData>> instance.

~/rust/hello-world main*
❯ curl http://127.0.0.1:8080/alt_index.html

~/rust/hello-world main*
❯ curl http://127.0.0.1:8080/index-alt.html
Counter: 1

~/rust/hello-world main*
❯ curl http://127.0.0.1:8080/index.html    
Counter: 2

~/rust/hello-world main*
❯ curl http://127.0.0.1:8080/index-alt.html
Counter: 3

~/rust/hello-world main*
❯ curl http://127.0.0.1:8080/index-alt.html
Counter: 4

~/rust/hello-world main*
❯ curl http://127.0.0.1:8080/index.html    
Counter: 5

Note : actix-web‘s Data<T> wraps the data in an Arc, making it automatically shared across threads. You just add Mutex if you need to safely mutate the data.

https://actix.rs/docs/application#state