Skip to main content

ntex_util/services/
buffer.rs

1//! Service that buffers incoming requests.
2#![allow(clippy::type_complexity)]
3use std::cell::{Cell, RefCell};
4use std::{collections::VecDeque, fmt, future, marker, task, task::Poll};
5
6use ntex_service::{Ctx, Middleware, Service, pipeline::PipelineState};
7
8use crate::channel::oneshot;
9
10#[derive(Copy, Clone, Debug)]
11/// Middleware that buffers requests while the wrapped service is not ready.
12///
13/// The default buffer capacity is 16 requests.
14pub struct Buffer<St: Clone, Req, Res, Err> {
15    buf_size: usize,
16    cancel_on_shutdown: bool,
17    st: marker::PhantomData<fn(St, Req) -> Result<Res, Err>>,
18}
19
20impl<St: Clone, Req, Res, Err> Buffer<St, Req, Res, Err> {
21    /// Sets the maximum number of buffered requests.
22    ///
23    /// The default capacity is 16.
24    #[must_use]
25    pub fn buf_size(mut self, size: usize) -> Self {
26        self.buf_size = size;
27        self
28    }
29
30    /// Cancel all buffered requests on shutdown.
31    ///
32    /// By default, buffered requests are released during service shutdown.
33    #[must_use]
34    pub fn cancel_on_shutdown(mut self) -> Self {
35        self.cancel_on_shutdown = true;
36        self
37    }
38}
39
40impl<St: Clone, Req, Res, Err> Default for Buffer<St, Req, Res, Err> {
41    fn default() -> Self {
42        Self {
43            buf_size: 16,
44            cancel_on_shutdown: false,
45            st: marker::PhantomData,
46        }
47    }
48}
49
50impl<S, St, Req, Res, Err> Middleware<S, St> for Buffer<St, Req, Res, Err>
51where
52    S: Service<St, Req, Res = Res, Error = Err> + 'static,
53    St: Clone + 'static,
54    Req: 'static,
55    Res: 'static,
56    Err: 'static,
57{
58    type Service = BufferService<St, Req, Res, Err>;
59
60    fn create(&self, _: &St, service: S) -> Self::Service {
61        let service = BufferService::new(self.buf_size, PipelineState::new(service));
62        if self.cancel_on_shutdown {
63            service.cancel_on_shutdown()
64        } else {
65            service
66        }
67    }
68}
69
70/// Errors returned by [`BufferService`].
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum BufferServiceError<E> {
73    /// Error returned by the wrapped service.
74    Service(E),
75    /// The buffered request was canceled before it could be processed.
76    RequestCanceled,
77}
78
79impl<E> From<E> for BufferServiceError<E> {
80    fn from(err: E) -> Self {
81        BufferServiceError::Service(err)
82    }
83}
84
85impl<E: fmt::Display> fmt::Display for BufferServiceError<E> {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            BufferServiceError::Service(e) => fmt::Display::fmt(e, f),
89            BufferServiceError::RequestCanceled => f.write_str("buffer service request canceled"),
90        }
91    }
92}
93
94impl<E: fmt::Display + fmt::Debug> std::error::Error for BufferServiceError<E> {}
95
96/// A service that buffers requests while its wrapped service is not ready.
97///
98/// The default buffer capacity is 16 requests.
99pub struct BufferService<St, Req, Res, Err> {
100    size: usize,
101    ready: Cell<bool>,
102    service: PipelineState<St, Req, Res, Err>,
103    buf: RefCell<VecDeque<oneshot::Sender<oneshot::Sender<()>>>>,
104    next_call: RefCell<Option<oneshot::Receiver<()>>>,
105    cancel_on_shutdown: bool,
106    readiness: Cell<Option<task::Waker>>,
107}
108
109impl<St, Req, Res, Err> BufferService<St, Req, Res, Err>
110where
111    St: Clone + 'static,
112{
113    #[must_use]
114    /// Creates a buffering service with the specified capacity.
115    pub fn new(size: usize, service: PipelineState<St, Req, Res, Err>) -> Self {
116        Self {
117            size,
118            service,
119            ready: Cell::new(false),
120            buf: RefCell::new(VecDeque::with_capacity(size)),
121            next_call: RefCell::default(),
122            cancel_on_shutdown: false,
123            readiness: Cell::new(None),
124        }
125    }
126
127    #[must_use]
128    /// Cancels pending buffered requests instead of releasing them on shutdown.
129    pub fn cancel_on_shutdown(self) -> Self {
130        Self {
131            cancel_on_shutdown: true,
132            ..self
133        }
134    }
135}
136
137impl<St, Req, Res, Err> fmt::Debug for BufferService<St, Req, Res, Err> {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        f.debug_struct("BufferService")
140            .field("size", &self.size)
141            .field("cancel_on_shutdown", &self.cancel_on_shutdown)
142            .field("ready", &self.ready)
143            .field("service", &self.service)
144            .field("buf", &self.buf)
145            .field("next_call", &self.next_call)
146            .finish()
147    }
148}
149
150impl<St, Req, Res, Err> Service<St, Req> for BufferService<St, Req, Res, Err>
151where
152    St: Clone + 'static,
153    Req: 'static,
154    Res: 'static,
155    Err: 'static,
156{
157    type Res = Res;
158    type Error = BufferServiceError<Err>;
159
160    async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), Self::Error> {
161        // hold advancement until the last released task either makes a call or is dropped
162        let next_call = self.next_call.borrow_mut().take();
163        if let Some(next_call) = next_call {
164            let _ = next_call.recv().await;
165        }
166
167        ctx.poll_fn(|cx| {
168            let mut buffer = self.buf.borrow_mut();
169
170            // handle inner service readiness
171            if self.service.poll_ready(cx, ctx.st())?.is_pending() {
172                if buffer.len() < self.size {
173                    // buffer next request
174                    self.ready.set(false);
175                    Poll::Ready(Ok(()))
176                } else {
177                    log::trace!("Buffer limit exceeded");
178                    // service is not ready
179                    let _ = self.readiness.take().map(task::Waker::wake);
180                    Poll::Pending
181                }
182            } else {
183                while let Some(sender) = buffer.pop_front() {
184                    let (next_call_tx, next_call_rx) = oneshot::channel();
185                    if sender.send(next_call_tx).is_err() || next_call_rx.poll_recv(cx).is_ready() {
186                        // the task is gone
187                        continue;
188                    }
189                    self.next_call.borrow_mut().replace(next_call_rx);
190                    self.ready.set(false);
191                    return Poll::Ready(Ok(()));
192                }
193
194                self.ready.set(true);
195                Poll::Ready(Ok(()))
196            }
197        })
198        .await
199    }
200
201    async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {
202        // hold advancement until the last released task either makes a call or is dropped
203        let next_call = self.next_call.borrow_mut().take();
204        if let Some(next_call) = next_call {
205            let _ = next_call.recv().await;
206        }
207
208        future::poll_fn(|cx| {
209            let mut buffer = self.buf.borrow_mut();
210            if self.cancel_on_shutdown {
211                buffer.clear();
212            }
213
214            if !buffer.is_empty() {
215                if task::ready!(self.service.poll_ready(cx, ctx.st())).is_err() {
216                    log::error!("Buffered inner service failed while buffer flushing on shutdown");
217                    return Poll::Ready(());
218                }
219
220                while let Some(sender) = buffer.pop_front() {
221                    let (next_call_tx, next_call_rx) = oneshot::channel();
222                    if sender.send(next_call_tx).is_err() || next_call_rx.poll_recv(cx).is_ready() {
223                        // the task is gone
224                        continue;
225                    }
226                    self.next_call.borrow_mut().replace(next_call_rx);
227                    if buffer.is_empty() {
228                        break;
229                    }
230                    return Poll::Pending;
231                }
232            }
233            Poll::Ready(())
234        })
235        .await;
236
237        self.service.shutdown(ctx.st()).await;
238    }
239
240    async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<Res, Self::Error> {
241        if self.ready.get() {
242            self.ready.set(false);
243            Ok(self.service.call_nowait(req, ctx.st()).await?)
244        } else {
245            let (tx, rx) = oneshot::channel();
246            self.buf.borrow_mut().push_back(tx);
247
248            // release
249            let _task_guard = rx.recv().await.map_err(|_| {
250                log::trace!("Buffered service request canceled");
251                BufferServiceError::RequestCanceled
252            })?;
253
254            // call service
255            Ok(self.service.call(req, ctx.st()).await?)
256        }
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    #![allow(clippy::unused_async_trait_impl)]
263    use ntex_service::{Pipeline, apply, fn_factory};
264    use std::{cell::RefCell, rc::Rc, time::Duration};
265
266    use super::*;
267    use crate::{future::lazy, task::LocalWaker};
268
269    #[derive(Debug, Clone)]
270    struct TestService(Rc<Inner>);
271
272    #[derive(Debug)]
273    struct Inner {
274        ready: Cell<bool>,
275        waker: LocalWaker,
276        count: Cell<usize>,
277    }
278
279    impl Service<(), ()> for TestService {
280        type Res = ();
281        type Error = ();
282
283        async fn ready(&self, ctx: Ctx<'_, Self, ()>) -> Result<(), Self::Error> {
284            ctx.poll_fn(|cx| {
285                self.0.waker.register(cx.waker());
286                if self.0.ready.get() {
287                    Poll::Ready(Ok(()))
288                } else {
289                    Poll::Pending
290                }
291            })
292            .await
293        }
294
295        async fn call(&self, _r: (), _: Ctx<'_, Self, ()>) -> Result<(), ()> {
296            self.0.ready.set(false);
297            self.0.count.set(self.0.count.get() + 1);
298            Ok(())
299        }
300    }
301
302    struct PendingService {
303        inner: Rc<PendingInner>,
304        release: RefCell<Option<oneshot::Receiver<()>>>,
305    }
306
307    struct PendingInner {
308        started: Cell<bool>,
309        completed: Cell<bool>,
310        shutdown: Cell<bool>,
311    }
312
313    impl Service<(), ()> for PendingService {
314        type Res = ();
315        type Error = ();
316
317        async fn call(&self, (): (), _: Ctx<'_, Self, ()>) -> Result<(), ()> {
318            self.inner.started.set(true);
319            let release = self.release.borrow_mut().take().unwrap();
320            release.recv().await.unwrap();
321            self.inner.completed.set(true);
322            Ok(())
323        }
324
325        async fn shutdown(&self, _: Ctx<'_, Self, ()>) {
326            self.inner.shutdown.set(true);
327        }
328    }
329
330    #[ntex::test]
331    async fn test_service() {
332        let inner = Rc::new(Inner {
333            ready: Cell::new(false),
334            waker: LocalWaker::default(),
335            count: Cell::new(0),
336        });
337
338        let svc = BufferService::new(2, PipelineState::new(TestService(inner.clone())));
339        assert!(format!("{svc:?}").contains("BufferService"));
340
341        let srv = Pipeline::new((), svc);
342        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
343
344        let srv1 = srv.bind();
345        ntex::rt::spawn(async move {
346            let _ = srv1.call(()).await;
347        });
348        crate::time::sleep(Duration::from_millis(25)).await;
349        assert_eq!(inner.count.get(), 0);
350        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
351
352        let srv1 = srv.bind();
353        ntex::rt::spawn(async move {
354            let _ = srv1.call(()).await;
355        });
356        crate::time::sleep(Duration::from_millis(25)).await;
357        assert_eq!(inner.count.get(), 0);
358        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Pending);
359
360        inner.ready.set(true);
361        inner.waker.wake();
362        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
363
364        crate::time::sleep(Duration::from_millis(25)).await;
365        assert_eq!(inner.count.get(), 1);
366
367        inner.ready.set(true);
368        inner.waker.wake();
369        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
370
371        crate::time::sleep(Duration::from_millis(25)).await;
372        assert_eq!(inner.count.get(), 2);
373
374        let inner = Rc::new(Inner {
375            ready: Cell::new(true),
376            waker: LocalWaker::default(),
377            count: Cell::new(0),
378        });
379
380        let srv = Pipeline::new(
381            (),
382            BufferService::new(2, PipelineState::new(TestService(inner.clone()))),
383        );
384        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
385
386        let _ = srv.call(()).await;
387        assert_eq!(inner.count.get(), 1);
388        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
389        assert!(lazy(|cx| srv.poll_shutdown(cx)).await.is_ready());
390
391        let err = BufferServiceError::from("test");
392        assert!(format!("{err}").contains("test"));
393        assert!(format!("{:?}", Buffer::<(), (), (), ()>::default()).contains("Buffer"));
394    }
395
396    #[ntex::test]
397    #[allow(clippy::redundant_clone)]
398    async fn test_middleware() {
399        let inner = Rc::new(Inner {
400            ready: Cell::new(false),
401            waker: LocalWaker::default(),
402            count: Cell::new(0),
403        });
404        let inner2 = inner.clone();
405
406        let srv = apply(
407            Buffer::default().buf_size(2),
408            fn_factory(async move |(): &()| Ok::<_, ()>(TestService(inner2.clone()))),
409        );
410
411        let srv = srv.pipeline(()).await.unwrap();
412        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
413
414        let srv1 = srv.bind();
415        ntex::rt::spawn(async move {
416            let _ = srv1.call(()).await;
417        });
418        crate::time::sleep(Duration::from_millis(25)).await;
419        assert_eq!(inner.count.get(), 0);
420        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
421
422        let srv1 = srv.bind();
423        ntex::rt::spawn(async move {
424            let _ = srv1.call(()).await;
425        });
426        crate::time::sleep(Duration::from_millis(25)).await;
427        assert_eq!(inner.count.get(), 0);
428        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Pending);
429
430        inner.ready.set(true);
431        inner.waker.wake();
432        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
433
434        crate::time::sleep(Duration::from_millis(25)).await;
435        assert_eq!(inner.count.get(), 1);
436
437        inner.ready.set(true);
438        inner.waker.wake();
439        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
440
441        crate::time::sleep(Duration::from_millis(25)).await;
442        assert_eq!(inner.count.get(), 2);
443    }
444
445    #[ntex::test]
446    #[allow(clippy::redundant_clone)]
447    async fn test_middleware2() {
448        let inner = Rc::new(Inner {
449            ready: Cell::new(false),
450            waker: LocalWaker::default(),
451            count: Cell::new(0),
452        });
453        let inner2 = inner.clone();
454
455        let srv = apply(
456            Buffer::default().buf_size(2),
457            fn_factory(async move |(): &()| Ok::<_, ()>(TestService(inner2.clone()))),
458        );
459
460        let srv = srv.pipeline(()).await.unwrap();
461        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
462
463        let srv1 = srv.bind();
464        ntex::rt::spawn(async move {
465            let _ = srv1.call(()).await;
466        });
467        crate::time::sleep(Duration::from_millis(25)).await;
468        assert_eq!(inner.count.get(), 0);
469        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
470
471        let srv1 = srv.bind();
472        ntex::rt::spawn(async move {
473            let _ = srv1.call(()).await;
474        });
475        crate::time::sleep(Duration::from_millis(25)).await;
476        assert_eq!(inner.count.get(), 0);
477        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Pending);
478
479        inner.ready.set(true);
480        inner.waker.wake();
481        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
482
483        crate::time::sleep(Duration::from_millis(25)).await;
484        assert_eq!(inner.count.get(), 1);
485
486        inner.ready.set(true);
487        inner.waker.wake();
488        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
489
490        crate::time::sleep(Duration::from_millis(25)).await;
491        assert_eq!(inner.count.get(), 2);
492    }
493
494    #[ntex::test]
495    async fn middleware_cancels_buffered_requests_on_shutdown() {
496        let inner = Rc::new(Inner {
497            ready: Cell::new(false),
498            waker: LocalWaker::default(),
499            count: Cell::new(0),
500        });
501        let inner2 = inner.clone();
502
503        let srv = apply(
504            Buffer::default().buf_size(1).cancel_on_shutdown(),
505            fn_factory(async move |(): &()| Ok::<_, ()>(TestService(inner2.clone()))),
506        )
507        .pipeline(())
508        .await
509        .unwrap();
510
511        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
512
513        let canceled = Rc::new(Cell::new(false));
514        let canceled2 = canceled.clone();
515        let srv2 = srv.bind();
516        ntex::rt::spawn(async move {
517            canceled2.set(matches!(
518                srv2.call(()).await,
519                Err(BufferServiceError::RequestCanceled)
520            ));
521        });
522
523        crate::time::sleep(Duration::from_millis(25)).await;
524        srv.shutdown().await;
525        crate::time::sleep(Duration::from_millis(25)).await;
526
527        assert!(canceled.get());
528        assert_eq!(inner.count.get(), 0);
529    }
530
531    #[ntex::test]
532    async fn shutdown_with_in_flight_request() {
533        let inner = Rc::new(PendingInner {
534            started: Cell::new(false),
535            completed: Cell::new(false),
536            shutdown: Cell::new(false),
537        });
538        let (release_tx, release_rx) = oneshot::channel();
539        let service = PendingService {
540            inner: inner.clone(),
541            release: RefCell::new(Some(release_rx)),
542        };
543        let srv = Pipeline::new((), BufferService::new(1, PipelineState::new(service)));
544
545        assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
546
547        let srv2 = srv.bind();
548        ntex::rt::spawn(async move {
549            srv2.call(()).await.unwrap();
550        });
551        crate::time::sleep(Duration::from_millis(25)).await;
552
553        assert!(inner.started.get());
554        assert!(!inner.completed.get());
555        assert!(lazy(|cx| srv.poll_shutdown(cx)).await.is_ready());
556        assert!(inner.shutdown.get());
557        assert!(!inner.completed.get());
558
559        release_tx.send(()).unwrap();
560        crate::time::sleep(Duration::from_millis(25)).await;
561
562        assert!(inner.completed.get());
563    }
564}