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

Add util::BoxCloneSyncServiceLayer #802

Merged
merged 4 commits into from
Dec 10, 2024
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
129 changes: 129 additions & 0 deletions tower/src/util/boxed/layer_clone_sync.rs
Original file line number Diff line number Diff line change
@@ -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<S, T>() -> BoxCloneSyncServiceLayer<S, T, S::Response, BoxError>
/// where
/// S: Service<T> + Clone + Send + Sync + 'static,
/// S::Future: Send + 'static,
/// S::Error: Into<BoxError> + '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<Request, Response, BoxError> = 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<S, R>(svc: S) -> S
/// # where S: Service<R> { svc }
///
/// ```
///
/// [`Layer`]: tower_layer::Layer
/// [`Service`]: tower_service::Service
/// [`BoxService`]: super::BoxService
/// [`Timeout`]: crate::timeout
pub struct BoxCloneSyncServiceLayer<In, T, U, E> {
boxed: Arc<dyn Layer<In, Service = BoxCloneSyncService<T, U, E>> + Send + Sync + 'static>,
}

impl<In, T, U, E> BoxCloneSyncServiceLayer<In, T, U, E> {
/// Create a new [`BoxCloneSyncServiceLayer`].
pub fn new<L>(inner_layer: L) -> Self
where
L: Layer<In> + Send + Sync + 'static,
L::Service: Service<T, Response = U, Error = E> + Send + Sync + Clone + 'static,
<L::Service as Service<T>>::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<In, T, U, E> Layer<In> for BoxCloneSyncServiceLayer<In, T, U, E> {
type Service = BoxCloneSyncService<T, U, E>;

fn layer(&self, inner: In) -> Self::Service {
self.boxed.layer(inner)
}
}

impl<In, T, U, E> Clone for BoxCloneSyncServiceLayer<In, T, U, E> {
fn clone(&self) -> Self {
Self {
boxed: Arc::clone(&self.boxed),
}
}
}

impl<In, T, U, E> fmt::Debug for BoxCloneSyncServiceLayer<In, T, U, E> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("BoxCloneSyncServiceLayer").finish()
}
}
4 changes: 3 additions & 1 deletion tower/src/util/boxed/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
};
2 changes: 1 addition & 1 deletion tower/src/util/boxed_clone_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct BoxCloneSyncService<T, U, E>(
);

impl<T, U, E> BoxCloneSyncService<T, U, E> {
/// Create a new `BoxCloneService`.
/// Create a new `BoxCloneSyncService`.
pub fn new<S>(inner: S) -> Self
where
S: Service<T, Response = U, Error = E> + Clone + Send + Sync + 'static,
Expand Down
4 changes: 3 additions & 1 deletion tower/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading