-
Notifications
You must be signed in to change notification settings - Fork 280
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
Add a ReadyService
trait
#25
Conversation
Incidentally, the actual PR diff is more an illustration of the trait than anything. If merged, docs, etc... would need to be added and we would probably want to add combinators like |
It's interesting that I think this change seems fine to me, once the mentioned helpers/combinators are introduced. |
Should this be a separate trait? It really sounds like a specialized struct ReadyService<F>(F);
impl<F, Req, Res, Err, Fut> Service for ReadyService<F>
where
F: FnMut(Req) -> Fut,
Fut: Future<Item=Res, Error=Err>,
{
type Request = Req;
type Response = Res;
type Error = Err;
type Future = Fut;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
// always ready
Ok(Async::Ready(()))
}
fn call(&mut self, req: S::Request) -> S::Future {
(self.0)(req)
}
} |
@seanmonstar The reason I didn't make it a specialization of It would also force an explicit step before having a server run an unbounded service:
|
Unless there are objections, I am going to merge this and create an issue tracking that the utility of |
Looks good to me. :) |
I added more documentation. Additional helpers, such as limit, will be deferred to another PR. |
This PR adds a
ReadyService
trait. AReadyService
is similar toService
except that it is always "ready" to accept a request. AReadyService
implementation can immediately error the response if it is "at capacity", but it has no strategy by which to notify the caller that it becomes ready again.It is expected that types will usually implement either
Service
ORReadyService
(though, there may be cases in which a type can implement both). It is also expected that server implementations will only acceptService
values.Adapting
ReadyService
->Service
In order to adapt a
ReadyService
to aService
, I can think of a couple strategies off the top of my head.Don't do anything special.
In this case, there will be some type
AdaptReadyService
(naming TBD) that takes aReadyService
and implementsService
such thatpoll_ready
always returnsReady
. This requires the user to make an explicit step to say that the server will not have any mechanism to handle back pressure.Add a limiter
In this case, a
ReadyService
value is passed toLimit::new(my_ready_service, n)
. This type ensures that there are onlyn
in-flight requests. This limiter can be scoped to the connection (n
in-flight requests per connection) or per "app" so that the number of in-flight requests are limited across many connections.Service
->ReadyService
There are reasons to want to adapt the other direction as well. For example a router (a single service that routes a request to one of many possible inner services) cannot easily provide a back pressure strategy. In this case, a router would want to be initialized with a set of
ReadyService
values.To adapt a
ReadyService
toService
, the primary strategy would be to add a buffering layer. This buffer would ideally have an upper limit. When the upper limit is reached, the buffer starts erroring requests immediately.Higher level frameworks
Obviously, introducing a new trait adds non trivial complexity. That said, I would expect that
ReadyService
would provide a better suited trait for reducing initial friction. I would not necessarily expect frameworks to ask their users to implementReadyService
, but frameworks could use this trait internally and build up a tower stack usingRouter
,Limit
,Buffer
, etc... and eventually provide aService
representing the entire stack and pass that to a server.Alternatives
There are two alternatives to introducing
ReadyService
.Keep things as is.
In this case, a "ready service" would be a type that implements
Service
such thatpoll_ready
always returnsReady
. The service would need to document thatpoll_ready
does not need to be called beforecall
(as there are some services that requirepoll_ready
to returnReady
before callingcall
).The router type would accept a set of
Service
values, documenting that these must be "ready service" values and as suchpoll_ready
will not be called. This means, if a service that requirespoll_ready
to be called is passed, it would just hang (for example,Reconnect
).Remove
poll_ready
fromService
.If
ReadyService
is needed, ispoll_ready
actually helpful? I would say yes as most middleware implementations so far heavily rely onpoll_ready
to do the "right thing". The only middleware that I have written that requires a "ready service" is the router.