Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

service: Clarify subtly around cloning and readiness #548

Merged
merged 3 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tower-service/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Clarify subtlety around cloning and readiness in the `Service` docs.

# 0.3.1 (November 29, 2019)

- Improve example in `Service` docs. ([#510])
Expand Down
77 changes: 77 additions & 0 deletions tower-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,83 @@ use std::task::{Context, Poll};
/// `Service` provides a mechanism by which the caller is able to coordinate
/// readiness. `Service::poll_ready` returns `Ready` if the service expects that
/// it is able to process a request.
///
/// # Be careful when cloning inner services
///
/// Services are permitted to panic if `call` is invoked without obtaining `Poll::Ready(Ok(()))`
/// from `poll_ready`. You should therefore be careful when cloning services for example to move
/// them into boxed futures. Even though the original service is ready, the clone might not be.
///
/// Therefore this kind of code is wrong and might panic:
///
/// ```rust
/// # use std::pin::Pin;
/// # use std::task::{Poll, Context};
/// # use std::future::Future;
/// # use tower_service::Service;
/// #
/// struct Wrapper<S> {
/// inner: S,
/// }
///
/// impl<R, S> Service<R> for Wrapper<S>
/// where
/// S: Service<R> + Clone + 'static,
/// R: 'static,
/// {
/// type Response = S::Response;
/// type Error = S::Error;
/// type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
/// Poll::Ready(Ok(()))
/// }
///
/// fn call(&mut self, req: R) -> Self::Future {
/// let mut inner = self.inner.clone();
/// Box::pin(async move {
/// // `inner` might not be ready since its a clone
/// inner.call(req).await
/// })
/// }
/// }
/// ```
///
/// You should instead use [`std::mem::replace`] to take the service that was ready:
///
/// ```rust
/// # use std::pin::Pin;
/// # use std::task::{Poll, Context};
/// # use std::future::Future;
/// # use tower_service::Service;
/// #
/// struct Wrapper<S> {
/// inner: S,
/// }
///
/// impl<R, S> Service<R> for Wrapper<S>
/// where
/// S: Service<R> + Clone + 'static,
/// R: 'static,
/// {
/// type Response = S::Response;
/// type Error = S::Error;
/// type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
/// Poll::Ready(Ok(()))
/// }
///
/// fn call(&mut self, req: R) -> Self::Future {
/// let clone = self.inner.clone();
/// // take the service that was ready
/// let mut inner = std::mem::replace(&mut self.inner, clone);
/// Box::pin(async move {
/// inner.call(req).await
/// })
/// }
/// }
/// ```
pub trait Service<Request> {
/// Responses given by the service.
type Response;
Expand Down