From b291d6689ffd92250b853ac0629f95e84705bdc0 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 9 Nov 2021 10:21:43 +0100 Subject: [PATCH 1/3] util: add `CloneService` This upstreams a little utility I'm using a bunch in axum. Its often useful to erase the type of a service while still being able to clone it. `BoxService` isn't `Clone` previously you had to combine it with `Buffer` but doing that a lot (which we did in axum) had measurable impact on performance. --- tower/CHANGELOG.md | 2 + tower/src/util/clone_boxed.rs | 138 ++++++++++++++++++++++++++++++++++ tower/src/util/mod.rs | 2 + 3 files changed, 142 insertions(+) create mode 100644 tower/src/util/clone_boxed.rs diff --git a/tower/CHANGELOG.md b/tower/CHANGELOG.md index 0671f77e5..52827e0e5 100644 --- a/tower/CHANGELOG.md +++ b/tower/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased +- **util**: Add `CloneService` which is a `Clone + Send` boxed `Service`. + ### Fixed - **balance**: Remove redundant `Req: Clone` bound from `Clone` impls diff --git a/tower/src/util/clone_boxed.rs b/tower/src/util/clone_boxed.rs new file mode 100644 index 000000000..c48702c4c --- /dev/null +++ b/tower/src/util/clone_boxed.rs @@ -0,0 +1,138 @@ +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. +/// +/// # 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); +/// +/// // `CloneService` will erase the type so its nameable +/// let service: CloneBoxService = CloneBoxService::new(service); +/// # let service = assert_service(service); +/// +/// // And we can still clone the service +/// let cloned_service = service.clone(); +/// # +/// # fn assert_service(svc: S) -> S +/// # where S: Service { svc } +/// ``` +pub struct CloneBoxService( + Box< + dyn CloneService>> + + Send, + >, +); + +impl CloneBoxService { + /// Create a new `CloneBoxService`. + pub fn new(inner: S) -> Self + where + S: Service + 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() -> LayerFn Self> + where + S: Service + Clone + Send + 'static, + S::Future: Send + 'static, + { + layer_fn(Self::new) + } +} + +impl Service for CloneBoxService { + type Response = U; + type Error = E; + type Future = BoxFuture<'static, Result>; + + #[inline] + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + #[inline] + fn call(&mut self, request: T) -> Self::Future { + self.0.call(request) + } +} + +impl Clone for CloneBoxService { + fn clone(&self) -> Self { + Self(self.0.clone_box()) + } +} + +trait CloneService: Service { + fn clone_box( + &self, + ) -> Box< + dyn CloneService + + Send, + >; +} + +impl CloneService for T +where + T: Service + Send + Clone + 'static, +{ + fn clone_box( + &self, + ) -> Box + Send> + { + Box::new(self.clone()) + } +} + +impl fmt::Debug for CloneBoxService +where + T: fmt::Debug, + U: fmt::Debug, + E: fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("CloneBoxService").finish() + } +} diff --git a/tower/src/util/mod.rs b/tower/src/util/mod.rs index 901034ae4..669a6e9ed 100644 --- a/tower/src/util/mod.rs +++ b/tower/src/util/mod.rs @@ -3,6 +3,7 @@ mod and_then; mod boxed; mod call_all; +mod clone_boxed; mod either; mod future_service; @@ -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}, From 28915aa9d1cab86aebb38a8fd6f928f0bd1c58d4 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 9 Nov 2021 19:45:12 +0100 Subject: [PATCH 2/3] Address review feedback --- tower/CHANGELOG.md | 2 +- tower/src/util/boxed/sync.rs | 3 +++ tower/src/util/clone_boxed.rs | 7 +++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tower/CHANGELOG.md b/tower/CHANGELOG.md index 52827e0e5..3caaf4b8e 100644 --- a/tower/CHANGELOG.md +++ b/tower/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- **util**: Add `CloneService` which is a `Clone + Send` boxed `Service`. +- **util**: Add `CloneBoxService` which is a `Clone + Send` boxed `Service`. ### Fixed diff --git a/tower/src/util/boxed/sync.rs b/tower/src/util/boxed/sync.rs index 641bc0c0f..4dbb06cf4 100644 --- a/tower/src/util/boxed/sync.rs +++ b/tower/src/util/boxed/sync.rs @@ -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 { inner: Box> + Send>, diff --git a/tower/src/util/clone_boxed.rs b/tower/src/util/clone_boxed.rs index c48702c4c..702cbf59f 100644 --- a/tower/src/util/clone_boxed.rs +++ b/tower/src/util/clone_boxed.rs @@ -7,11 +7,14 @@ use std::{ use tower_layer::{layer_fn, LayerFn}; use tower_service::Service; -/// A `Clone + Send` boxed `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. /// +/// This is similar to [`BoxService`](super::BoxService) except the resulting +/// service implements [`Clone`]. +/// /// # Example /// /// ``` @@ -42,7 +45,7 @@ use tower_service::Service; /// }); /// # let service = assert_service(service); /// -/// // `CloneService` will erase the type so its nameable +/// // `CloneBoxService` will erase the type so it's nameable /// let service: CloneBoxService = CloneBoxService::new(service); /// # let service = assert_service(service); /// From 975ae05a4f8aa25129b5f5f48300b679e7b23e3d Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 9 Nov 2021 19:48:32 +0100 Subject: [PATCH 3/3] remove needless trait bounds --- tower/src/util/clone_boxed.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tower/src/util/clone_boxed.rs b/tower/src/util/clone_boxed.rs index 702cbf59f..9468e7715 100644 --- a/tower/src/util/clone_boxed.rs +++ b/tower/src/util/clone_boxed.rs @@ -129,12 +129,7 @@ where } } -impl fmt::Debug for CloneBoxService -where - T: fmt::Debug, - U: fmt::Debug, - E: fmt::Debug, -{ +impl fmt::Debug for CloneBoxService { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("CloneBoxService").finish() }