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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/// AlternatingReady service, responding to a [AlternatingReadyRequest] with an
/// [AlternatingReadyResponse].
#[derive(Debug, Clone)]
pub struct AlternatingReadyService {
ready: bool,
}

/// Tower `Service` implementation for [AlternatingReadyService]: readiness alternates between
/// pending and ready, calls never fail and responses are ready immediately.
impl Service<AlternatingReadyRequest> for AlternatingReadyService {
type Response = AlternatingReadyResponse;

/// This service never fails.
type Error = Infallible;

/// Responses of this service are ready immediately.
type Future = Ready<Result<Self::Response, Self::Error>>;

/// Alternately return `Poll::Pending` and `Poll::Ready`.
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
if self.ready {
// Wake up the task to make sure the executor calls `poll_ready` again.
cx.waker().wake_by_ref();

self.ready = false;
Poll::Pending
} else {
self.ready = true;
Poll::Ready(Ok(()))
}
}

/// Always return [AlternatingReadyResponse].
///
/// # Panics
/// Panics if called without prior calling `poll_ready`.
fn call(&mut self, _req: AlternatingReadyRequest) -> Self::Future {
if !self.ready {
panic!("service not ready; poll_ready must be called first");
}
self.ready = false;
ready(Ok(AlternatingReadyResponse))
}
}

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
2
3
4
5
6
7
8
let mut service = AlternatingReadyService::new();

let service = service.ready().await?;
let _response = service.call(AlternatingReadyRequest).await?;

// We should invoke `ready` once again before invoking `call`!
// service.ready().await?;
let _response = service.call(AlternatingReadyRequest).await?;

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.