Skip to main content

ntex_service/
pipeline.rs

1use std::{fmt, future, pin::Pin, task::Context, task::Poll};
2
3use crate::pl_inner::PipelineApi;
4use crate::{IntoService, Service, util::BoxFuture};
5
6pub use crate::pl_factory::{PipelineFactory, PipelineStateFactory};
7pub use crate::pl_state::{PipelineState, PipelineStateBinding};
8
9/// Container for a service.
10///
11/// Provides a way to call the enclosed service and share its readiness state.
12pub struct Pipeline<Req, Res, Err> {
13    api: PipelineApi<Req, Res, Err>,
14}
15
16/// Bound container for a service.
17pub struct PipelineBinding<Req, Res, Err> {
18    idx: u32,
19    api: PipelineApi<Req, Res, Err>,
20}
21
22impl<Req, Res, Err> Pipeline<Req, Res, Err>
23where
24    Req: 'static,
25    Res: 'static,
26    Err: 'static,
27{
28    #[inline]
29    /// Construct new service pipeline instance with default state.
30    pub fn new<S, St>(f: impl IntoService<S, St, Req>) -> Self
31    where
32        S: Service<St, Req, Res = Res, Error = Err> + 'static,
33        St: Default + 'static,
34    {
35        Pipeline {
36            api: PipelineApi::new(f.into_service(), St::default()),
37        }
38    }
39
40    #[inline]
41    /// Construct new service pipeline instance with state.
42    pub fn with<S, St>(st: St, f: impl IntoService<S, St, Req>) -> Self
43    where
44        S: Service<St, Req, Res = Res, Error = Err> + 'static,
45        St: 'static,
46    {
47        Pipeline {
48            api: PipelineApi::new(f.into_service(), st),
49        }
50    }
51
52    #[inline]
53    /// Returns when the pipeline is ready to process requests.
54    pub async fn ready(&self) -> Result<(), Err> {
55        future::poll_fn(|cx| self.api.poll_ready(cx)).await
56    }
57
58    #[inline]
59    /// Wait for service readiness, then create a future
60    /// that resolves to the service call result.
61    pub async fn call(&self, req: Req) -> Result<Res, Err> {
62        let pl = self.bind();
63        pl.api.call(pl.idx, req, true).await
64    }
65
66    #[inline]
67    /// Wait for service readiness, then create a future
68    /// that resolves to the service result.
69    ///
70    /// This call can be completed from different async tasks.
71    pub fn call_static(&self, req: Req) -> PipelineCall<Req, Res, Err> {
72        PipelineCall::new(self.bind(), req, true)
73    }
74
75    #[inline]
76    /// Call the service and create a future that resolves to the service result.
77    ///
78    /// This call can be completed from different async tasks.
79    /// Note: this call does not check service readiness.
80    pub fn call_nowait(&self, req: Req) -> PipelineCall<Req, Res, Err> {
81        PipelineCall::new(self.bind(), req, false)
82    }
83
84    #[inline]
85    /// Returns `Ready` when the pipeline is ready to process requests.
86    pub fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Err>> {
87        self.api.poll_ready(cx)
88    }
89
90    #[inline]
91    /// Returns `Ready` when the service has been properly shut down.
92    pub fn poll_shutdown(&self, cx: &mut Context<'_>) -> Poll<()> {
93        self.api.poll_shutdown(cx)
94    }
95
96    #[inline]
97    /// Checks whether pipeline shutdown has been initiated.
98    pub fn is_shutdown(&self) -> bool {
99        self.api.is_shutdown()
100    }
101
102    #[inline]
103    /// Shuts down the enclosed service.
104    pub async fn shutdown(&self) {
105        future::poll_fn(|cx| self.api.poll_shutdown(cx)).await;
106    }
107
108    #[inline]
109    /// Returns the current pipeline binding.
110    ///
111    /// The binding can be used to check readiness and call the service.
112    pub fn bind(&self) -> PipelineBinding<Req, Res, Err> {
113        PipelineBinding::new(self)
114    }
115}
116
117impl<Req, Res, Err> Drop for Pipeline<Req, Res, Err> {
118    #[inline]
119    fn drop(&mut self) {
120        self.api.unreg(0);
121    }
122}
123
124impl<Req, Res, Err> PipelineBinding<Req, Res, Err>
125where
126    Req: 'static,
127    Res: 'static,
128    Err: 'static,
129{
130    fn new(pl: &Pipeline<Req, Res, Err>) -> Self {
131        Self {
132            idx: pl.api.reg(),
133            api: pl.api.clone(),
134        }
135    }
136
137    pub(crate) fn with(idx: u32, api: PipelineApi<Req, Res, Err>) -> Self {
138        Self { idx, api }
139    }
140
141    #[inline]
142    /// Returns when the pipeline is ready to process requests.
143    pub async fn ready(&self) -> Result<(), Err> {
144        self.api.ready(self.idx).await
145    }
146
147    #[inline]
148    /// Wait for service readiness, then create a future
149    /// that resolves to the service call result.
150    pub async fn call(&self, req: Req) -> Result<Res, Err> {
151        let pl = self.clone();
152        pl.api.call(pl.idx, req, true).await
153    }
154
155    #[inline]
156    /// Wait for service readiness, then create a future
157    /// that resolves to the service result.
158    ///
159    /// This call can be completed from different async tasks.
160    pub fn call_static(&self, req: Req) -> PipelineCall<Req, Res, Err> {
161        PipelineCall::new(self.clone(), req, true)
162    }
163
164    #[inline]
165    /// Call the service and create a future that resolves to the service result.
166    ///
167    /// This call can be completed from different async tasks.
168    /// Note: this call does not check service readiness.
169    pub fn call_nowait(&self, req: Req) -> PipelineCall<Req, Res, Err> {
170        PipelineCall::new(self.clone(), req, false)
171    }
172}
173
174impl<Req, Res, Err> Drop for PipelineBinding<Req, Res, Err> {
175    #[inline]
176    fn drop(&mut self) {
177        self.api.unreg(self.idx);
178    }
179}
180
181impl<Req, Res, Err> Clone for PipelineBinding<Req, Res, Err> {
182    fn clone(&self) -> Self {
183        Self {
184            idx: self.api.reg(),
185            api: self.api.clone(),
186        }
187    }
188}
189
190#[must_use = "futures do nothing unless polled"]
191/// Pipeline call
192pub struct PipelineCall<Req, Res, Err> {
193    #[allow(dead_code)]
194    pl: PipelineBinding<Req, Res, Err>,
195    fut: BoxFuture<'static, Result<Res, Err>>,
196}
197
198impl<Req, Res, Err> PipelineCall<Req, Res, Err> {
199    #[allow(clippy::missing_transmute_annotations)]
200    fn new(pl: PipelineBinding<Req, Res, Err>, req: Req, ready: bool) -> Self {
201        // SAFETY: `fut` has same lifetime same as lifetime of `self.pl`.
202        // and it is being kept alive until `self` is alive
203        PipelineCall {
204            fut: unsafe { std::mem::transmute(pl.api.call(pl.idx, req, ready)) },
205            pl,
206        }
207    }
208}
209
210impl<Req, Res, Err> future::Future for PipelineCall<Req, Res, Err> {
211    type Output = Result<Res, Err>;
212
213    #[inline]
214    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
215        Pin::new(&mut self.as_mut().fut).poll(cx)
216    }
217}
218
219impl<Req, Res, Err> fmt::Debug for Pipeline<Req, Res, Err> {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        f.debug_struct("Pipeline").finish()
222    }
223}
224
225impl<Req, Res, Err> fmt::Debug for PipelineBinding<Req, Res, Err> {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        f.debug_struct("PipelineBinding")
228            .field("idx", &self.idx)
229            .finish()
230    }
231}
232
233impl<Req, Res, Err> fmt::Debug for PipelineCall<Req, Res, Err> {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        f.debug_struct("PipelineCall").finish()
236    }
237}