Using hyper to modify a global state


#1

I’m pretty sure this is simple to solve, but I just cannot figure out how :neutral_face:

I’m trying to use a hyper-server to provide an interface for setting a global value (screen color).

let display = Display::open(DEVICE).expect("Cannot create display for device #0");
let mut overlay = Overlay::new(&display);
let overlay_mutex = Arc::new(Mutex::new(overlay));

let addr = "0.0.0.0:56501".parse().unwrap();
let server = Http::new().bind(&addr, move || Ok(HttpService { overlay: overlay_mutex.clone() } )).unwrap();
server.run().unwrap();

overlay is the global object I want to share with all requests.

I’m getting the following error

error[E0597]: `display` does not live long enough
   --> src/main.rs:268:37
    |
268 |     let mut overlay = Overlay::new(&display);
    |                                     ^^^^^^^ does not live long enough
...
290 | }
    | - borrowed value only lives until here (<-- end of my source file)
    |
    = note: borrowed value must be valid for the static lifetime...

There are too many parts I only have half knowledge of. I’m pretty sure I cannot give display a static lifetime, nor can I use thread_local for hyper. Or do I have to wrap the run() within some thread_local? It feels weird that run() is a blocking operation with a small scope and parameters passed to the server need to have the biggest possible lifetime/scope at the same time.

There were some usage examples for hyper-server before the switch to tokio which I could have adapted to my use case, but I didn’t find anything appropriate for the current API. Are there any usage examples beyond the “echo”-Server?


#2

Can Overlay own the Display instead of taking a reference to it?


#3

In this special case it would be possible, but I’d like to know the general solution where this isn’t an option.


#4

The only other (non-hack/safe) option here is to put Display into an Arc and share ownership that way (ie Overlay would have a clone of it). Otherwise, there’s no way to satisfy the 'static requirement.

It’s true that run blocks and thus those references to this thread’s stack variables will be kept alive, but it’s not expressible in the language. Some libs allow you to do this type of thing, such as crossbeam, but it’s tricky. For a multithreaded http server, putting stuff into an Arc is pretty simple though, and so avoiding the static lifetime isn’t too onerous.


#5

By the way, if you’re not married to hyper and willing to use the nightly compiler, Rocket has special design for such a thing: https://rocket.rs/guide/state/


#6

Thanks, that seems to work, but that simple change forced me to change about 20 occurrences within my rather small program. And now the API is incompatible to my other programs. Is there a workaround to accept a reference and Arc/Rc with a single declaration.

Am I forced to work without any compile-time lifetimes and wrap every bit with an Arc/Rc? Or is there an option to wrap normal code into a single Arc? (I hope this is comprehensible)


#7

Not married to hyper at all :grinning:
I just wanted to have a simple way to get my function called by an http-request. I don’t like to use the nightly compiler, but I’ll definitely have a look at rocket, thanks!


#8

Yes. You can abstract over owned or borrowed data using generics and traits like std::borrow::Borrow or std::convert::AsRef. So for example you can make a struct like:

struct Overlay<T: Borrow<Display>>(T)

You can then use an owned Display, a reference to one, or an Rc/Arc. Note that if you use T=Display(or Rc or Arc), the resulting type is 'static. If you use T=&Display, it’s not. So that’s all fine and dandy.

In addition to static, you may hit APIs that require you to be Send as well. So then T=Display (assuming it itself is Send) or T=Arc<Display> or T=&Display are valid, and T=Rc<Display> isn’t. If you need 'static and Send then you just pick the combo that satisfies that (ie owned Display or Arc<Display>).

No, you can work with lifetimes as described above. The 'static usually comes into play when multiple threads are involved, but code confined to a single thread can use plain old references without anything special.


#9

A simpler solution is to use https://github.com/tiny-http/tiny-http


#11

Thanks, this looks better suited for my case. I’ll give it a try, once I understood my current design problem.


#12

Great tip, thank you. I got it working that way.


Just to make sure I got this right:

fn main() {
    bcm_host::init();

    let display = Display::open(DEVICE).expect("Cannot create display for device #0");
    let overlay = Overlay::new(&display);
    let graphics: Graphics<[u8; 3]> = Graphics::new(overlay);

    let shareable_graphics = Arc::new(Mutex::new(graphics));

    println!("Listening on 0.0.0.0:56501 ...");
    let addr = "0.0.0.0:56501".parse().unwrap();
    let server = Http::new().bind(&addr, move || Ok(HttpService { graphics: shareable_graphics.clone() } )).unwrap();
    server.run().unwrap();
}

My only option is to modify the implementation of Overlay::new(&display) to accept an Arc (or more generally a Borrow)? How could i possibly use types from external crate with hyper that make use of compile-time lifetimes?


#13

Right - the NewService implementation that hyper expects in the bind call must be 'static. This means that type cannot borrow any data from the current stack, which is where the Display lives in the snippet above.

In the snippet above, you have a two options:

  1. Make Overlay own the Display. Graphics already owns the Overlay, so you could move the graphics object into the HttpService. The end result is that HttpService owns that whole graph.
  2. Make Overlay share ownership of the Display with the current stack frame (although I don’t see why that’s needed based on the snippet above, but maybe the real code is more involved). The rest works just like #1 above.

Depending on the type of Service your HttpService creates, you may want the HttpService (or parts of the object graph) to be wrapped in Rc so that those values can be shared with the service instances. But that’s now a separate situation from just bootstrapping the HttpService itself.


#14

First of all, thanks a lot for your time invested so far!

Display will generally be used by multiple Overlays.

Sorry, I don’t understand that part.

I still wonder what to do if Overlay wasn’t my code but was provided by some external crate, that I cannot modify. I think some example code demonstrating how to use hyper as server would help me. I also wonder how to start multiple servers, as they’re blocking - wrap them in multiple threads (more or less a theoretical question)?


#15

Sorry, I should’ve been clearer. I meant put the Display into an Rc<Display> and then give Overlay a clone of that Rc. You may need to use Arc<Display> if multiple threads are involved, but otherwise the idea is the same - Overlay shares ownership of the Display with other users. This also addresses your point about a Display being used by multiple Overlays.

If Overlay wasn’t yours and couldn’t be configured to not have non-static references, then it would be tough or potentially impossible.

For multiple servers, yeah - you’d start an instance on a thread. There’s also https://docs.rs/hyper/0.11.7/hyper/server/struct.Http.html#method.serve_addr_handle, which I saw just now. That gives you multiple servers sharing a reactor.


#16

Thank you very much. I think I now understood everything but I still find hyper quite impractical to use.

I’ll give tiny-http a chance.