Tower, Episode 3: Readiness
In the previous episode we have seen how to call a Tower service. In particular we have discussed the contract between poll_ready and call: callers must first invoke poll_ready until it returns Poll::Ready before they invoke call, else call might panic.
Of course some services, e.g. the EchoService described in the first episode, are always ready and therefore invokig call without poll_ready works fine.
But generally the internal state or external dependencies, e.g. database connections, can infuence the readiness of a service. Also, composing domain services with middleware, e.g. with a rate limit or a circuit breaker, naturally effect the readiness.
To demonstrate this, let’s take a look at a service which alternates between ready and not ready:
1 | /// AlternatingReady service, responding to a [AlternatingReadyRequest] with an |
The AlternatingReadyService tracks its readiness via the ready field. poll_ready is implemented such that, if the service has been ready before, it sets it as not ready and returns Poll::Pending; and vice versa. Also, call takes the ready field into account such that invoking call results in panicking if the service is not ready.
Of course this implementation is highly constructed, but it serves well to demonstrate the importance of the contract between poll_ready and call:
1 | let mut service = AlternatingReadyService::new(); |
In this example the second call fails with a panic, because the first one, or rather the ready invocation, leaves the service as not ready. If you want to check this out yourself, you can find the full code at tower-experiments on GitHub.