Hyper: passing a state reference to async request handler

There is other topics similar to this, but they are either outdated or don't seem to cover async/await case.

I'm trying to pass a reference to the RequestState to the async handle_request.

I have this code (open in Playground):

let make_svc = make_service_fn(|_| {
    let onion1 = Arc::clone(&state);
    async move {
        let onion2 = Arc::clone(&onion1);
        Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
            let onion3 = Arc::clone(&onion2);
            async move {
                let onion4 = &*onion3;
                handle_request(onion4, req).await
            }
        }))
    }
});

How can I make some sanity this? Do I really need all of these: onion1, onion2, onion3, onion4?

P.S. Code works just fine, but it just seems silly.

Thanks.

I tried to understand it, but all I found out is you can get rid of onion2:

    let make_svc = make_service_fn(|_| {
        let onion1 = Arc::clone(&state);
        async move {
            Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
                let onion3 = Arc::clone(&onion1);
                async move {
                    let onion4 = &*onion3;
                    handle_request(onion4, req).await
                }
            }))
        }
    });
1 Like

You can simplify to this:

async fn main() {
    let addr = SocketAddr::from(([0,0,0,0], 3000));
    
    // My global state
    let state = Arc::new(RequestState { foo: 321 });
    
    // How can I make some sanity this onion1, onion2, onion3, onion4? Is it possible?
    let make_svc = make_service_fn(|_| {
        let onion1 = Arc::clone(&state);
        async move {
            Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
                let onion2 = Arc::clone(&onion1);
                async move {
                    handle_request(&*onion2, req).await
                }
            }))
        }
    });
    let server = Server::bind(&addr).serve(make_svc);
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

playground

Basically what happens is:

  1. make_service_fn is called for each connection you receive.
  2. service_fn is called for each request in that connection. Note that with the Connection: keep-alive header, you can have several requests in one connection.

What happens now is:

  1. When a new connection occurs, the outer closure is called and it makes a clone of the state, which it gives to the closure in service_fn (through two moves). It has to clone it, because this may happen many times, so it can't give it's own copy away.
  2. When the closure in service_fn is called, it needs to clone it again, because the async block it returns must not borrow from the one copy stored in the service_fn closure — so another clone must be made to be owned by that async block it returns. Note that this closure may also be called many times (once per request in current connection), which is why it cannot give away it's own copy.

This is why two clones are necessary. And as you can see in the snippet above, it's possible to do with only two clones.

1 Like

See a similar example in

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.