I'm trying to implement a basic middleware in a Axum project, and I'm facing an issue when my middleware is passed a state wrapped in a Mutex.
Disclaimer
There is a high chance that the shared state used in this sample is not optimized nor recommended to use as-is in Axum. Above all I'm trying to understand the issue and learn how I could spot the underlying error in my code.
The code
cargo.toml
[package]
name = "mutex_sample"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.6.10"
tokio = {version = "1.26.0", features = ["full"]}
main.rs
use std::{
net::SocketAddr,
sync::{Arc, Mutex},
};
use axum::{
extract::State,
http::{Request, StatusCode},
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
Router,
};
#[tokio::main]
async fn main() {
let state_with_mutex = Arc::new(Mutex::new(0));
let state_without_mutex = Arc::new(0);
let app: Router = Router::new()
.route("/", get(hello_world))
// state without mutex compiles correctly
.layer(middleware::from_fn_with_state(state_without_mutex, test_middleware))
// state with mutex : trait bound not satisfied
.layer(middleware::from_fn_with_state(state_with_mutex, test_middleware_mutex));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn hello_world() -> String {
String::from("Hello Reachy !")
}
async fn test_middleware_mutex<B>(
State(state): State<Arc<Mutex<u16>>>,
request: Request<B>,
next: Next<B>,
) -> Response {
// Commenting this let-else expression fixes the compiler error
let Ok(inner) = state.lock() else {
return StatusCode::BAD_REQUEST.into_response();
};
next.run(request).await
}
async fn test_middleware<B>(
State(state): State<Arc<u16>>,
request: Request<B>,
next: Next<B>,
) -> Response {
if *state > 1 {
return StatusCode::BAD_REQUEST.into_response();
}
next.run(request).await
}
The error
the trait bound `axum::middleware::FromFn<fn(State<Arc<std::sync::Mutex<u16>>>, Request<_>, Next<_>) -> impl Future<Output = Response<http_body::combinators::box_body::UnsyncBoxBody<axum::body::Bytes, axum::Error>>> {test_middleware_mutex::<_>}, Arc<std::sync::Mutex<{integer}>>, Route<_>, _>: tower_service::Service<Request<_>>` is not satisfied
the following other types implement trait `tower_service::Service<Request>`:
axum::middleware::FromFn<F, S, I, (T1, T2)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7, T8)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7, T8, T9)>
and 8 others
Questions
Two behavior I don't understand here (despite the compiler error message) :
- as stated in the code, if I remove the
let-else
expression fromtest_middleware_mutex()
, then everything compiles correctly. It seems that the multiple possible return type inside the function causes the compiler to complain. - for the same type of code (
let-else
inside the async middleware), wrapping the state in a Mutex also causes an issue.
These two behaviors are mutually exclusive, I can fix the error by removing the Mutex
or by removing the let-else
expression, but I can't keep both.
Does someone know why is this code not working ? Thanks in advance
As stated in my post's comment section, this is a normal behavior for
std::sync::Mutex
. It is well documented here : https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-useTL;DR
std::sync::Mutex
does not do well withasync
methods since they cannot be held acrossFuture
s. Ifstd::sync::Mutex
is used, manipulate it in non-async function. Otherwise, use some async-compatible mutex, such as Tokio's https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#