Skip to main content

ntex_service/
pipeline.rs

1use std::{cell, fmt, future, pin::Pin, ptr, rc::Rc, task::Context, task::Poll};
2
3use crate::state::{Noop, State};
4use crate::{Ctx, IntoService, Service, ctx::WaitersRef, util::BoxFuture};
5
6pub use crate::pl_factory::PipelineFactory;
7pub use crate::pl_nost::{PipelineWithState, PipelineWithStateBinding};
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    state: Rc<dyn PipelineApi<Req, Res, Err>>,
14}
15
16/// Bound container for a service.
17pub struct PipelineBinding<Req, Res, Err> {
18    index: u32,
19    state: Rc<dyn PipelineApi<Req, Res, Err>>,
20}
21
22struct PipelineState<S: Service<St, Req>, St, Req, Ctl> {
23    s: S,
24    st: St,
25    st_ctl: Ctl,
26    st_runtime: cell::UnsafeCell<RuntimeState<S::Error>>,
27    waiters: WaitersRef,
28}
29
30impl<Req, Res, Err> Pipeline<Req, Res, Err>
31where
32    Req: 'static,
33    Res: 'static,
34    Err: 'static,
35{
36    #[inline]
37    /// Construct new service pipeline instance with default state.
38    pub fn new<S, St>(f: impl IntoService<S, St, Req>) -> Self
39    where
40        S: Service<St, Req, Res = Res, Error = Err> + 'static,
41        St: Default + 'static,
42    {
43        Self::create(f.into_service(), St::default(), Noop)
44    }
45
46    #[inline]
47    /// Construct new service pipeline instance with state.
48    pub fn with<S, St>(st: St, f: impl IntoService<S, St, Req>) -> Self
49    where
50        S: Service<St, Req, Res = Res, Error = Err> + 'static,
51        St: 'static,
52    {
53        Self::create(f.into_service(), st, Noop)
54    }
55
56    #[inline]
57    /// Construct new service pipeline instance with state.
58    pub fn with_ctl<S, St, Ctl>(st: St, ctl: Ctl, f: impl IntoService<S, St, Req>) -> Self
59    where
60        S: Service<St, Req, Res = Res, Error = Err> + 'static,
61        St: 'static,
62        Ctl: State<St, Req> + 'static,
63    {
64        Self::create(f.into_service(), st, ctl)
65    }
66
67    fn create<S, St, Ctl>(s: S, st: St, ctl: Ctl) -> Self
68    where
69        S: Service<St, Req, Res = Res, Error = Err> + 'static,
70        St: 'static,
71        Ctl: State<St, Req> + 'static,
72    {
73        Pipeline {
74            state: Rc::new(PipelineState {
75                s,
76                st,
77                waiters: WaitersRef::new(),
78                st_ctl: ctl,
79                st_runtime: cell::UnsafeCell::new(RuntimeState::New),
80            }),
81        }
82    }
83
84    #[inline]
85    /// Returns when the pipeline is ready to process requests.
86    pub async fn ready(&self) -> Result<(), Err> {
87        future::poll_fn(|cx| self.state.poll_ready(cx)).await
88    }
89
90    #[inline]
91    /// Wait for service readiness, then create a future
92    /// that resolves to the service call result.
93    pub async fn call(&self, req: Req) -> Result<Res, Err> {
94        let pl = self.bind();
95        pl.state.call(pl.index, req, true).await
96    }
97
98    #[inline]
99    /// Wait for service readiness, then create a future
100    /// that resolves to the service result.
101    ///
102    /// This call can be completed from different async tasks.
103    pub fn call_static(&self, req: Req) -> PipelineCall<Req, Res, Err> {
104        PipelineCall::new(self.bind(), req, true)
105    }
106
107    #[inline]
108    /// Call the service and create a future that resolves to the service result.
109    ///
110    /// This call can be completed from different async tasks.
111    /// Note: this call does not check service readiness.
112    pub fn call_nowait(&self, req: Req) -> PipelineCall<Req, Res, Err> {
113        PipelineCall::new(self.bind(), req, false)
114    }
115
116    #[inline]
117    /// Returns `Ready` when the pipeline is ready to process requests.
118    pub fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Err>> {
119        self.state.poll_ready(cx)
120    }
121
122    #[inline]
123    /// Returns `Ready` when the service has been properly shut down.
124    pub fn poll_shutdown(&self, cx: &mut Context<'_>) -> Poll<()> {
125        self.state.poll_shutdown(cx)
126    }
127
128    #[inline]
129    /// Checks whether pipeline shutdown has been initiated.
130    pub fn is_shutdown(&self) -> bool {
131        self.state.is_shutdown()
132    }
133
134    #[inline]
135    /// Shuts down the enclosed service.
136    pub async fn shutdown(&self) {
137        future::poll_fn(|cx| self.state.poll_shutdown(cx)).await;
138    }
139
140    #[inline]
141    /// Returns the current pipeline binding.
142    ///
143    /// The binding can be used to check readiness and call the service.
144    pub fn bind(&self) -> PipelineBinding<Req, Res, Err> {
145        PipelineBinding::new(self)
146    }
147}
148
149impl<Req, Res, Err> Drop for Pipeline<Req, Res, Err> {
150    #[inline]
151    fn drop(&mut self) {
152        self.state.unreg(0);
153    }
154}
155
156impl<Req, Res, Err> PipelineBinding<Req, Res, Err>
157where
158    Req: 'static,
159    Res: 'static,
160    Err: 'static,
161{
162    fn new(pl: &Pipeline<Req, Res, Err>) -> Self {
163        Self {
164            index: pl.state.reg(),
165            state: pl.state.clone(),
166        }
167    }
168
169    #[inline]
170    /// Returns when the pipeline is ready to process requests.
171    pub async fn ready(&self) -> Result<(), Err> {
172        self.state.ready(self.index).await
173    }
174
175    #[inline]
176    /// Wait for service readiness, then create a future
177    /// that resolves to the service call result.
178    pub async fn call(&self, req: Req) -> Result<Res, Err> {
179        let pl = self.clone();
180        pl.state.call(pl.index, req, true).await
181    }
182
183    #[inline]
184    /// Wait for service readiness, then create a future
185    /// that resolves to the service result.
186    ///
187    /// This call can be completed from different async tasks.
188    pub fn call_static(&self, req: Req) -> PipelineCall<Req, Res, Err> {
189        PipelineCall::new(self.clone(), req, true)
190    }
191
192    #[inline]
193    /// Call the service and create a future that resolves to the service result.
194    ///
195    /// This call can be completed from different async tasks.
196    /// Note: this call does not check service readiness.
197    pub fn call_nowait(&self, req: Req) -> PipelineCall<Req, Res, Err> {
198        PipelineCall::new(self.clone(), req, false)
199    }
200
201    #[inline]
202    /// Shuts down the enclosed service.
203    pub async fn shutdown(&self) {
204        future::poll_fn(|cx| self.state.poll_shutdown(cx)).await;
205    }
206}
207
208impl<Req, Res, Err> Drop for PipelineBinding<Req, Res, Err> {
209    #[inline]
210    fn drop(&mut self) {
211        self.state.unreg(self.index);
212    }
213}
214
215impl<Req, Res, Err> Clone for PipelineBinding<Req, Res, Err> {
216    fn clone(&self) -> Self {
217        Self {
218            index: self.state.reg(),
219            state: self.state.clone(),
220        }
221    }
222}
223
224#[must_use = "futures do nothing unless polled"]
225/// Pipeline call
226pub struct PipelineCall<Req, Res, Err> {
227    #[allow(dead_code)]
228    pl: PipelineBinding<Req, Res, Err>,
229    fut: BoxFuture<'static, Result<Res, Err>>,
230}
231
232impl<Req, Res, Err> PipelineCall<Req, Res, Err> {
233    #[allow(clippy::missing_transmute_annotations)]
234    fn new(pl: PipelineBinding<Req, Res, Err>, req: Req, ready: bool) -> Self {
235        // SAFETY: `fut` has same lifetime same as lifetime of `self.pl`.
236        // and it is being kept alive until `self` is alive
237        PipelineCall {
238            fut: unsafe { std::mem::transmute(pl.state.call(pl.index, req, ready)) },
239            pl,
240        }
241    }
242}
243
244impl<Req, Res, Err> future::Future for PipelineCall<Req, Res, Err> {
245    type Output = Result<Res, Err>;
246
247    #[inline]
248    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
249        Pin::new(&mut self.as_mut().fut).poll(cx)
250    }
251}
252
253impl<S, St, Req, Ctl> PipelineState<S, St, Req, Ctl>
254where
255    S: Service<St, Req>,
256    Ctl: State<St, Req>,
257{
258    fn st(&self, req: &Req) -> StateRef<'_, St> {
259        if let Some(s) = self.st_ctl.on_req(&self.st, req) {
260            StateRef::Owned(s)
261        } else {
262            StateRef::Ref(&self.st)
263        }
264    }
265}
266
267enum RuntimeState<E> {
268    New,
269    Readiness(BoxFuture<'static, Result<(), E>>),
270    Shutdown(BoxFuture<'static, ()>),
271    Done,
272}
273
274enum StateRef<'a, T> {
275    Ref(&'a T),
276    Owned(T),
277}
278
279impl<'a, T> StateRef<'a, T> {
280    fn get_ref(&'a self) -> &'a T {
281        match self {
282            StateRef::Ref(t) => t,
283            StateRef::Owned(t) => t,
284        }
285    }
286}
287
288trait PipelineApi<Req, Res, Err> {
289    fn reg(&self) -> u32;
290
291    fn unreg(&self, idx: u32);
292
293    fn ready(&self, idx: u32) -> BoxFuture<'_, Result<(), Err>>;
294
295    fn call(&self, idx: u32, req: Req, ready: bool) -> BoxFuture<'_, Result<Res, Err>>;
296
297    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Err>>;
298
299    fn poll_shutdown(&self, cx: &mut Context<'_>) -> Poll<()>;
300
301    fn is_shutdown(&self) -> bool;
302}
303
304impl<S, St, Req, Ctl> PipelineApi<Req, S::Res, S::Error> for PipelineState<S, St, Req, Ctl>
305where
306    S: Service<St, Req> + 'static,
307    St: 'static,
308    Req: 'static,
309    Ctl: State<St, Req> + 'static,
310{
311    fn reg(&self) -> u32 {
312        self.waiters.insert()
313    }
314
315    fn unreg(&self, index: u32) {
316        self.waiters.remove(index);
317    }
318
319    fn ready(&self, idx: u32) -> BoxFuture<'_, Result<(), S::Error>> {
320        Box::pin(async move {
321            Ctx::<'_, S, St>::new(idx, &self.waiters, &self.st)
322                .ready(&self.s)
323                .await
324        })
325    }
326
327    fn call(&self, idx: u32, req: Req, ready: bool) -> BoxFuture<'_, Result<S::Res, S::Error>> {
328        Box::pin(async move {
329            let st = self.st(&req);
330
331            if ready {
332                Ctx::<'_, S, St>::new(idx, &self.waiters, st.get_ref())
333                    .call(&self.s, req)
334                    .await
335            } else {
336                Ctx::<'_, S, St>::new(idx, &self.waiters, st.get_ref())
337                    .call_nowait(&self.s, req)
338                    .await
339            }
340        })
341    }
342
343    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
344        let st = unsafe { &mut *self.st_runtime.get() };
345        match st {
346            RuntimeState::New => {
347                // SAFETY: `fut` has same lifetime same as lifetime of `self.pl`.
348                // Pipeline::svc is heap allocated(Rc<S>), and it is being kept alive until
349                // `self` is alive
350                let pl = unsafe { &*(ptr::from_ref(self)) };
351                let fut = Box::pin(CheckReadiness {
352                    pl,
353                    f: ready,
354                    fut: None,
355                });
356                *st = RuntimeState::Readiness(fut);
357                self.poll_ready(cx)
358            }
359            RuntimeState::Readiness(fut) => Pin::new(fut).poll(cx),
360            RuntimeState::Shutdown(_) | RuntimeState::Done => Poll::Ready(Ok(())),
361        }
362    }
363
364    fn poll_shutdown(&self, cx: &mut Context<'_>) -> Poll<()> {
365        let st = unsafe { &mut *self.st_runtime.get() };
366        match st {
367            RuntimeState::New | RuntimeState::Readiness(_) => {
368                // SAFETY: `fut` has same lifetime same as lifetime of `self.pl`.
369                // Pipeline::svc is heap allocated(Rc<S>), and it is being kept alive until
370                // `self` is alive
371                let pl = unsafe { &*(ptr::from_ref(self)) };
372
373                let fut = Box::pin(async move {
374                    let ctx = Ctx::<'_, S, St>::new(0, &pl.waiters, &pl.st);
375                    pl.s.shutdown(ctx).await;
376                });
377                *st = RuntimeState::Shutdown(fut);
378                pl.waiters.shutdown();
379                self.poll_shutdown(cx)
380            }
381            RuntimeState::Shutdown(fut) => {
382                let res = Pin::new(fut).poll(cx);
383                if res.is_ready() {
384                    *st = RuntimeState::Done;
385                }
386                res
387            }
388            RuntimeState::Done => Poll::Ready(()),
389        }
390    }
391
392    fn is_shutdown(&self) -> bool {
393        self.waiters.is_shutdown()
394    }
395}
396
397fn ready<S, St, Req, Ctl>(
398    pl: &'static PipelineState<S, St, Req, Ctl>,
399) -> impl future::Future<Output = Result<(), S::Error>>
400where
401    S: Service<St, Req>,
402    Ctl: State<St, Req>,
403{
404    pl.s.ready(Ctx::<'_, S, St>::new(0, &pl.waiters, &pl.st))
405}
406
407struct CheckReadiness<S, St, Req, Ctl, F, Fut>
408where
409    S: Service<St, Req> + 'static,
410    St: 'static,
411    Req: 'static,
412    Ctl: 'static,
413{
414    f: F,
415    fut: Option<Fut>,
416    pl: &'static PipelineState<S, St, Req, Ctl>,
417}
418
419impl<S: Service<St, Req>, St, Req, Ctl, F, Fut> Unpin for CheckReadiness<S, St, Req, Ctl, F, Fut> {}
420
421impl<S: Service<St, Req>, St, Req, Ctl, F, Fut> Drop for CheckReadiness<S, St, Req, Ctl, F, Fut> {
422    fn drop(&mut self) {
423        // future got dropped during polling, we must notify other waiters
424        if self.fut.is_some() {
425            self.pl.waiters.notify();
426        }
427    }
428}
429
430impl<S, St, Req, Ctl, F, Fut> Future for CheckReadiness<S, St, Req, Ctl, F, Fut>
431where
432    S: Service<St, Req>,
433    F: Fn(&'static PipelineState<S, St, Req, Ctl>) -> Fut,
434    Fut: Future<Output = Result<(), S::Error>>,
435{
436    type Output = Result<(), S::Error>;
437
438    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
439        let mut this = self.as_mut();
440
441        this.pl.waiters.run(0, cx, |cx| {
442            if this.fut.is_none() {
443                this.fut = Some((this.f)(this.pl));
444            }
445            let fut = this.fut.as_mut().unwrap();
446            let result = unsafe { Pin::new_unchecked(fut) }.poll(cx);
447            if result.is_ready() {
448                let _ = this.fut.take();
449            }
450            result
451        })
452    }
453}
454
455impl<Req, Res, Err> fmt::Debug for Pipeline<Req, Res, Err> {
456    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457        f.debug_struct("Pipeline").finish()
458    }
459}
460
461impl<Req, Res, Err> fmt::Debug for PipelineBinding<Req, Res, Err> {
462    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463        f.debug_struct("PipelineBinding")
464            .field("idx", &self.index)
465            .finish()
466    }
467}
468
469impl<Req, Res, Err> fmt::Debug for PipelineCall<Req, Res, Err> {
470    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471        f.debug_struct("PipelineCall").finish()
472    }
473}