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

feat(util) Implement UnsyncBoxCloneService #746

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions tower/src/util/boxed_clone/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod sync;
mod unsync;

#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
pub use self::{
sync::BoxCloneService, unsync::UnsyncBoxCloneService,
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::ServiceExt;
use crate::ServiceExt;
use futures_util::future::BoxFuture;
use std::{
fmt,
Expand Down
90 changes: 90 additions & 0 deletions tower/src/util/boxed_clone/unsync.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::ServiceExt;
use futures_util::future::LocalBoxFuture;
use std::{
fmt,
task::{Context, Poll},
};
use tower_layer::{layer_fn, LayerFn};
use tower_service::Service;

/// A boxed [`CloneService`] trait object.
///
/// This type alias represents a boxed future that is *not* [`Send`] and must
/// remain on the current thread.
pub struct UnsyncBoxCloneService<T, U, E>(
Box<
dyn UnsyncCloneService<T, Response = U, Error = E, Future = LocalBoxFuture<'static, Result<U, E>>>
>,
);

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

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

impl<T, U, E> Service<T> for UnsyncBoxCloneService<T, U, E> {
type Response = U;
type Error = E;
type Future = LocalBoxFuture<'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 UnsyncBoxCloneService<T, U, E> {
fn clone(&self) -> Self {
Self(self.0.clone_box())
}
}

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

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

impl<T, U, E> fmt::Debug for UnsyncBoxCloneService<T, U, E> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("BoxCloneService").finish()
}
}
2 changes: 1 addition & 1 deletion tower/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub mod rng;
pub use self::{
and_then::{AndThen, AndThenLayer},
boxed::{BoxCloneServiceLayer, BoxLayer, BoxService, UnsyncBoxService},
boxed_clone::BoxCloneService,
boxed_clone::{BoxCloneService, UnsyncBoxCloneService},
either::Either,
future_service::{future_service, FutureService},
map_err::{MapErr, MapErrLayer},
Expand Down
14 changes: 13 additions & 1 deletion tower/tests/util/service_fn.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use futures_util::future::ready;
use tower::util::service_fn;
use tower::util::{service_fn, UnsyncBoxCloneService};
use tower_service::Service;

#[tokio::test(flavor = "current_thread")]
Expand All @@ -10,3 +10,15 @@
let answer = add_one.call(1).await.unwrap();
assert_eq!(answer, 2);
}

#[tokio::test(flavor = "current_thread")]
async fn boxed_clone() {
let _t = super::support::trace_init();
let x = std::rc::Rc::new(1);

Check warning on line 17 in tower/tests/util/service_fn.rs

View workflow job for this annotation

GitHub Actions / test-versions (nightly)

unused variable: `x`

Check warning on line 17 in tower/tests/util/service_fn.rs

View workflow job for this annotation

GitHub Actions / check-msrv

unused variable: `x`
let mut add_one = service_fn(|req| ready(Ok::<_, ()>(req + 1)));
let mut cloned = UnsyncBoxCloneService::new(add_one);
let answer = cloned.call(1).await.unwrap();
assert_eq!(answer, 2);
let answer = add_one.call(1).await.unwrap();
assert_eq!(answer, 2);
}
Loading