Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shared mutable state documentation #1759

Merged
merged 10 commits into from Feb 23, 2023
43 changes: 43 additions & 0 deletions axum/src/extract/state.rs
Expand Up @@ -295,6 +295,49 @@ use std::{
///
/// In general however we recommend you implement `Clone` for all your state types to avoid
/// potential type errors.
///
/// # Shared mutable state
///
/// [As state is global within a `Router`][global] you can't directly get a mutable reference to
/// the state.
///
/// The most basic solution is to use an `Arc<Mutex<_>>`. Which kind of mutex you need depends on
/// your use case. See [the tokio docs] for more details.
///
/// Note that holding a locked `std::sync::Mutex` across `.await` points will result in `!Send`
/// futures which are incompatible with axum. If you need to hold a mutex across `.await` points
Vagelis-Prokopiou marked this conversation as resolved.
Show resolved Hide resolved
/// consider using a `tokio::sync::Mutex` instead.
///
/// ## Example
///
/// ```
/// use axum::{Router, routing::get, extract::State};
/// use std::sync::{Arc, Mutex};
///
/// #[derive(Clone)]
/// struct AppState {
/// data: Arc<Mutex<String>>,
/// }
Comment on lines +317 to +320
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little worried that people will do fine-grained locking where more coarse locking is the right solution, based on this example. I'm not sure just showing an Arc<Mutex<_>> with everything inside locked at once is better either though...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jplatte Maybe something like the following can be added, in order to make your point super explicit?

#[derive(Clone)]
struct AppState {
      // This is just an example usage, not a best practice.
      // Choose a setup that matches your specific use case.
      data: Arc<Mutex<String>>,
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little worried that people will do fine-grained locking where more coarse locking is the right solution, based on this example.

Yeah I'm a little worried about that as well. But like you say putting a mutex around the whole state probably isn't right either 😅 I dunno. Think I'll just keep it as is.

///
/// async fn handler(State(state): State<AppState>) {
/// let mut data = state.data.lock().expect("mutex was poisoned");
/// *data = "updated foo".to_owned();
///
/// // ...
/// }
///
/// let state = AppState {
/// data: Arc::new(Mutex::new("foo".to_owned())),
/// };
///
/// let app = Router::new()
/// .route("/", get(handler))
/// .with_state(state);
/// # let _: Router = app;
/// ```
///
/// [global]: crate::Router::with_state
/// [the tokio docs]: https://docs.rs/tokio/1.25.0/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use
#[derive(Debug, Default, Clone, Copy)]
pub struct State<S>(pub S);

Expand Down