Passing mutable shared state as parameter to hyper service handler, using Arc and Mutex

Help! I am trying to pass a parameter to Hyper service handler, so that I can count the number of each type of request. the parameter should be mutable, so I make the handler accept a mutable reference,
question is do I need to acquire the lock before passing it to handler or get the lock inside the handler? How to decide the type of the parameter in the handler while using Arc<Mutex> to achieve sharing and mutability inside the handler. Not so much experience with Arc and Mutex:sweat_smile:

// Build a hyper server, which serves our custom echo service.
let mut state = shared_state {
    request_counter: HashMap::new(),
    service_name: "Counting".to_string(),
};
state.request_counter.insert("GET".to_string(), 0);
state.request_counter.insert("POST".to_string(), 0);
state.request_counter.insert("OTHERS".to_string(), 0);

let request_counter = Arc::new(Mutex::new(state));
let fut = Server::builder(tls).serve(move || {
    let inner = Arc::clone(&request_counter);
    service_fn(move |req| echo(req, &inner))
});

// Run the future, keep going until an error occurs.
println!("Starting to serve on https://{}.", addr);
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(fut).map_err(|e| error(format!("{}", e)))?;
Ok(())
}

// Future result: either a hyper body or an error.
type ResponseFuture = Box<Future<Item = Response<Body>, Error = hyper::Error> + Send>;

struct shared_state {
request_counter: HashMap<String, usize>,
service_name: String,
}

// Custom echo service, handling two different routes and a
// catch-all 404 responder.
fn echo(req: Request<Body>, counter: &mut shared_state) -> ResponseFuture {
let (parts, body) = req.into_parts();
println!("{:?}", parts);

println!("service name: {}", counter.service_name);
for (key, value) in counter.request_counter.into_iter() {
    println!("{} : {}", key, value);
}

the full code is the following:

Any hint?
Thanks in advance!

I’d pass &Arc<Mutex<shared_state>> or just &Mutex<shared_state> to the handler fn. The key thing you want to achieve is minimal duration for which the lock is held, so you want to acquire it as late as possible.

Also, shared_state should be named SharedState if you want to follow Rust naming conventions :slight_smile:

1 Like

Also, you can skip Mutex altogether and store AtomicUsize (or a fixed size atomic if you’re ok with nightly) since it looks like you prepopulate the categories.

Thank you for the answer, and the nice additional advices.
For now, I will not change it to AtomicUsize, looks like still have possibility in the future that I have to populate the HashMap inside the handler fn

:sweat: Sorry to bother you again, I am current putting more application logic into the echo function, where I can record the request number, and accumulate content when the request is a POST,
but now, I got another lifetime issue, which I don't quite understand, why static lifetime is required for my counter parameter? And how to deal with this type of issue when concerning constructing a response from the output from manipulating the shared state?

    // Echo service route.
    (Method::POST, "/echo") => {
        // for POST request, record the number and concatenate the content
        let entire_body = body.concat2();
        let res = entire_body.and_then(|body| {
            println!("Body:\n{}", str::from_utf8(&body).unwrap());
            println!("\n");
            let accumulated = record(counter, "POST", str::from_utf8(&body).unwrap());
            // response with accumulated content
            future::ok(Response::builder().body(Body::from(accumulated)).unwrap())
        });
        Box::new(res)
    }

the full code is still in the same link

Thank you in advance!

the error message is the following

error[E0621]: explicit lifetime required in the type of `counter`
   --> tls_server_v2/src/main.rs:205:13
    |
168 | fn echo(req: Request<Body>, counter: &Arc<Mutex<SharedState>>) -> ResponseFuture {
    |                             ------- consider changing the type of `counter` to `&'static std::sync::Arc<std::sync::Mutex<SharedState>>`
...
205 |             Box::new(res)
    |             ^^^^^^^^^^^^^ lifetime `'static` required

error: aborting due to previous error

For more information about this error, try `rustc --explain E0621`.

after change it to &'static, I got even more confusing error message

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> tls_server_v2/src/main.rs:85:41
   |
85 |         service_fn(move |req| echo(req, &inner))
   |                                         ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime  as defined on the body at 85:20...
  --> tls_server_v2/src/main.rs:85:20
   |
85 |         service_fn(move |req| echo(req, &inner))
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `inner`
  --> tls_server_v2/src/main.rs:85:41
   |
85 |         service_fn(move |req| echo(req, &inner))
   |                                         ^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that reference does not outlive borrowed content
  --> tls_server_v2/src/main.rs:85:41
   |
85 |         service_fn(move |req| echo(req, &inner))
   |                                         ^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.
(Method::POST, "/echo") => {
        // for POST request, record the number and concatenate the content
        let entire_body = body.concat2();
        let res = entire_body.and_then(|body| {
            println!("Body:\n{}", str::from_utf8(&body).unwrap());
            println!("\n");
            let accumulated = record(counter, "POST", str::from_utf8(&body).unwrap());
            // response with accumulated content
            future::ok(Response::builder().body(Body::from(accumulated)).unwrap())
        });
        Box::new(res)
    }

Now you will want to go back to cloning the Arc because you're returning a future via Box::new(res) - that future impl needs to be 'static, but the closure there captures a reference to the Arc.

So, clone the Arc and make sure it's moved into the closure that represents the Future impl.

Thank you so much. I fixed by cloning another Arc and move it into the and_then closure.
Just for anybody who may want to check the final solution, I have update it in the same link.
Can I conclude that, for going into and out of every inner scope (function or closure), you have to clone another Arc pointing to the same memory location?

Only if that closure/scope requires 'static :slight_smile:. In most cases, you can have a closure that is short-lived, and is analogous to a plain function call and thus can freely borrow references.

Typically, some code will require that a closure given to it is 'static if it intends to "detach" the closure from the current call stack - that may mean moving it to another thread and/or retaining it for execution later on (e.g. single-threaded tokio reactor). In that case, the closure needs to be a self-contained unit and not borrow anything transient from its environment.

:grinning:
wow, thank you again, your explanation is always accurate, to-the-point!