Search code examples
rustasync-awaitmutexrust-axum

How to fix "future returned by is not `Send`" when using Mutex in axum?


I wanted to implement an authorization code flow in axum with PKCE. Therefore I have to hand over the generated PKCE code verifier to the callback route in order to exchange the code for a token, so that I can proceed logging my user in with a session and such.

Right now I mainly use a hammer, just to describe why the solution might look like it does:

#[derive(Clone)]
struct AppState {
    db: PgPool,
    oauth_client: BasicClient,
    verifiers: Arc<Mutex<HashMap<String, String>>>,
}

#[debug_handler]
async fn callback(
    State(state): State<AppState>,
    Query(auth_request): Query<AuthRequest>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    let auth_request = auth_request;

    let verifiers = state.verifiers.lock().unwrap();
    let pkce_verifier = verifiers.get(&auth_request.state).unwrap().into();
    let pkce_verifier = PkceCodeVerifier::new(pkce_verifier);

    let _token_result = match state
        .oauth_client
        .exchange_code(AuthorizationCode::new(auth_request.code))
        .set_pkce_verifier(pkce_verifier)
        .request_async(async_http_client)
        .await
    {
        Ok(res) => res,
        Err(e) => {
            error!("could not exchange code: {e}");
            return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string()));
        }
    };

    Ok(Redirect::temporary("/"))
}

the error is as follows:

  --> src/main.rs:134:10
    |
125 |     let verifiers = state.verifiers.lock().unwrap();
    |         --------- has type `std::sync::MutexGuard<'_, std::collections::HashMap<std::string::String, std::string::String>>` which is not `Send`
134 |         .await
    |          ^^^^^ await occurs here, with `verifiers` maybe used later

I understand that the lock on the mutex is hold too long. I've tried to understand lots of similar problems, but I am not able to understand this yet.


Solution

  • Rust's std::sync::Mutex cannot be held across an await point, which is why you're seeing the type is not Send compile error. You have two options:

    1. Drop the lock before you reach an await point. You only use verifiers to get pkce_verifier, so you could just inline it:

      let pkce_verifier = state
          .verifiers
          .lock()
          .unwrap()
          .get(&auth_request.state)
          .unwrap()
          .into();
      

      That is somewhat ugly, so you could also just manually call drop() on the lock:

      let verifiers = state.verifiers.lock().unwrap();
      let pkce_verifier = verifiers.get(&auth_request.state).unwrap().into();
      drop(verifiers);
      

      Alternatively, you could create and use the lock within an inner block scope, which will automatically drop the lock when the block exits scope:

      let pkce_verifier = {
          let verifiers = state.verifiers.lock().unwrap();
          verifiers.get(&auth_request.state).unwrap().into()
      };
      
    2. You could use an async-aware lock, like futures::lock::Mutex. If you think you will need to use a mutex across an await point in the future, you could just switch to an async-aware mutex. Otherwise, the standard library mutex will work as long as the lock isn't held across an await point.


    As a side note, it's bad practice to call .unwrap() on objects unless you're sure the field exists. Especially in the case of a HashMap, you should use pattern matching or built-in Option-to-Result functions Rust provides like ok_or().