Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More complex examples for Service trait #65

Closed
svartalf opened this issue Apr 2, 2018 · 2 comments
Closed

More complex examples for Service trait #65

svartalf opened this issue Apr 2, 2018 · 2 comments

Comments

@svartalf
Copy link

svartalf commented Apr 2, 2018

It is common for client libraries to have some kind of Connection struct,
which have various protocol-related methods, ex. abstract client (let's say, redis-like) can have methods like these:

struct Connection;
impl Connection {
    pub fn get(&self, key: String) -> Result<String, ()> { unimplemented!() }
    pub fn set(&self, key: String, value: String) -> Result<(), ()> { unimplemented!() }
    pub fn delete(&self, key: String) -> Result<(), ()> { unimplemented!() }
}

If that Connection implements tower::Service trait, it should provide call method, ex.

impl Service for Connection {
    type Request = MyRequest;
    type Response = MyResponse;
    type Error = ();
    type Future = MyResponseFuture<Item=Self::Response, Error=Self::Error>;

    fn call(&mut self, req: Self::Request) -> Self::Future {
        unimplemented!()
    }
}

Hard part here is that current tokio implementation is not very easy; at least as for me and for a other random people from the internet:

Speaking about Service again, with a current futures crate (0.1.20 as for now) it is hard to implement call method.

For example, if Connection struct contains Framed<_, _> in it, it's own sink method can be used to send incoming request to an underline framed object, ex.:

struct Connection {
    inner: Framed<TcpStream, MyCodec>
}
fn call(&mut self, req: Self::Request) -> Self::Future {
    self.inner.send(req);
    // and return a result
}

Two caveats appear here:

  1. futures::Sink::send is moving Self, which is okay by itself, but became a problem if you still want to keep it in the Connection;
  2. call should return future that will resolve into MyResponse later, but it can't be achieved easily; inner Framed<_, _>, as a stream, should be polled somehow and somewhere and should resolve returned earlier future (MyResponseFuture in an example above).

@benashford have a redis-async-rs crate, which solves second problem via multiple channels: MyResponseFuture in that case contains oneshot::Receiver<MyResponse> and Connection by itself polls inner stream and keeping track of the oneshot::Sender<MyResponse> instances.

While it seems to solve the problem, it looks kinda complicated and might reduce tokio + tower expansion.

Can we elaborate here and provide more complex examples of how clients should be done properly?
AFAIR, there were examples of the pipelined and multiplexed protocols at the tokio repo earlier, maybe we can do something similar?

I'm not sure how exactly can I help here, but I'm willing too, just give me a hint :)

@carllerche
Copy link
Member

I think the best strategy here is to push forward work on tower-http. This can also serve as an example of how to organize clients.

I have a spike here: https://github.com/tower-rs/tower-http/pull/13/files#diff-5a188c9609f41af41f2096bd7c8682bcR22

More work will be coming. The other aspect is documentation. I will track this as part of the doc issue.

@carllerche carllerche mentioned this issue Apr 4, 2019
8 tasks
@carllerche
Copy link
Member

I'm going to close this issue for now. If you have further questions, you can post them here or continue the discussion in #33.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants