Currently, Service contains:
trait Service {
// …
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, req: Request) -> Self::Future
}
Convention requires that the user call poll_ready(), and only when it returns Poll::Ready(Ok(())) is the user permitted to call call(). call() is permitted to panic if misused.
If we introduce token – an opaque type which the user cannot construct – we can instead say:
trait Service {
// …
type Token;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<Self::Token, Self::Error>>;
fn call(&mut self, req: Request, token: Self::Token) -> Self::Future
}
Now the type system enforces the required call sequence. The user must call poll_ready() and receive a Poll::Ready(Ok(token)), since call() now requires such a token to accompany each request.
This pattern has benefits beyond just preventing API misuse. The token type could itself hold the resources necessary to handle a request, like a lock on a table slot or a connection checked out from a connection pool. In other words, this proposal reduces the TOCTOU raciness of the poll_ready() -> call() dance.
A token also provides an alternative to the disarming mechanism proposed in #408: a user who got a Poll::Ready(Ok(_)) may either return their token to the service via call(), or they may forfeit the opportunity to make a request by dropping their token. Just as type system can guarantee the correct flow between poll_ready() and call(), the ownership system can guarantee use-it-or-lose-it semantics for every Poll::Ready(Ok(_)) response, ensuring services can all reliably distribute and reclaim per-request resources.
Currently,
Servicecontains:Convention requires that the user call
poll_ready(), and only when it returnsPoll::Ready(Ok(()))is the user permitted to callcall().call()is permitted to panic if misused.If we introduce token – an opaque type which the user cannot construct – we can instead say:
Now the type system enforces the required call sequence. The user must call
poll_ready()and receive aPoll::Ready(Ok(token)), sincecall()now requires such atokento accompany each request.This pattern has benefits beyond just preventing API misuse. The token type could itself hold the resources necessary to handle a request, like a lock on a table slot or a connection checked out from a connection pool. In other words, this proposal reduces the TOCTOU raciness of the
poll_ready()->call()dance.A token also provides an alternative to the disarming mechanism proposed in #408: a user who got a
Poll::Ready(Ok(_))may either return their token to the service viacall(), or they may forfeit the opportunity to make a request by dropping their token. Just as type system can guarantee the correct flow betweenpoll_ready()andcall(), the ownership system can guarantee use-it-or-lose-it semantics for everyPoll::Ready(Ok(_))response, ensuring services can all reliably distribute and reclaim per-request resources.