From f57e31b0e6355c2a5c7fd0b681cb03b865ddcfe1 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:31:45 -0800 Subject: [PATCH] Add `util::BoxCloneSyncServiceLayer` (#802) cc #777 --- tower/src/util/boxed/layer_clone_sync.rs | 129 +++++++++++++++++++++++ tower/src/util/boxed/mod.rs | 4 +- tower/src/util/boxed_clone_sync.rs | 2 +- tower/src/util/mod.rs | 4 +- 4 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 tower/src/util/boxed/layer_clone_sync.rs diff --git a/tower/src/util/boxed/layer_clone_sync.rs b/tower/src/util/boxed/layer_clone_sync.rs new file mode 100644 index 000000000..950e66be6 --- /dev/null +++ b/tower/src/util/boxed/layer_clone_sync.rs @@ -0,0 +1,129 @@ +use std::{fmt, sync::Arc}; +use tower_layer::{layer_fn, Layer}; +use tower_service::Service; + +use crate::util::BoxCloneSyncService; + +/// A [`Clone`] + [`Send`] + [`Sync`] boxed [`Layer`]. +/// +/// [`BoxCloneSyncServiceLayer`] turns a layer into a trait object, allowing both the [`Layer`] itself +/// and the output [`Service`] to be dynamic, while having consistent types. +/// +/// This [`Layer`] produces [`BoxCloneSyncService`] instances erasing the type of the +/// [`Service`] produced by the wrapped [`Layer`]. +/// +/// This is similar to [`BoxCloneServiceLayer`](super::BoxCloneServiceLayer) except the layer and resulting +/// service implements [`Sync`]. +/// +/// # Example +/// +/// `BoxCloneSyncServiceLayer` can, for example, be useful to create layers dynamically that otherwise wouldn't have +/// the same types, when the underlying service must be clone and sync (for example, when building a Hyper connector). +/// In this example, we include a [`Timeout`] layer only if an environment variable is set. We can use +/// `BoxCloneSyncServiceLayer` to return a consistent type regardless of runtime configuration: +/// +/// ``` +/// use std::time::Duration; +/// use tower::{Service, ServiceBuilder, BoxError}; +/// use tower::util::{BoxCloneSyncServiceLayer, BoxCloneSyncService}; +/// +/// # +/// # struct Request; +/// # struct Response; +/// # impl Response { +/// # fn new() -> Self { Self } +/// # } +/// +/// fn common_layer() -> BoxCloneSyncServiceLayer +/// where +/// S: Service + Clone + Send + Sync + 'static, +/// S::Future: Send + 'static, +/// S::Error: Into + 'static, +/// { +/// let builder = ServiceBuilder::new() +/// .concurrency_limit(100); +/// +/// if std::env::var("SET_TIMEOUT").is_ok() { +/// let layer = builder +/// .timeout(Duration::from_secs(30)) +/// .into_inner(); +/// +/// BoxCloneSyncServiceLayer::new(layer) +/// } else { +/// let layer = builder +/// .map_err(Into::into) +/// .into_inner(); +/// +/// BoxCloneSyncServiceLayer::new(layer) +/// } +/// } +/// +/// // We can clone the layer (this is true of BoxLayer as well) +/// let boxed_clone_sync_layer = common_layer(); +/// +/// let cloned_sync_layer = boxed_clone_sync_layer.clone(); +/// +/// // Using the `BoxCloneSyncServiceLayer` we can create a `BoxCloneSyncService` +/// let service: BoxCloneSyncService = ServiceBuilder::new().layer(cloned_sync_layer) +/// .service_fn(|req: Request| async { +/// Ok::<_, BoxError>(Response::new()) +/// }); +/// +/// # 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 } +/// +/// ``` +/// +/// [`Layer`]: tower_layer::Layer +/// [`Service`]: tower_service::Service +/// [`BoxService`]: super::BoxService +/// [`Timeout`]: crate::timeout +pub struct BoxCloneSyncServiceLayer { + boxed: Arc> + Send + Sync + 'static>, +} + +impl BoxCloneSyncServiceLayer { + /// Create a new [`BoxCloneSyncServiceLayer`]. + pub fn new(inner_layer: L) -> Self + where + L: Layer + Send + Sync + 'static, + L::Service: Service + Send + Sync + Clone + 'static, + >::Future: Send + 'static, + { + let layer = layer_fn(move |inner: In| { + let out = inner_layer.layer(inner); + BoxCloneSyncService::new(out) + }); + + Self { + boxed: Arc::new(layer), + } + } +} + +impl Layer for BoxCloneSyncServiceLayer { + type Service = BoxCloneSyncService; + + fn layer(&self, inner: In) -> Self::Service { + self.boxed.layer(inner) + } +} + +impl Clone for BoxCloneSyncServiceLayer { + fn clone(&self) -> Self { + Self { + boxed: Arc::clone(&self.boxed), + } + } +} + +impl fmt::Debug for BoxCloneSyncServiceLayer { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BoxCloneSyncServiceLayer").finish() + } +} diff --git a/tower/src/util/boxed/mod.rs b/tower/src/util/boxed/mod.rs index 93a9e0dfe..7da5d63cb 100644 --- a/tower/src/util/boxed/mod.rs +++ b/tower/src/util/boxed/mod.rs @@ -1,9 +1,11 @@ mod layer; mod layer_clone; +mod layer_clone_sync; mod sync; mod unsync; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::{ - layer::BoxLayer, layer_clone::BoxCloneServiceLayer, sync::BoxService, unsync::UnsyncBoxService, + layer::BoxLayer, layer_clone::BoxCloneServiceLayer, layer_clone_sync::BoxCloneSyncServiceLayer, + sync::BoxService, unsync::UnsyncBoxService, }; diff --git a/tower/src/util/boxed_clone_sync.rs b/tower/src/util/boxed_clone_sync.rs index e8cbe0077..d62e8ff22 100644 --- a/tower/src/util/boxed_clone_sync.rs +++ b/tower/src/util/boxed_clone_sync.rs @@ -24,7 +24,7 @@ pub struct BoxCloneSyncService( ); impl BoxCloneSyncService { - /// Create a new `BoxCloneService`. + /// Create a new `BoxCloneSyncService`. pub fn new(inner: S) -> Self where S: Service + Clone + Send + Sync + 'static, diff --git a/tower/src/util/mod.rs b/tower/src/util/mod.rs index dcc35cc42..4c56de813 100644 --- a/tower/src/util/mod.rs +++ b/tower/src/util/mod.rs @@ -24,7 +24,9 @@ pub mod rng; pub use self::{ and_then::{AndThen, AndThenLayer}, - boxed::{BoxCloneServiceLayer, BoxLayer, BoxService, UnsyncBoxService}, + boxed::{ + BoxCloneServiceLayer, BoxCloneSyncServiceLayer, BoxLayer, BoxService, UnsyncBoxService, + }, boxed_clone::BoxCloneService, boxed_clone_sync::BoxCloneSyncService, either::Either,