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

util: add CloneBoxService #615

Merged
merged 4 commits into from
Nov 9, 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/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

- **util**: Add `CloneBoxService` which is a `Clone + Send` boxed `Service`.

### Fixed

- **balance**: Remove redundant `Req: Clone` bound from `Clone` impls
Expand Down
3 changes: 3 additions & 0 deletions tower/src/util/boxed/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use std::{
/// future type to be dynamic. This type requires both the service and the
/// response future to be [`Send`].
///
/// If you need a boxed [`Service`] that implements [`Clone`] consider using
/// [`CloneBoxService`](crate::util::CloneBoxService).
///
/// See module level documentation for more details.
pub struct BoxService<T, U, E> {
inner: Box<dyn Service<T, Response = U, Error = E, Future = BoxFuture<U, E>> + Send>,
Expand Down
136 changes: 136 additions & 0 deletions tower/src/util/clone_boxed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use super::ServiceExt;
use futures_util::future::BoxFuture;
use std::{
fmt,
task::{Context, Poll},
};
use tower_layer::{layer_fn, LayerFn};
use tower_service::Service;

/// A [`Clone`] + [`Send`] boxed [`Service`].
///
/// [`CloneBoxService`] turns a service into a trait object, allowing the
/// response future type to be dynamic, and allowing the service to be cloned.
davidpdrsn marked this conversation as resolved.
Show resolved Hide resolved
///
/// This is similar to [`BoxService`](super::BoxService) except the resulting
/// service implements [`Clone`].
///
/// # Example
///
/// ```
/// use tower::{Service, ServiceBuilder, BoxError, util::CloneBoxService};
/// use std::time::Duration;
/// #
/// # struct Request;
/// # struct Response;
/// # impl Response {
/// # fn new() -> Self { Self }
/// # }
///
/// // This service has a complex type that is hard to name
/// let service = ServiceBuilder::new()
/// .map_request(|req| {
/// println!("received request");
/// req
/// })
/// .map_response(|res| {
/// println!("response produced");
/// res
/// })
/// .load_shed()
/// .concurrency_limit(64)
/// .timeout(Duration::from_secs(10))
/// .service_fn(|req: Request| async {
/// Ok::<_, BoxError>(Response::new())
/// });
/// # let service = assert_service(service);
///
/// // `CloneBoxService` will erase the type so it's nameable
/// let service: CloneBoxService<Request, Response, BoxError> = CloneBoxService::new(service);
/// # let service = assert_service(service);
///
/// // And we can still clone the service
/// let cloned_service = service.clone();
/// #
/// # fn assert_service<S, R>(svc: S) -> S
/// # where S: Service<R> { svc }
/// ```
pub struct CloneBoxService<T, U, E>(
Box<
dyn CloneService<T, Response = U, Error = E, Future = BoxFuture<'static, Result<U, E>>>
+ Send,
>,
);

impl<T, U, E> CloneBoxService<T, U, E> {
/// Create a new `CloneBoxService`.
pub fn new<S>(inner: S) -> Self
where
S: Service<T, Response = U, Error = E> + Clone + Send + 'static,
S::Future: Send + 'static,
{
let inner = inner.map_future(|f| Box::pin(f) as _);
CloneBoxService(Box::new(inner))
}

/// Returns a [`Layer`] for wrapping a [`Service`] in a [`CloneBoxService`]
/// middleware.
///
/// [`Layer`]: crate::Layer
pub fn layer<S>() -> LayerFn<fn(S) -> Self>
where
S: Service<T, Response = U, Error = E> + Clone + Send + 'static,
S::Future: Send + 'static,
{
layer_fn(Self::new)
}
}

impl<T, U, E> Service<T> for CloneBoxService<T, U, E> {
type Response = U;
type Error = E;
type Future = BoxFuture<'static, Result<U, E>>;

#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), E>> {
self.0.poll_ready(cx)
}

#[inline]
fn call(&mut self, request: T) -> Self::Future {
self.0.call(request)
}
}

impl<T, U, E> Clone for CloneBoxService<T, U, E> {
fn clone(&self) -> Self {
Self(self.0.clone_box())
}
}

trait CloneService<R>: Service<R> {
fn clone_box(
&self,
) -> Box<
dyn CloneService<R, Response = Self::Response, Error = Self::Error, Future = Self::Future>
+ Send,
>;
}

impl<R, T> CloneService<R> for T
where
T: Service<R> + Send + Clone + 'static,
{
fn clone_box(
&self,
) -> Box<dyn CloneService<R, Response = T::Response, Error = T::Error, Future = T::Future> + Send>
{
Box::new(self.clone())
}
}

impl<T, U, E> fmt::Debug for CloneBoxService<T, U, E> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("CloneBoxService").finish()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take it or leave it: we may want to consider including the request and response type names here, so different instances of CloneBoxService can be distinguished? Maybe something like

Suggested change
fmt.debug_struct("CloneBoxService").finish()
write!(
fmt,
"CloneBoxService<Request = {}, Response = {}, Error = {}>",
std::any::type_name::<T>(),
std::any::type_name::<U>(),
std::any::type_name::<E>(),
)

but, on the other hand, this could be quite long, and we might want to correctly handle multi-line formatting in alt mode ({:#})...so, maybe that's something to hold off on and add later if it's needed...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, BoxService doesn't do this, so it's probably better left for a separate PR if we do want to add something like that...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually thinking the type params are there such that this could be improved in the future, without a breaking change. I think I'll leave it as it is now and make an issue about improving the formatting, for BoxService as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually thinking the type params are there such that this could be improved in the future,

I don't think we want the type params to be Debug in any case; we can't actually include values of those types in the formatted output, since they're not part of the struct. I think the where clause won't ever be useful for future improvements to the debug format, since we'll never have instances of those types to format when formatting a CloneBoxService instance. We can use type_name to get the names of those types without requiring those types to be Debug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah doh of course! I've removed the bounds.

}
}
2 changes: 2 additions & 0 deletions tower/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod and_then;
mod boxed;
mod call_all;
mod clone_boxed;
mod either;

mod future_service;
Expand All @@ -22,6 +23,7 @@ mod then;
pub use self::{
and_then::{AndThen, AndThenLayer},
boxed::{BoxLayer, BoxService, UnsyncBoxService},
clone_boxed::CloneBoxService,
either::Either,
future_service::{future_service, FutureService},
map_err::{MapErr, MapErrLayer},
Expand Down