Tower, Episode 2: calling a Tower Service
In the previous episode we have seen that a Tower service is made up of the two methods poll_ready
and call
, which are subject to the following contract: callers first must invoke poll_ready
until it returns Poll::Ready
before they can invoke call
, else call
might panic.
This is the signature of poll_ready
:
1 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>; |
In order to invoke poll_ready
, we need a std::task::Context
. If you are familiar with Future
s, you will notice that its poll
method also has such an argument. So do we have to implement a Future
just to call a service?
Luckily not, because Tower already provides the ServiceExt
trait with some helper methods to either invoke poll_ready
or even chain invoking poll_ready
and call
according to their contracts.
Let’s first take a look at ServiceExt::oneshot
. According to the documentation, it “consumes this service, calling with the provided request once it is ready”.
1 | let mut service = EchoService; |
The Future
returned by oneshot
first invokes poll_ready
when poll
ed and then, if the service returns Poll::Ready
, invokes call
. Of course it’s not possible to invoke the service again, because oneshot
consumes it by taking it as self
. But it actually is the only way provided by Tower enforcing the contract between poll_ready
and call
.
Next let’s take a look at ServiceExt::ready
, which, according to the documentation, “yields a mutable reference to the service when it is ready to accept a request”.
1 | let mut service = EchoService; |
The Future
returned by ready
returns a mutable reference to the service which then can be used to call
it. As you can see from the above, we can even call
the service several times without checking its readiness. Of course this is against the contract and we should instead invoke ready
once again every time before we invoke call
, i.e. in the above code snippet we should uncomment line 8.
Finally there is ServiceExt::ready_oneshot
. It works like ready
, but it consumes the service, yet returns it again.
1 | let service = EchoService; |
Like for ready
, we can call
the returned service several times without checking its readiness. Therefore I consider this method misnamed, because “oneshot” for me means, well, no more than one invocation of call
.
You might now ask whether this service contract of always invoking poll_ready
before call
really matters that much. Well, not for our harmless EchoService
which is always ready and therefore its call
method never panics. Let’s look at some other service, behaving less nicely, in the next episode. As usual the full code is available in tower-experiments on GitHub.