Hyper 0.12 requires the Service/NewService Send'able. How to migrate?

I am migrating from hyper 0.11 to 0.12. I have implemented NewService and Service traits, but hyper::server::Server::serve wants them to be Send'able. The previous version of hyper does not require it. Unfortunately, I can not make implementation of the Service with Send support (the initial design assumed single threaded processing). How can I solve this problem?

Here are the snippets of my code:

#[derive(Clone)]
struct Server {
    // internal fields, which are clonable but not sendable
}

impl hyper::service::Service for Server {
    type ReqBody = Body;
    type ResBody = Body;
    type Error = hyper::Error;
    type Future = Box<Future<Item=Response<Body>, Error=hyper::Error>>;

    fn call(&mut self, _req: Request<Body>) -> Box<Future<Item=Response<Body>, Error=hyper::Error>> {
        // something
    }
}

struct NewServer {
    singleton: Server
}

impl hyper::service::NewService for NewServer {
    type ReqBody = Body;
    type ResBody = Body;
    type Error = hyper::Error;
    type Service = Server;
    type Future = Box<Future<Item=Self::Service, Error=Self::InitError>>;
    type InitError = hyper::Error;

    fn new_service(&self) -> Box<Future<Item=Self::Service, Error=Self::InitError>> {
        Box::new(futures::future::ok(self.singleton.clone()))
    }
}

pub fn run(address: SocketAddr) -> errors::Result<()> {

    let server = Server::new(...);

    let new_service = NewServer{singleton: server};

    let bound = hyper::server::Server::try_bind(&address)
        .chain_err(|| format!("failure binding to {}", address))?;
    let server = bound.serve(new_service);

    let mut runtime = tokio::runtime::Runtime::new()
        .chain_err(|| "failure launching runtime")?;
    runtime.spawn(server);
    runtime.shutdown_on_idle().wait()
        .chain_err(|| "failure waiting for idle")
}

I believe this is to allow hyper to work with the default tokio runtime, which uses a threadpool to execute futures.

But curious - why can’t you make it Send? It might be as simple as switching to Arc from Rc, which is likely a common reason for not being Send. Or is yours more complicated?

1 Like

The server analyses incoming traffic, sends it further to external server, receives the response, analyses the response. It is like proxy server with analytics built-in. So, it manages state, which will need to be lock guarded everywhere, effectively serializing all of the treads. In my case single threaded lock free model was simpler and more efficient. No ways to proceed with hyper without making all thread-safe and Sendable?

Have you tried the current_thread runtime? It's doesn't have the Send requirement.

1 Like

Hmm, I wouldn’t think it’d serialize all of them unless you need to hold the lock for the duration of handling.

In the singlethreaded model, you’d sort of have a form of serialization as well in that while a handler is running, the reactor cannot service anything else. But I take your point - you’d need to redesign a bunch of stuff.

I’m not sure if hyper supports the old case. It would need to somehow expose the current_thread runtime of tokio, or rather, expose another set of APIs for executors that don’t require Send futures. Paging @seanmonstar :slight_smile:

2 Likes

current_thread would work, if hyper serve allowed non-sendable NewService too.

The requirement for Send is because:

  1. futures is moving towards a Send by default on Executors.
  2. Tokio needs Send if you going to use the threadpool executor.

Since hyper needs to start some background tasks, it thus needs an Executor, and chose to require that the Executor execute Send futures, including those used to define a Service. Providing the flexibility to choose would make the APIs extremely unwieldy.

There's usually several ways one can adjust their app so as to not need to lock the world while a request happens. A lock can be taken for only long enough to read or write the shared field, and then released again. Or state can be managed in a separate thread altogether, with synchronization happening with message passing.

In your case, you could consider replacing whatever synchronization you used (like a RefCell?) with a Mutex, but still only use the current_thread runtime. In that case, since there is literally zero contention, the mutex should be nearly free.

3 Likes

I got it running on single threaded runtime with Send support implemented. It was quite a big refactoring job for me. I use Arc<Mutex> instead of Rc<RefCell> now. Also, setup of the server required rework, because tokio runtime introduced new way of doing things, but the resulting code became shorter finally. I also dropped using reqwest and use hyper::client directly. Works OK, not much more code required. Thank you for your help.

1 Like