From 8a58a341a464db928e60309a4b5a66081ee7ecea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 27 Dec 2020 14:15:42 +0000 Subject: [PATCH 1/2] service improvements (#233) --- .github/workflows/bench.yml | 29 -- actix-connect/src/connector.rs | 4 +- actix-connect/src/resolve.rs | 4 +- actix-connect/src/service.rs | 8 +- actix-connect/src/ssl/openssl.rs | 8 +- actix-connect/src/ssl/rustls.rs | 4 +- actix-service/CHANGES.md | 8 +- actix-service/Cargo.toml | 13 +- actix-service/benches/and_then.rs | 334 ------------------ .../benches/unsafecell_vs_refcell.rs | 110 ------ actix-service/src/and_then.rs | 103 +++--- actix-service/src/and_then_apply_fn.rs | 334 ------------------ actix-service/src/apply.rs | 40 +-- actix-service/src/apply_cfg.rs | 85 +++-- actix-service/src/boxed.rs | 19 +- actix-service/src/fn_service.rs | 14 +- actix-service/src/lib.rs | 39 +- actix-service/src/map.rs | 58 +-- actix-service/src/map_config.rs | 2 +- actix-service/src/map_err.rs | 50 +-- actix-service/src/map_init_err.rs | 31 +- actix-service/src/pipeline.rs | 62 +--- actix-service/src/then.rs | 106 +++--- actix-service/src/transform.rs | 61 ++-- actix-service/src/transform_err.rs | 27 +- actix-tracing/src/lib.rs | 7 +- actix-utils/src/timeout.rs | 6 +- 27 files changed, 387 insertions(+), 1179 deletions(-) delete mode 100644 .github/workflows/bench.yml delete mode 100644 actix-service/benches/and_then.rs delete mode 100644 actix-service/benches/unsafecell_vs_refcell.rs delete mode 100644 actix-service/src/and_then_apply_fn.rs diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml deleted file mode 100644 index 7c76e171..00000000 --- a/.github/workflows/bench.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Benchmark (Linux) - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - master - - '1.0' - -jobs: - check_benchmark: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - override: true - - - name: Check benchmark - uses: actions-rs/cargo@v1 - with: - command: bench - args: --package=actix-service diff --git a/actix-connect/src/connector.rs b/actix-connect/src/connector.rs index e4c86d91..d3ef9813 100644 --- a/actix-connect/src/connector.rs +++ b/actix-connect/src/connector.rs @@ -75,9 +75,7 @@ impl Service> for TcpConnector { #[allow(clippy::type_complexity)] type Future = Either, Ready>>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, req: Connect) -> Self::Future { let port = req.port(); diff --git a/actix-connect/src/resolve.rs b/actix-connect/src/resolve.rs index 85edf0d8..2c75cc0d 100644 --- a/actix-connect/src/resolve.rs +++ b/actix-connect/src/resolve.rs @@ -110,9 +110,7 @@ impl Service> for Resolver { Ready, Self::Error>>, >; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, mut req: Connect) -> Self::Future { if req.addr.is_some() { diff --git a/actix-connect/src/service.rs b/actix-connect/src/service.rs index ef5d04da..b942d230 100644 --- a/actix-connect/src/service.rs +++ b/actix-connect/src/service.rs @@ -94,9 +94,7 @@ impl Service> for ConnectService { type Error = ConnectError; type Future = ConnectServiceResponse; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, req: Connect) -> Self::Future { ConnectServiceResponse { @@ -163,9 +161,7 @@ impl Service> for TcpConnectService { type Error = ConnectError; type Future = TcpConnectServiceResponse; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, req: Connect) -> Self::Future { TcpConnectServiceResponse { diff --git a/actix-connect/src/ssl/openssl.rs b/actix-connect/src/ssl/openssl.rs index e1c6b6fb..a9bcc3c7 100644 --- a/actix-connect/src/ssl/openssl.rs +++ b/actix-connect/src/ssl/openssl.rs @@ -100,9 +100,7 @@ where #[allow(clippy::type_complexity)] type Future = Either, Ready>>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, stream: Connection) -> Self::Future { trace!("SSL Handshake start for: {:?}", stream.host()); @@ -220,9 +218,7 @@ impl Service for OpensslConnectService { type Error = ConnectError; type Future = OpensslConnectServiceResponse; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, req: Connect) -> Self::Future { OpensslConnectServiceResponse { diff --git a/actix-connect/src/ssl/rustls.rs b/actix-connect/src/ssl/rustls.rs index 3e646082..984fbe49 100644 --- a/actix-connect/src/ssl/rustls.rs +++ b/actix-connect/src/ssl/rustls.rs @@ -96,9 +96,7 @@ where type Error = std::io::Error; type Future = ConnectAsyncExt; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, stream: Connection) -> Self::Future { trace!("SSL Handshake start for: {:?}", stream.host()); diff --git a/actix-service/CHANGES.md b/actix-service/CHANGES.md index 971741e8..3b9a9cc0 100644 --- a/actix-service/CHANGES.md +++ b/actix-service/CHANGES.md @@ -3,10 +3,14 @@ ## Unreleased - 2020-xx-xx * `Service`, other traits, and many type signatures now take the the request type as a type parameter instead of an associated type. [#232] -* Upgrade `pin-project` to `1.0`. - +* Add `always_ready!` and `forward_ready!` macros. [#233] +* Crate is now `no_std`. [#233] +* Migrate pin projections to `pin-project-lite`. [#233] +* Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the + `.and_then(apply_fn(...))` construction. [#233] [#232]: https://github.com/actix/actix-net/pull/232 +[#233]: https://github.com/actix/actix-net/pull/233 ## 1.0.6 - 2020-08-09 diff --git a/actix-service/Cargo.toml b/actix-service/Cargo.toml index 1505873b..60818968 100644 --- a/actix-service/Cargo.toml +++ b/actix-service/Cargo.toml @@ -17,17 +17,10 @@ name = "actix_service" path = "src/lib.rs" [dependencies] -futures-util = "0.3.1" -pin-project = "1.0.0" +pin-project-lite = "0.2" +futures-util = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.7", default-features = false } [dev-dependencies] actix-rt = "1.0.0" criterion = "0.3" - -[[bench]] -name = "unsafecell_vs_refcell" -harness = false - -[[bench]] -name = "and_then" -harness = false diff --git a/actix-service/benches/and_then.rs b/actix-service/benches/and_then.rs deleted file mode 100644 index c8aa315d..00000000 --- a/actix-service/benches/and_then.rs +++ /dev/null @@ -1,334 +0,0 @@ -use actix_service::boxed::BoxFuture; -use actix_service::IntoService; -use actix_service::Service; -/// Benchmark various implementations of and_then -use criterion::{criterion_main, Criterion}; -use futures_util::future::join_all; -use futures_util::future::TryFutureExt; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{ - cell::{RefCell, UnsafeCell}, - marker::PhantomData, -}; - -/* - * Test services A,B for AndThen service implementations - */ - -async fn svc1(_: ()) -> Result { - Ok(1) -} - -async fn svc2(req: usize) -> Result { - Ok(req + 1) -} - -/* - * AndThenUC - original AndThen service based on UnsafeCell - * Cut down version of actix_service::AndThenService based on actix-service::Cell - */ - -struct AndThenUC(Rc>, PhantomData); - -impl AndThenUC { - fn new(a: A, b: B) -> Self - where - A: Service, - B: Service, - { - Self(Rc::new(UnsafeCell::new((a, b))), PhantomData) - } -} - -impl Clone for AndThenUC { - fn clone(&self) -> Self { - Self(self.0.clone(), PhantomData) - } -} - -impl Service for AndThenUC -where - A: Service, - B: Service, -{ - type Response = B::Response; - type Error = A::Error; - type Future = AndThenServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Req) -> Self::Future { - let fut = unsafe { &mut *(*self.0).get() }.0.call(req); - AndThenServiceResponse { - state: State::A(fut, Some(self.0.clone())), - _phantom: PhantomData, - } - } -} - -#[pin_project::pin_project] -pub(crate) struct AndThenServiceResponse -where - A: Service, - B: Service, -{ - #[pin] - state: State, - _phantom: PhantomData, -} - -#[pin_project::pin_project(project = StateProj)] -enum State -where - A: Service, - B: Service, -{ - A(#[pin] A::Future, Option>>), - B(#[pin] B::Future), - Empty(PhantomData), -} - -impl Future for AndThenServiceResponse -where - A: Service, - B: Service, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - match this.state.as_mut().project() { - StateProj::A(fut, b) => match fut.poll(cx)? { - Poll::Ready(res) => { - let b = b.take().unwrap(); - this.state.set(State::Empty(PhantomData)); // drop fut A - let fut = unsafe { &mut (*b.get()).1 }.call(res); - this.state.set(State::B(fut)); - self.poll(cx) - } - Poll::Pending => Poll::Pending, - }, - StateProj::B(fut) => fut.poll(cx).map(|r| { - this.state.set(State::Empty(PhantomData)); - r - }), - StateProj::Empty(_) => { - panic!("future must not be polled after it returned `Poll::Ready`") - } - } - } -} - -/* - * AndThenRC - AndThen service based on RefCell - */ - -struct AndThenRC(Rc>, PhantomData); - -impl AndThenRC { - fn new(a: A, b: B) -> Self - where - A: Service, - B: Service, - { - Self(Rc::new(RefCell::new((a, b))), PhantomData) - } -} - -impl Clone for AndThenRC { - fn clone(&self) -> Self { - Self(self.0.clone(), PhantomData) - } -} - -impl Service for AndThenRC -where - A: Service, - B: Service, -{ - type Response = B::Response; - type Error = A::Error; - type Future = AndThenServiceResponseRC; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Req) -> Self::Future { - let fut = self.0.borrow_mut().0.call(req); - AndThenServiceResponseRC { - state: StateRC::A(fut, Some(self.0.clone())), - } - } -} - -#[pin_project::pin_project] -pub(crate) struct AndThenServiceResponseRC -where - A: Service, - B: Service, -{ - #[pin] - state: StateRC, -} - -#[pin_project::pin_project(project = StateRCProj)] -enum StateRC -where - A: Service, - B: Service, -{ - A(#[pin] A::Future, Option>>), - B(#[pin] B::Future), - Empty(PhantomData), -} - -impl Future for AndThenServiceResponseRC -where - A: Service, - B: Service, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - match this.state.as_mut().project() { - StateRCProj::A(fut, b) => match fut.poll(cx)? { - Poll::Ready(res) => { - let b = b.take().unwrap(); - this.state.set(StateRC::Empty(PhantomData)); // drop fut A - let fut = b.borrow_mut().1.call(res); - this.state.set(StateRC::B(fut)); - self.poll(cx) - } - Poll::Pending => Poll::Pending, - }, - StateRCProj::B(fut) => fut.poll(cx).map(|r| { - this.state.set(StateRC::Empty(PhantomData)); - r - }), - StateRCProj::Empty(_) => { - panic!("future must not be polled after it returned `Poll::Ready`") - } - } - } -} - -/* - * AndThenRCFuture - AndThen service based on RefCell - * and standard futures::future::and_then combinator in a Box - */ - -struct AndThenRCFuture(Rc>, PhantomData); - -impl AndThenRCFuture { - fn new(a: A, b: B) -> Self - where - A: Service, - B: Service, - { - Self(Rc::new(RefCell::new((a, b))), PhantomData) - } -} - -impl Clone for AndThenRCFuture { - fn clone(&self) -> Self { - Self(self.0.clone(), PhantomData) - } -} - -impl Service for AndThenRCFuture -where - A: Service + 'static, - A::Future: 'static, - B: Service + 'static, - B::Future: 'static, -{ - type Response = B::Response; - type Error = A::Error; - type Future = BoxFuture; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Req) -> Self::Future { - let fut = self.0.borrow_mut().0.call(req); - let core = self.0.clone(); - let fut2 = move |res| (*core).borrow_mut().1.call(res); - Box::pin(fut.and_then(fut2)) - } -} - -/// Criterion Benchmark for async Service -/// Should be used from within criterion group: -/// ```rust,ignore -/// let mut criterion: ::criterion::Criterion<_> = -/// ::criterion::Criterion::default().configure_from_args(); -/// bench_async_service(&mut criterion, ok_service(), "async_service_direct"); -/// ``` -/// -/// Usable for benching Service wrappers: -/// Using minimum service code implementation we first measure -/// time to run minimum service, then measure time with wrapper. -/// -/// Sample output -/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] -pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) -where - S: Service<(), Response = usize, Error = ()> + Clone + 'static, -{ - let mut rt = actix_rt::System::new("test"); - - // start benchmark loops - c.bench_function(name, move |b| { - b.iter_custom(|iters| { - let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect(); - // exclude request generation, it appears it takes significant time vs call (3us vs 1us) - let start = std::time::Instant::now(); - // benchmark body - rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await }); - // check that at least first request succeeded - start.elapsed() - }) - }); -} - -pub fn service_benches() { - let mut criterion: ::criterion::Criterion<_> = - ::criterion::Criterion::default().configure_from_args(); - bench_async_service( - &mut criterion, - AndThenUC::new(svc1.into_service(), svc2.into_service()), - "AndThen with UnsafeCell", - ); - bench_async_service( - &mut criterion, - AndThenRC::new(svc1.into_service(), svc2.into_service()), - "AndThen with RefCell", - ); - bench_async_service( - &mut criterion, - AndThenUC::new(svc1.into_service(), svc2.into_service()), - "AndThen with UnsafeCell", - ); - bench_async_service( - &mut criterion, - AndThenRC::new(svc1.into_service(), svc2.into_service()), - "AndThen with RefCell", - ); - bench_async_service( - &mut criterion, - AndThenRCFuture::new(svc1.into_service(), svc2.into_service()), - "AndThen with RefCell via future::and_then", - ); -} - -criterion_main!(service_benches); diff --git a/actix-service/benches/unsafecell_vs_refcell.rs b/actix-service/benches/unsafecell_vs_refcell.rs deleted file mode 100644 index cdf91233..00000000 --- a/actix-service/benches/unsafecell_vs_refcell.rs +++ /dev/null @@ -1,110 +0,0 @@ -use actix_service::Service; -use criterion::{criterion_main, Criterion}; -use futures_util::future::join_all; -use futures_util::future::{ok, Ready}; -use std::cell::{RefCell, UnsafeCell}; -use std::rc::Rc; -use std::task::{Context, Poll}; - -struct SrvUC(Rc>); - -impl Default for SrvUC { - fn default() -> Self { - Self(Rc::new(UnsafeCell::new(0))) - } -} - -impl Clone for SrvUC { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Service<()> for SrvUC { - type Response = usize; - type Error = (); - type Future = Ready>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: ()) -> Self::Future { - unsafe { *(*self.0).get() = *(*self.0).get() + 1 }; - ok(unsafe { *self.0.get() }) - } -} - -struct SrvRC(Rc>); - -impl Default for SrvRC { - fn default() -> Self { - Self(Rc::new(RefCell::new(0))) - } -} - -impl Clone for SrvRC { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Service<()> for SrvRC { - type Response = usize; - type Error = (); - type Future = Ready>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: ()) -> Self::Future { - let prev = *self.0.borrow(); - *(*self.0).borrow_mut() = prev + 1; - ok(*self.0.borrow()) - } -} - -/// Criterion Benchmark for async Service -/// Should be used from within criterion group: -/// ```rust,ignore -/// let mut criterion: ::criterion::Criterion<_> = -/// ::criterion::Criterion::default().configure_from_args(); -/// bench_async_service(&mut criterion, ok_service(), "async_service_direct"); -/// ``` -/// -/// Usable for benching Service wrappers: -/// Using minimum service code implementation we first measure -/// time to run minimum service, then measure time with wrapper. -/// -/// Sample output -/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] -pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) -where - S: Service<(), Response = usize, Error = ()> + Clone + 'static, -{ - let mut rt = actix_rt::System::new("test"); - - // start benchmark loops - c.bench_function(name, move |b| { - b.iter_custom(|iters| { - let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect(); - // exclude request generation, it appears it takes significant time vs call (3us vs 1us) - let start = std::time::Instant::now(); - // benchmark body - rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await }); - // check that at least first request succeeded - start.elapsed() - }) - }); -} - -pub fn service_benches() { - let mut criterion: ::criterion::Criterion<_> = - ::criterion::Criterion::default().configure_from_args(); - bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell"); - bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell"); - bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell"); - bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell"); -} -criterion_main!(service_benches); diff --git a/actix-service/src/and_then.rs b/actix-service/src/and_then.rs index 04caf79d..17d62e8f 100644 --- a/actix-service/src/and_then.rs +++ b/actix-service/src/and_then.rs @@ -1,8 +1,13 @@ -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{cell::RefCell, marker::PhantomData}; +use alloc::rc::Rc; +use core::{ + cell::RefCell, + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use super::{Service, ServiceFactory}; @@ -50,30 +55,43 @@ where fn call(&mut self, req: Req) -> Self::Future { AndThenServiceResponse { - state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())), + state: State::A { + fut: self.0.borrow_mut().0.call(req), + b: Some(self.0.clone()), + }, } } } -#[pin_project::pin_project] -pub(crate) struct AndThenServiceResponse -where - A: Service, - B: Service, -{ - #[pin] - state: State, +pin_project! { + pub(crate) struct AndThenServiceResponse + where + A: Service, + B: Service, + { + #[pin] + state: State, + } } -#[pin_project::pin_project(project = StateProj)] -enum State -where - A: Service, - B: Service, -{ - A(#[pin] A::Future, Option>>), - B(#[pin] B::Future), - Empty, +pin_project! { + #[project = StateProj] + enum State + where + A: Service, + B: Service, + { + A { + #[pin] + fut: A::Future, + b: Option>>, + }, + B { + #[pin] + fut: B::Future, + }, + Empty, + } } impl Future for AndThenServiceResponse @@ -87,17 +105,17 @@ where let mut this = self.as_mut().project(); match this.state.as_mut().project() { - StateProj::A(fut, b) => match fut.poll(cx)? { + StateProj::A { fut, b } => match fut.poll(cx)? { Poll::Ready(res) => { let b = b.take().unwrap(); this.state.set(State::Empty); // drop fut A let fut = b.borrow_mut().1.call(res); - this.state.set(State::B(fut)); + this.state.set(State::B { fut }); self.poll(cx) } Poll::Pending => Poll::Pending, }, - StateProj::B(fut) => fut.poll(cx).map(|r| { + StateProj::B { fut } => fut.poll(cx).map(|r| { this.state.set(State::Empty); r }), @@ -191,19 +209,20 @@ where } } -#[pin_project::pin_project] -pub(crate) struct AndThenServiceFactoryResponse -where - A: ServiceFactory, - B: ServiceFactory, -{ - #[pin] - fut_a: A::Future, - #[pin] - fut_b: B::Future, +pin_project! { + pub(crate) struct AndThenServiceFactoryResponse + where + A: ServiceFactory, + B: ServiceFactory, + { + #[pin] + fut_a: A::Future, + #[pin] + fut_b: B::Future, - a: Option, - b: Option, + a: Option, + b: Option, + } } impl AndThenServiceFactoryResponse @@ -254,9 +273,11 @@ where #[cfg(test)] mod tests { - use std::cell::Cell; - use std::rc::Rc; - use std::task::{Context, Poll}; + use alloc::rc::Rc; + use core::{ + cell::Cell, + task::{Context, Poll}, + }; use futures_util::future::{lazy, ok, ready, Ready}; diff --git a/actix-service/src/and_then_apply_fn.rs b/actix-service/src/and_then_apply_fn.rs deleted file mode 100644 index c7bd098c..00000000 --- a/actix-service/src/and_then_apply_fn.rs +++ /dev/null @@ -1,334 +0,0 @@ -use std::cell::RefCell; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use crate::{Service, ServiceFactory}; - -/// `Apply` service combinator -pub(crate) struct AndThenApplyFn -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - svc: Rc>, - _phantom: PhantomData<(Fut, Req, In, Res, Err)>, -} - -impl AndThenApplyFn -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - /// Create new `Apply` combinator - pub(crate) fn new(a: S1, b: S2, wrap_fn: F) -> Self { - Self { - svc: Rc::new(RefCell::new((a, b, wrap_fn))), - _phantom: PhantomData, - } - } -} - -impl Clone - for AndThenApplyFn -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - fn clone(&self) -> Self { - AndThenApplyFn { - svc: self.svc.clone(), - _phantom: PhantomData, - } - } -} - -impl Service - for AndThenApplyFn -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - type Response = Res; - type Error = Err; - type Future = AndThenApplyFnFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut inner = self.svc.borrow_mut(); - let not_ready = inner.0.poll_ready(cx)?.is_pending(); - if inner.1.poll_ready(cx)?.is_pending() || not_ready { - Poll::Pending - } else { - Poll::Ready(Ok(())) - } - } - - fn call(&mut self, req: Req) -> Self::Future { - let fut = self.svc.borrow_mut().0.call(req); - AndThenApplyFnFuture { - state: State::A(fut, Some(self.svc.clone())), - } - } -} - -#[pin_project::pin_project] -pub(crate) struct AndThenApplyFnFuture -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - #[pin] - state: State, -} - -#[pin_project::pin_project(project = StateProj)] -enum State -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - A(#[pin] S1::Future, Option>>), - B(#[pin] Fut), - Empty(PhantomData), -} - -impl Future - for AndThenApplyFnFuture -where - S1: Service, - S2: Service, - F: FnMut(S1::Response, &mut S2) -> Fut, - Fut: Future>, - Err: From + From, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - match this.state.as_mut().project() { - StateProj::A(fut, b) => match fut.poll(cx)? { - Poll::Ready(res) => { - let b = Option::take(b).unwrap(); - this.state.set(State::Empty(PhantomData)); - let (_, b, f) = &mut *b.borrow_mut(); - let fut = f(res, b); - this.state.set(State::B(fut)); - self.poll(cx) - } - Poll::Pending => Poll::Pending, - }, - StateProj::B(fut) => fut.poll(cx).map(|r| { - this.state.set(State::Empty(PhantomData)); - r - }), - StateProj::Empty(_) => { - panic!("future must not be polled after it returned `Poll::Ready`") - } - } - } -} - -/// `AndThenApplyFn` service factory -pub(crate) struct AndThenApplyFnFactory { - srv: Rc<(SF1, SF2, F)>, - _phantom: PhantomData<(Fut, Req, In, Res, Err)>, -} - -impl - AndThenApplyFnFactory -where - SF1: ServiceFactory, - SF2: ServiceFactory, - F: FnMut(SF1::Response, &mut SF2::Service) -> Fut + Clone, - Fut: Future>, - Err: From + From, -{ - /// Create new `ApplyNewService` new service instance - pub(crate) fn new(a: SF1, b: SF2, wrap_fn: F) -> Self { - Self { - srv: Rc::new((a, b, wrap_fn)), - _phantom: PhantomData, - } - } -} - -impl Clone - for AndThenApplyFnFactory -{ - fn clone(&self) -> Self { - Self { - srv: self.srv.clone(), - _phantom: PhantomData, - } - } -} - -impl ServiceFactory - for AndThenApplyFnFactory -where - SF1: ServiceFactory, - SF1::Config: Clone, - SF2: ServiceFactory, - F: FnMut(SF1::Response, &mut SF2::Service) -> Fut + Clone, - Fut: Future>, - Err: From + From, -{ - type Response = Res; - type Error = Err; - type Service = AndThenApplyFn; - type Config = SF1::Config; - type InitError = SF1::InitError; - type Future = AndThenApplyFnFactoryResponse; - - fn new_service(&self, cfg: SF1::Config) -> Self::Future { - let srv = &*self.srv; - AndThenApplyFnFactoryResponse { - s1: None, - s2: None, - wrap_fn: srv.2.clone(), - fut_s1: srv.0.new_service(cfg.clone()), - fut_s2: srv.1.new_service(cfg), - _phantom: PhantomData, - } - } -} - -#[pin_project::pin_project] -pub(crate) struct AndThenApplyFnFactoryResponse -where - SF1: ServiceFactory, - SF2: ServiceFactory, - F: FnMut(SF1::Response, &mut SF2::Service) -> Fut + Clone, - Fut: Future>, - Err: From, - Err: From, -{ - #[pin] - fut_s1: SF1::Future, - #[pin] - fut_s2: SF2::Future, - wrap_fn: F, - s1: Option, - s2: Option, - _phantom: PhantomData, -} - -impl Future - for AndThenApplyFnFactoryResponse -where - SF1: ServiceFactory, - SF2: ServiceFactory, - F: FnMut(SF1::Response, &mut SF2::Service) -> Fut + Clone, - Fut: Future>, - Err: From + From, -{ - type Output = Result< - AndThenApplyFn, - SF1::InitError, - >; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - if this.s1.is_none() { - if let Poll::Ready(service) = this.fut_s1.poll(cx)? { - *this.s1 = Some(service); - } - } - - if this.s2.is_none() { - if let Poll::Ready(service) = this.fut_s2.poll(cx)? { - *this.s2 = Some(service); - } - } - - if this.s1.is_some() && this.s2.is_some() { - Poll::Ready(Ok(AndThenApplyFn { - svc: Rc::new(RefCell::new(( - Option::take(this.s1).unwrap(), - Option::take(this.s2).unwrap(), - this.wrap_fn.clone(), - ))), - _phantom: PhantomData, - })) - } else { - Poll::Pending - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use futures_util::future::{lazy, ok, Ready, TryFutureExt}; - - use crate::{fn_service, pipeline, pipeline_factory, Service, ServiceFactory}; - - #[derive(Clone)] - struct Srv; - - impl Service for Srv { - type Response = (); - type Error = (); - type Future = Ready>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: u8) -> Self::Future { - let _ = req; - ok(()) - } - } - - #[actix_rt::test] - async fn test_service() { - let mut srv = pipeline(ok).and_then_apply_fn(Srv, |req: &'static str, s| { - s.call(1).map_ok(move |res| (req, res)) - }); - let res = lazy(|cx| srv.poll_ready(cx)).await; - assert!(res.is_ready()); - - let res = srv.call("srv").await; - assert!(res.is_ok()); - assert_eq!(res.unwrap(), ("srv", ())); - } - - #[actix_rt::test] - async fn test_service_factory() { - let new_srv = pipeline_factory(|| ok::<_, ()>(fn_service(ok))).and_then_apply_fn( - || ok(Srv), - |req: &'static str, s| s.call(1).map_ok(move |res| (req, res)), - ); - let mut srv = new_srv.new_service(()).await.unwrap(); - let res = lazy(|cx| srv.poll_ready(cx)).await; - assert!(res.is_ready()); - - let res = srv.call("srv").await; - assert!(res.is_ok()); - assert_eq!(res.unwrap(), ("srv", ())); - } -} diff --git a/actix-service/src/apply.rs b/actix-service/src/apply.rs index 27a09684..8db6018f 100644 --- a/actix-service/src/apply.rs +++ b/actix-service/src/apply.rs @@ -1,11 +1,12 @@ -use std::{ +use core::{ future::Future, marker::PhantomData, pin::Pin, task::{Context, Poll}, }; -use futures_util::ready; +use futures_core::ready; +use pin_project_lite::pin_project; use super::{IntoService, IntoServiceFactory, Service, ServiceFactory}; @@ -94,9 +95,7 @@ where type Error = Err; type Future = Fut; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - Poll::Ready(ready!(self.service.poll_ready(cx))) - } + crate::forward_ready!(service); fn call(&mut self, req: Req) -> Self::Future { (self.wrap_fn)(req, &mut self.service) @@ -162,17 +161,18 @@ where } } -#[pin_project::pin_project] -pub struct ApplyServiceFactoryResponse -where - SF: ServiceFactory, - F: FnMut(Req, &mut SF::Service) -> Fut, - Fut: Future>, -{ - #[pin] - fut: SF::Future, - wrap_fn: Option, - _phantom: PhantomData<(Req, Res)>, +pin_project! { + pub struct ApplyServiceFactoryResponse + where + SF: ServiceFactory, + F: FnMut(Req, &mut SF::Service) -> Fut, + Fut: Future>, + { + #[pin] + fut: SF::Future, + wrap_fn: Option, + _phantom: PhantomData<(Req, Res)>, + } } impl ApplyServiceFactoryResponse @@ -203,13 +203,13 @@ where let this = self.project(); let svc = ready!(this.fut.poll(cx))?; - Poll::Ready(Ok(Apply::new(svc, Option::take(this.wrap_fn).unwrap()))) + Poll::Ready(Ok(Apply::new(svc, this.wrap_fn.take().unwrap()))) } } #[cfg(test)] mod tests { - use std::task::{Context, Poll}; + use core::task::Poll; use futures_util::future::{lazy, ok, Ready}; @@ -224,9 +224,7 @@ mod tests { type Error = (); type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + crate::always_ready!(); fn call(&mut self, _: ()) -> Self::Future { ok(()) diff --git a/actix-service/src/apply_cfg.rs b/actix-service/src/apply_cfg.rs index da24e87d..3e111231 100644 --- a/actix-service/src/apply_cfg.rs +++ b/actix-service/src/apply_cfg.rs @@ -1,9 +1,13 @@ -use std::cell::RefCell; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; +use alloc::rc::Rc; +use core::{ + cell::RefCell, + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use crate::{Service, ServiceFactory}; @@ -156,37 +160,42 @@ where ApplyConfigServiceFactoryResponse { cfg: Some(cfg), store: self.srv.clone(), - state: State::A(self.srv.borrow().0.new_service(())), + state: State::A { + fut: self.srv.borrow().0.new_service(()), + }, } } } -#[pin_project::pin_project] -struct ApplyConfigServiceFactoryResponse -where - SF: ServiceFactory, - SF::InitError: From, - F: FnMut(Cfg, &mut SF::Service) -> Fut, - Fut: Future>, - S: Service, -{ - cfg: Option, - store: Rc>, - #[pin] - state: State, +pin_project! { + struct ApplyConfigServiceFactoryResponse + where + SF: ServiceFactory, + SF::InitError: From, + F: FnMut(Cfg, &mut SF::Service) -> Fut, + Fut: Future>, + S: Service, + { + cfg: Option, + store: Rc>, + #[pin] + state: State, + } } -#[pin_project::pin_project(project = StateProj)] -enum State -where - SF: ServiceFactory, - SF::InitError: From, - Fut: Future>, - S: Service, -{ - A(#[pin] SF::Future), - B(SF::Service), - C(#[pin] Fut), +pin_project! { + #[project = StateProj] + enum State + where + SF: ServiceFactory, + SF::InitError: From, + Fut: Future>, + S: Service, + { + A { #[pin] fut: SF::Future }, + B { svc: SF::Service }, + C { #[pin] fut: Fut }, + } } impl Future @@ -204,25 +213,25 @@ where let mut this = self.as_mut().project(); match this.state.as_mut().project() { - StateProj::A(fut) => match fut.poll(cx)? { + StateProj::A { fut } => match fut.poll(cx)? { Poll::Pending => Poll::Pending, - Poll::Ready(srv) => { - this.state.set(State::B(srv)); + Poll::Ready(svc) => { + this.state.set(State::B { svc }); self.poll(cx) } }, - StateProj::B(srv) => match srv.poll_ready(cx)? { + StateProj::B { svc } => match svc.poll_ready(cx)? { Poll::Ready(_) => { { let (_, f) = &mut *this.store.borrow_mut(); - let fut = f(this.cfg.take().unwrap(), srv); - this.state.set(State::C(fut)); + let fut = f(this.cfg.take().unwrap(), svc); + this.state.set(State::C { fut }); } self.poll(cx) } Poll::Pending => Poll::Pending, }, - StateProj::C(fut) => fut.poll(cx), + StateProj::C { fut } => fut.poll(cx), } } } diff --git a/actix-service/src/boxed.rs b/actix-service/src/boxed.rs index 35a10dac..203d575c 100644 --- a/actix-service/src/boxed.rs +++ b/actix-service/src/boxed.rs @@ -1,6 +1,10 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{future::Future, marker::PhantomData}; +use alloc::boxed::Box; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; use futures_util::future::FutureExt; @@ -28,7 +32,7 @@ where { BoxServiceFactory(Box::new(FactoryWrapper { factory, - _t: std::marker::PhantomData, + _t: PhantomData, })) } @@ -75,12 +79,9 @@ where } } -struct FactoryWrapper -where - SF: ServiceFactory, -{ +struct FactoryWrapper { factory: SF, - _t: PhantomData<(C, Req)>, + _t: PhantomData<(Req, Cfg)>, } impl ServiceFactory for FactoryWrapper diff --git a/actix-service/src/fn_service.rs b/actix-service/src/fn_service.rs index 7d15304d..59792564 100644 --- a/actix-service/src/fn_service.rs +++ b/actix-service/src/fn_service.rs @@ -1,6 +1,4 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::task::{Context, Poll}; +use core::{future::Future, marker::PhantomData, task::Poll}; use futures_util::future::{ok, Ready}; @@ -143,9 +141,7 @@ where type Error = Err; type Future = Fut; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + crate::always_ready!(); fn call(&mut self, req: Req) -> Self::Future { (self.f)(req) @@ -200,9 +196,7 @@ where type Error = Err; type Future = Fut; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + crate::always_ready!(); fn call(&mut self, req: Req) -> Self::Future { (self.f)(req) @@ -361,7 +355,7 @@ where #[cfg(test)] mod tests { - use std::task::Poll; + use core::task::Poll; use futures_util::future::{lazy, ok}; diff --git a/actix-service/src/lib.rs b/actix-service/src/lib.rs index 2dfa0dd7..d66d5221 100644 --- a/actix-service/src/lib.rs +++ b/actix-service/src/lib.rs @@ -1,18 +1,21 @@ //! See [`Service`] docs for information on this crate's foundational trait. +#![no_std] #![deny(rust_2018_idioms, nonstandard_style)] #![allow(clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::cell::RefCell; -use std::future::Future; -use std::rc::Rc; -use std::sync::Arc; -use std::task::{self, Context, Poll}; +extern crate alloc; + +use alloc::{boxed::Box, rc::Rc, sync::Arc}; +use core::{ + cell::RefCell, + future::Future, + task::{self, Context, Poll}, +}; mod and_then; -mod and_then_apply_fn; mod apply; mod apply_cfg; pub mod boxed; @@ -359,3 +362,27 @@ pub mod dev { pub use crate::transform::ApplyTransform; pub use crate::transform_err::TransformMapInitErr; } + +#[macro_export] +macro_rules! always_ready { + () => { + fn poll_ready( + &mut self, + _: &mut ::core::task::Context<'_>, + ) -> ::core::task::Poll> { + Poll::Ready(Ok(())) + } + }; +} + +#[macro_export] +macro_rules! forward_ready { + ($field:ident) => { + fn poll_ready( + &mut self, + cx: &mut ::core::task::Context<'_>, + ) -> ::core::task::Poll> { + self.$field.poll_ready(cx) + } + }; +} diff --git a/actix-service/src/map.rs b/actix-service/src/map.rs index 04ef8c5f..a8afa25f 100644 --- a/actix-service/src/map.rs +++ b/actix-service/src/map.rs @@ -1,7 +1,11 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use super::{Service, ServiceFactory}; @@ -52,24 +56,23 @@ where type Error = A::Error; type Future = MapFuture; - fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(ctx) - } + crate::forward_ready!(service); fn call(&mut self, req: Req) -> Self::Future { MapFuture::new(self.service.call(req), self.f.clone()) } } -#[pin_project::pin_project] -pub struct MapFuture -where - A: Service, - F: FnMut(A::Response) -> Res, -{ - f: F, - #[pin] - fut: A::Future, +pin_project! { + pub struct MapFuture + where + A: Service, + F: FnMut(A::Response) -> Res, + { + f: F, + #[pin] + fut: A::Future, + } } impl MapFuture @@ -154,15 +157,16 @@ where } } -#[pin_project::pin_project] -pub struct MapServiceFuture -where - A: ServiceFactory, - F: FnMut(A::Response) -> Res, -{ - #[pin] - fut: A::Future, - f: Option, +pin_project! { + pub struct MapServiceFuture + where + A: ServiceFactory, + F: FnMut(A::Response) -> Res, + { + #[pin] + fut: A::Future, + f: Option, + } } impl MapServiceFuture @@ -207,9 +211,7 @@ mod tests { type Error = (); type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + crate::always_ready!(); fn call(&mut self, _: ()) -> Self::Future { ok(()) diff --git a/actix-service/src/map_config.rs b/actix-service/src/map_config.rs index 82b1789b..d6d6f6b2 100644 --- a/actix-service/src/map_config.rs +++ b/actix-service/src/map_config.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use core::marker::PhantomData; use super::{IntoServiceFactory, ServiceFactory}; diff --git a/actix-service/src/map_err.rs b/actix-service/src/map_err.rs index ae7442cc..f0bf134b 100644 --- a/actix-service/src/map_err.rs +++ b/actix-service/src/map_err.rs @@ -1,7 +1,11 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use super::{Service, ServiceFactory}; @@ -62,15 +66,16 @@ where } } -#[pin_project::pin_project] -pub struct MapErrFuture -where - A: Service, - F: Fn(A::Error) -> E, -{ - f: F, - #[pin] - fut: A::Future, +pin_project! { + pub struct MapErrFuture + where + A: Service, + F: Fn(A::Error) -> E, + { + f: F, + #[pin] + fut: A::Future, + } } impl MapErrFuture @@ -157,15 +162,16 @@ where } } -#[pin_project::pin_project] -pub struct MapErrServiceFuture -where - A: ServiceFactory, - F: Fn(A::Error) -> E, -{ - #[pin] - fut: A::Future, - f: F, +pin_project! { + pub struct MapErrServiceFuture + where + A: ServiceFactory, + F: Fn(A::Error) -> E, + { + #[pin] + fut: A::Future, + f: F, + } } impl MapErrServiceFuture diff --git a/actix-service/src/map_init_err.rs b/actix-service/src/map_init_err.rs index 518daaf6..9fc383aa 100644 --- a/actix-service/src/map_init_err.rs +++ b/actix-service/src/map_init_err.rs @@ -1,7 +1,11 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use super::ServiceFactory; @@ -59,15 +63,16 @@ where } } -#[pin_project::pin_project] -pub struct MapInitErrFuture -where - A: ServiceFactory, - F: Fn(A::InitError) -> E, -{ - f: F, - #[pin] - fut: A::Future, +pin_project! { + pub struct MapInitErrFuture + where + A: ServiceFactory, + F: Fn(A::InitError) -> E, + { + f: F, + #[pin] + fut: A::Future, + } } impl MapInitErrFuture diff --git a/actix-service/src/pipeline.rs b/actix-service/src/pipeline.rs index cba7ce78..580d7b4c 100644 --- a/actix-service/src/pipeline.rs +++ b/actix-service/src/pipeline.rs @@ -1,8 +1,9 @@ -use std::task::{Context, Poll}; -use std::{future::Future, marker::PhantomData}; +use core::{ + marker::PhantomData, + task::{Context, Poll}, +}; use crate::and_then::{AndThenService, AndThenServiceFactory}; -use crate::and_then_apply_fn::{AndThenApplyFn, AndThenApplyFnFactory}; use crate::map::{Map, MapServiceFactory}; use crate::map_err::{MapErr, MapErrServiceFactory}; use crate::map_init_err::MapInitErr; @@ -67,28 +68,6 @@ where } } - /// Apply function to specified service and use it as a next service in chain. - /// - /// Short version of `pipeline_factory(...).and_then(apply_fn(...))` - pub fn and_then_apply_fn( - self, - service: I, - wrap_fn: F, - ) -> Pipeline + Clone, Req> - where - Self: Sized, - I: IntoService, - S1: Service, - F: FnMut(S::Response, &mut S1) -> Fut, - Fut: Future>, - Err: From + From, - { - Pipeline { - service: AndThenApplyFn::new(self.service, service.into_service(), wrap_fn), - _phantom: PhantomData, - } - } - /// Chain on a computation for when a call to the service finished, /// passing the result of the call to the next service `U`. /// @@ -219,39 +198,6 @@ where } } - /// Apply function to specified service and use it as a next service in chain. - /// - /// Short version of `pipeline_factory(...).and_then(apply_fn_factory(...))` - pub fn and_then_apply_fn( - self, - factory: I, - wrap_fn: F, - ) -> PipelineFactory< - impl ServiceFactory< - Req, - Response = Res, - Error = Err, - Config = SF::Config, - InitError = SF::InitError, - Service = impl Service + Clone, - > + Clone, - Req, - > - where - Self: Sized, - SF::Config: Clone, - I: IntoServiceFactory, - SF1: ServiceFactory, - F: FnMut(SF::Response, &mut SF1::Service) -> Fut + Clone, - Fut: Future>, - Err: From + From, - { - PipelineFactory { - factory: AndThenApplyFnFactory::new(self.factory, factory.into_factory(), wrap_fn), - _phantom: PhantomData, - } - } - /// Create `NewService` to chain on a computation for when a call to the /// service finished, passing the result of the call to the next /// service `U`. diff --git a/actix-service/src/then.rs b/actix-service/src/then.rs index 021e5484..179713ac 100644 --- a/actix-service/src/then.rs +++ b/actix-service/src/then.rs @@ -1,8 +1,13 @@ -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{cell::RefCell, marker::PhantomData}; +use alloc::rc::Rc; +use core::{ + cell::RefCell, + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use super::{Service, ServiceFactory}; @@ -50,30 +55,36 @@ where fn call(&mut self, req: Req) -> Self::Future { ThenServiceResponse { - state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())), + state: State::A { + fut: self.0.borrow_mut().0.call(req), + b: Some(self.0.clone()), + }, } } } -#[pin_project::pin_project] -pub(crate) struct ThenServiceResponse -where - A: Service, - B: Service>, -{ - #[pin] - state: State, +pin_project! { + pub(crate) struct ThenServiceResponse + where + A: Service, + B: Service>, + { + #[pin] + state: State, + } } -#[pin_project::pin_project(project = StateProj)] -enum State -where - A: Service, - B: Service>, -{ - A(#[pin] A::Future, Option>>), - B(#[pin] B::Future), - Empty, +pin_project! { + #[project = StateProj] + enum State + where + A: Service, + B: Service>, + { + A { #[pin] fut: A::Future, b: Option>> }, + B { #[pin] fut: B::Future }, + Empty, + } } impl Future for ThenServiceResponse @@ -87,17 +98,17 @@ where let mut this = self.as_mut().project(); match this.state.as_mut().project() { - StateProj::A(fut, b) => match fut.poll(cx) { + StateProj::A { fut, b } => match fut.poll(cx) { Poll::Ready(res) => { let b = b.take().unwrap(); this.state.set(State::Empty); // drop fut A let fut = b.borrow_mut().1.call(res); - this.state.set(State::B(fut)); + this.state.set(State::B { fut }); self.poll(cx) } Poll::Pending => Poll::Pending, }, - StateProj::B(fut) => fut.poll(cx).map(|r| { + StateProj::B { fut } => fut.poll(cx).map(|r| { this.state.set(State::Empty); r }), @@ -159,23 +170,24 @@ impl Clone for ThenServiceFactory { } } -#[pin_project::pin_project] -pub(crate) struct ThenServiceFactoryResponse -where - A: ServiceFactory, - B: ServiceFactory< - Result, - Config = A::Config, - Error = A::Error, - InitError = A::InitError, - >, -{ - #[pin] - fut_b: B::Future, - #[pin] - fut_a: A::Future, - a: Option, - b: Option, +pin_project! { + pub(crate) struct ThenServiceFactoryResponse + where + A: ServiceFactory, + B: ServiceFactory< + Result, + Config = A::Config, + Error = A::Error, + InitError = A::InitError, + >, + { + #[pin] + fut_b: B::Future, + #[pin] + fut_a: A::Future, + a: Option, + b: Option, + } } impl ThenServiceFactoryResponse @@ -236,9 +248,11 @@ where #[cfg(test)] mod tests { - use std::cell::Cell; - use std::rc::Rc; - use std::task::{Context, Poll}; + use alloc::rc::Rc; + use core::{ + cell::Cell, + task::{Context, Poll}, + }; use futures_util::future::{err, lazy, ok, ready, Ready}; diff --git a/actix-service/src/transform.rs b/actix-service/src/transform.rs index d4d49417..76e4547a 100644 --- a/actix-service/src/transform.rs +++ b/actix-service/src/transform.rs @@ -1,8 +1,12 @@ -use std::pin::Pin; -use std::rc::Rc; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::{future::Future, marker::PhantomData}; +use alloc::{rc::Rc, sync::Arc}; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use crate::transform_err::TransformMapInitErr; use crate::{IntoServiceFactory, Service, ServiceFactory}; @@ -185,30 +189,35 @@ where fn new_service(&self, cfg: S::Config) -> Self::Future { ApplyTransformFuture { store: self.0.clone(), - state: ApplyTransformFutureState::A(self.0.as_ref().1.new_service(cfg)), + state: ApplyTransformFutureState::A { + fut: self.0.as_ref().1.new_service(cfg), + }, } } } -#[pin_project::pin_project] -pub struct ApplyTransformFuture -where - S: ServiceFactory, - T: Transform, -{ - store: Rc<(T, S)>, - #[pin] - state: ApplyTransformFutureState, +pin_project! { + pub struct ApplyTransformFuture + where + S: ServiceFactory, + T: Transform, + { + store: Rc<(T, S)>, + #[pin] + state: ApplyTransformFutureState, + } } -#[pin_project::pin_project(project = ApplyTransformFutureStateProj)] -pub enum ApplyTransformFutureState -where - S: ServiceFactory, - T: Transform, -{ - A(#[pin] S::Future), - B(#[pin] T::Future), +pin_project! { + #[project = ApplyTransformFutureStateProj] + pub enum ApplyTransformFutureState + where + S: ServiceFactory, + T: Transform, + { + A { #[pin] fut: S::Future }, + B { #[pin] fut: T::Future }, + } } impl Future for ApplyTransformFuture @@ -222,15 +231,15 @@ where let mut this = self.as_mut().project(); match this.state.as_mut().project() { - ApplyTransformFutureStateProj::A(fut) => match fut.poll(cx)? { + ApplyTransformFutureStateProj::A { fut } => match fut.poll(cx)? { Poll::Ready(srv) => { let fut = this.store.0.new_transform(srv); - this.state.set(ApplyTransformFutureState::B(fut)); + this.state.set(ApplyTransformFutureState::B { fut }); self.poll(cx) } Poll::Pending => Poll::Pending, }, - ApplyTransformFutureStateProj::B(fut) => fut.poll(cx), + ApplyTransformFutureStateProj::B { fut } => fut.poll(cx), } } } diff --git a/actix-service/src/transform_err.rs b/actix-service/src/transform_err.rs index 1d1b9576..cbf5fe3b 100644 --- a/actix-service/src/transform_err.rs +++ b/actix-service/src/transform_err.rs @@ -1,7 +1,11 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; +use core::{ + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project_lite::pin_project; use super::Transform; @@ -63,15 +67,16 @@ where } } -#[pin_project::pin_project] -pub struct TransformMapInitErrFuture -where +pin_project! { + pub struct TransformMapInitErrFuture + where T: Transform, F: Fn(T::InitError) -> E, -{ - #[pin] - fut: T::Future, - f: F, + { + #[pin] + fut: T::Future, + f: F, + } } impl Future for TransformMapInitErrFuture diff --git a/actix-tracing/src/lib.rs b/actix-tracing/src/lib.rs index 36aa21f2..6d37d9b3 100644 --- a/actix-tracing/src/lib.rs +++ b/actix-tracing/src/lib.rs @@ -4,8 +4,7 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::marker::PhantomData; -use std::task::{Context, Poll}; +use core::marker::PhantomData; use actix_service::{ apply, dev::ApplyTransform, IntoServiceFactory, Service, ServiceFactory, Transform, @@ -36,9 +35,7 @@ where type Error = S::Error; type Future = Either>; - fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(ctx) - } + actix_service::forward_ready!(inner); fn call(&mut self, req: Req) -> Self::Future { let span = (self.make_span)(&req); diff --git a/actix-utils/src/timeout.rs b/actix-utils/src/timeout.rs index 17647206..a27e9ffb 100644 --- a/actix-utils/src/timeout.rs +++ b/actix-utils/src/timeout.rs @@ -201,7 +201,7 @@ where #[cfg(test)] mod tests { - use std::task::{Context, Poll}; + use std::task::Poll; use std::time::Duration; use super::*; @@ -215,9 +215,7 @@ mod tests { type Error = (); type Future = LocalBoxFuture<'static, Result<(), ()>>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, _: ()) -> Self::Future { actix_rt::time::delay_for(self.0) From ba44ea7d0bafaf5fccb9a34003d503e1910943ee Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 27 Dec 2020 18:24:57 +0000 Subject: [PATCH 2/2] remove futures-util from service deps (#235) --- actix-service/CHANGES.md | 2 + actix-service/Cargo.toml | 5 +-- actix-service/src/and_then.rs | 6 ++- actix-service/src/apply.rs | 4 +- actix-service/src/boxed.rs | 12 +++--- actix-service/src/ext.rs | 70 +++++++++++++++++++++++++++++++++ actix-service/src/fn_service.rs | 8 ++-- actix-service/src/lib.rs | 67 +++---------------------------- actix-service/src/map.rs | 6 ++- actix-service/src/map_err.rs | 7 +++- actix-service/src/ready.rs | 54 +++++++++++++++++++++++++ actix-service/src/then.rs | 4 +- 12 files changed, 159 insertions(+), 86 deletions(-) create mode 100644 actix-service/src/ext.rs create mode 100644 actix-service/src/ready.rs diff --git a/actix-service/CHANGES.md b/actix-service/CHANGES.md index 3b9a9cc0..82c5adb3 100644 --- a/actix-service/CHANGES.md +++ b/actix-service/CHANGES.md @@ -8,9 +8,11 @@ * Migrate pin projections to `pin-project-lite`. [#233] * Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the `.and_then(apply_fn(...))` construction. [#233] +* Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235] [#232]: https://github.com/actix/actix-net/pull/232 [#233]: https://github.com/actix/actix-net/pull/233 +[#235]: https://github.com/actix/actix-net/pull/235 ## 1.0.6 - 2020-08-09 diff --git a/actix-service/Cargo.toml b/actix-service/Cargo.toml index 60818968..c08bb169 100644 --- a/actix-service/Cargo.toml +++ b/actix-service/Cargo.toml @@ -17,10 +17,9 @@ name = "actix_service" path = "src/lib.rs" [dependencies] -pin-project-lite = "0.2" -futures-util = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false } +pin-project-lite = "0.2" [dev-dependencies] actix-rt = "1.0.0" -criterion = "0.3" +futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-service/src/and_then.rs b/actix-service/src/and_then.rs index 17d62e8f..fd24cb56 100644 --- a/actix-service/src/and_then.rs +++ b/actix-service/src/and_then.rs @@ -279,9 +279,11 @@ mod tests { task::{Context, Poll}, }; - use futures_util::future::{lazy, ok, ready, Ready}; + use futures_util::future::lazy; - use crate::{fn_factory, pipeline, pipeline_factory, Service, ServiceFactory}; + use crate::{ + fn_factory, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory, + }; struct Srv1(Rc>); diff --git a/actix-service/src/apply.rs b/actix-service/src/apply.rs index 8db6018f..9b0c4025 100644 --- a/actix-service/src/apply.rs +++ b/actix-service/src/apply.rs @@ -211,10 +211,10 @@ where mod tests { use core::task::Poll; - use futures_util::future::{lazy, ok, Ready}; + use futures_util::future::lazy; use super::*; - use crate::{pipeline, pipeline_factory, Service, ServiceFactory}; + use crate::{ok, pipeline, pipeline_factory, Ready, Service, ServiceFactory}; #[derive(Clone)] struct Srv; diff --git a/actix-service/src/boxed.rs b/actix-service/src/boxed.rs index 203d575c..5c4557df 100644 --- a/actix-service/src/boxed.rs +++ b/actix-service/src/boxed.rs @@ -6,8 +6,6 @@ use core::{ task::{Context, Poll}, }; -use futures_util::future::FutureExt; - use crate::{Service, ServiceFactory}; pub type BoxFuture = Pin>>; @@ -103,11 +101,11 @@ where type Future = BoxFuture>; fn new_service(&self, cfg: Cfg) -> Self::Future { - Box::pin( - self.factory - .new_service(cfg) - .map(|res| res.map(ServiceWrapper::boxed)), - ) + let fut = self.factory.new_service(cfg); + Box::pin(async { + let res = fut.await; + res.map(ServiceWrapper::boxed) + }) } } diff --git a/actix-service/src/ext.rs b/actix-service/src/ext.rs new file mode 100644 index 00000000..e778d11e --- /dev/null +++ b/actix-service/src/ext.rs @@ -0,0 +1,70 @@ +use crate::{dev, Service, ServiceFactory}; + +pub trait ServiceExt: Service { + /// Map this service's output to a different type, returning a new service + /// of the resulting type. + /// + /// This function is similar to the `Option::map` or `Iterator::map` where + /// it will change the type of the underlying service. + /// + /// Note that this function consumes the receiving service and returns a + /// wrapped version of it, similar to the existing `map` methods in the + /// standard library. + fn map(self, f: F) -> dev::Map + where + Self: Sized, + F: FnMut(Self::Response) -> R, + { + dev::Map::new(self, f) + } + + /// Map this service's error to a different error, returning a new service. + /// + /// This function is similar to the `Result::map_err` where it will change + /// the error type of the underlying service. For example, this can be useful to + /// ensure that services have the same error type. + /// + /// Note that this function consumes the receiving service and returns a + /// wrapped version of it. + fn map_err(self, f: F) -> dev::MapErr + where + Self: Sized, + F: Fn(Self::Error) -> E, + { + dev::MapErr::new(self, f) + } +} + +impl ServiceExt for S where S: Service {} + +pub trait ServiceFactoryExt: ServiceFactory { + /// Map this service's output to a different type, returning a new service + /// of the resulting type. + fn map(self, f: F) -> crate::map::MapServiceFactory + where + Self: Sized, + F: FnMut(Self::Response) -> R + Clone, + { + crate::map::MapServiceFactory::new(self, f) + } + + /// Map this service's error to a different error, returning a new service. + fn map_err(self, f: F) -> crate::map_err::MapErrServiceFactory + where + Self: Sized, + F: Fn(Self::Error) -> E + Clone, + { + crate::map_err::MapErrServiceFactory::new(self, f) + } + + /// Map this factory's init error to a different error, returning a new service. + fn map_init_err(self, f: F) -> crate::map_init_err::MapInitErr + where + Self: Sized, + F: Fn(Self::InitError) -> E + Clone, + { + crate::map_init_err::MapInitErr::new(self, f) + } +} + +impl ServiceFactoryExt for S where S: ServiceFactory {} diff --git a/actix-service/src/fn_service.rs b/actix-service/src/fn_service.rs index 59792564..9f7d1eb7 100644 --- a/actix-service/src/fn_service.rs +++ b/actix-service/src/fn_service.rs @@ -1,8 +1,6 @@ use core::{future::Future, marker::PhantomData, task::Poll}; -use futures_util::future::{ok, Ready}; - -use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory}; +use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory}; /// Create `ServiceFactory` for function that can act as a `Service` pub fn fn_service( @@ -357,10 +355,10 @@ where mod tests { use core::task::Poll; - use futures_util::future::{lazy, ok}; + use futures_util::future::lazy; use super::*; - use crate::{Service, ServiceFactory}; + use crate::{ok, Service, ServiceFactory}; #[actix_rt::test] async fn test_fn_service() { diff --git a/actix-service/src/lib.rs b/actix-service/src/lib.rs index d66d5221..7bf979e5 100644 --- a/actix-service/src/lib.rs +++ b/actix-service/src/lib.rs @@ -19,23 +19,29 @@ mod and_then; mod apply; mod apply_cfg; pub mod boxed; +mod ext; mod fn_service; mod map; mod map_config; mod map_err; mod map_init_err; mod pipeline; +mod ready; mod then; mod transform; mod transform_err; pub use self::apply::{apply_fn, apply_fn_factory}; pub use self::apply_cfg::{apply_cfg, apply_cfg_factory}; +pub use self::ext::{ServiceExt, ServiceFactoryExt}; pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service}; pub use self::map_config::{map_config, unit_config}; pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory}; pub use self::transform::{apply, Transform}; +#[allow(unused_imports)] +use self::ready::{err, ok, ready, Ready}; + /// An asynchronous operation from `Request` to a `Response`. /// /// The `Service` trait models a request/response interaction, receiving requests and returning @@ -110,39 +116,6 @@ pub trait Service { /// Calling `call` without calling `poll_ready` is permitted. The /// implementation must be resilient to this fact. fn call(&mut self, req: Req) -> Self::Future; - - /// Map this service's output to a different type, returning a new service - /// of the resulting type. - /// - /// This function is similar to the `Option::map` or `Iterator::map` where - /// it will change the type of the underlying service. - /// - /// Note that this function consumes the receiving service and returns a - /// wrapped version of it, similar to the existing `map` methods in the - /// standard library. - fn map(self, f: F) -> crate::dev::Map - where - Self: Sized, - F: FnMut(Self::Response) -> R, - { - crate::dev::Map::new(self, f) - } - - /// Map this service's error to a different error, returning a new service. - /// - /// This function is similar to the `Result::map_err` where it will change - /// the error type of the underlying service. For example, this can be useful to - /// ensure that services have the same error type. - /// - /// Note that this function consumes the receiving service and returns a - /// wrapped version of it. - fn map_err(self, f: F) -> crate::dev::MapErr - where - Self: Sized, - F: Fn(Self::Error) -> E, - { - crate::dev::MapErr::new(self, f) - } } /// Factory for creating `Service`s. @@ -175,34 +148,6 @@ pub trait ServiceFactory { /// Create and return a new service asynchronously. fn new_service(&self, cfg: Self::Config) -> Self::Future; - - /// Map this service's output to a different type, returning a new service - /// of the resulting type. - fn map(self, f: F) -> crate::map::MapServiceFactory - where - Self: Sized, - F: FnMut(Self::Response) -> R + Clone, - { - crate::map::MapServiceFactory::new(self, f) - } - - /// Map this service's error to a different error, returning a new service. - fn map_err(self, f: F) -> crate::map_err::MapErrServiceFactory - where - Self: Sized, - F: Fn(Self::Error) -> E + Clone, - { - crate::map_err::MapErrServiceFactory::new(self, f) - } - - /// Map this factory's init error to a different error, returning a new service. - fn map_init_err(self, f: F) -> crate::map_init_err::MapInitErr - where - Self: Sized, - F: Fn(Self::InitError) -> E + Clone, - { - crate::map_init_err::MapInitErr::new(self, f) - } } impl<'a, S, Req> Service for &'a mut S diff --git a/actix-service/src/map.rs b/actix-service/src/map.rs index a8afa25f..0599a1d8 100644 --- a/actix-service/src/map.rs +++ b/actix-service/src/map.rs @@ -199,10 +199,12 @@ where #[cfg(test)] mod tests { - use futures_util::future::{lazy, ok, Ready}; + use futures_util::future::lazy; use super::*; - use crate::{IntoServiceFactory, Service, ServiceFactory}; + use crate::{ + ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt, + }; struct Srv; diff --git a/actix-service/src/map_err.rs b/actix-service/src/map_err.rs index f0bf134b..944056c2 100644 --- a/actix-service/src/map_err.rs +++ b/actix-service/src/map_err.rs @@ -203,10 +203,13 @@ where #[cfg(test)] mod tests { - use futures_util::future::{err, lazy, ok, Ready}; + use futures_util::future::lazy; use super::*; - use crate::{IntoServiceFactory, Service, ServiceFactory}; + use crate::{ + err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, + ServiceFactoryExt, + }; struct Srv; diff --git a/actix-service/src/ready.rs b/actix-service/src/ready.rs new file mode 100644 index 00000000..8b0c2ea7 --- /dev/null +++ b/actix-service/src/ready.rs @@ -0,0 +1,54 @@ +//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`. + +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +/// Future for the [`ready`](ready()) function. +#[derive(Debug, Clone)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Ready { + val: Option, +} + +impl Ready { + /// Unwraps the value from this immediately ready future. + #[inline] + pub fn into_inner(mut self) -> T { + self.val.take().unwrap() + } +} + +impl Unpin for Ready {} + +impl Future for Ready { + type Output = T; + + #[inline] + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + let val = self.val.take().expect("Ready can not be polled twice."); + Poll::Ready(val) + } +} + +/// Creates a future that is immediately ready with a value. +#[allow(dead_code)] +pub(crate) fn ready(val: T) -> Ready { + Ready { val: Some(val) } +} + +/// Create a future that is immediately ready with a success value. +#[allow(dead_code)] +pub(crate) fn ok(val: T) -> Ready> { + Ready { val: Some(Ok(val)) } +} + +/// Create a future that is immediately ready with an error value. +#[allow(dead_code)] +pub(crate) fn err(err: E) -> Ready> { + Ready { + val: Some(Err(err)), + } +} diff --git a/actix-service/src/then.rs b/actix-service/src/then.rs index 179713ac..060ca9c7 100644 --- a/actix-service/src/then.rs +++ b/actix-service/src/then.rs @@ -254,9 +254,9 @@ mod tests { task::{Context, Poll}, }; - use futures_util::future::{err, lazy, ok, ready, Ready}; + use futures_util::future::lazy; - use crate::{pipeline, pipeline_factory, Service, ServiceFactory}; + use crate::{err, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory}; #[derive(Clone)] struct Srv1(Rc>);