Skip to content

Make poll_ready() return a token which call() consumes #412

@willglynn

Description

@willglynn

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-serviceArea: The tower `Service` traitC-feature-requestCategory: A feature request, i.e: not implemented / a PR.I-needs-decisionIssues in need of decision.P-mediumMedium priority

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions