Skip to main content

ntex_util/services/
counter.rs

1use std::{
2    cell::Cell, cell::OnceCell, cell::RefCell, future::poll_fn, rc::Rc, task::Context, task::Poll,
3};
4
5use ntex_service::{BindCtx, ReadyNotifier};
6
7use crate::{future::OnDropFn, task::LocalWaker};
8
9/// A shared count with an asynchronous capacity notification.
10///
11/// Clones use the same count and capacity, but each clone registers its own
12/// waiting task.
13#[derive(Debug)]
14pub struct Counter(usize, Rc<CounterInner>);
15
16#[derive(Debug)]
17struct CounterInner {
18    count: Cell<usize>,
19    capacity: Cell<usize>,
20    tasks: RefCell<slab::Slab<LocalWaker>>,
21    changed: OnceCell<ReadyNotifier>,
22}
23
24impl Counter {
25    /// Creates a counter with the specified capacity.
26    pub fn new(capacity: usize) -> Self {
27        let mut tasks = slab::Slab::new();
28        let idx = tasks.insert(LocalWaker::new());
29
30        Counter(
31            idx,
32            Rc::new(CounterInner {
33                count: Cell::new(0),
34                capacity: Cell::new(capacity),
35                tasks: RefCell::new(tasks),
36                changed: OnceCell::new(),
37            }),
38        )
39    }
40
41    /// Acquires one count and returns a guard that releases it on drop.
42    ///
43    /// This does not wait for capacity; call [`available`](Self::available)
44    /// first when exceeding the configured capacity is not acceptable.
45    pub fn get(&self) -> CounterGuard {
46        CounterGuard::new(self.1.clone())
47    }
48
49    /// Changes the capacity and wakes waiting tasks.
50    pub fn set_capacity(&self, cap: usize) {
51        self.1.capacity.set(cap);
52        self.1.notify();
53    }
54
55    /// Returns `true` if another count can be acquired without exceeding the capacity.
56    pub fn is_available(&self) -> bool {
57        self.1.count.get() < self.1.capacity.get()
58    }
59
60    /// Waits until the counter has free capacity.
61    ///
62    /// Returns immediately if there is capacity available. Otherwise,
63    /// registers the current task for wakeup and waits until a slot is freed.
64    pub async fn available(&self) {
65        poll_fn(|cx| {
66            if self.poll_available(cx) {
67                Poll::Ready(())
68            } else {
69                Poll::Pending
70            }
71        })
72        .await;
73    }
74
75    /// Waits until the counter reaches its capacity (i.e., becomes unavailable).
76    pub async fn unavailable(&self) {
77        poll_fn(|cx| {
78            if self.is_available() {
79                self.1.tasks.borrow()[self.0].register(cx.waker());
80                Poll::Pending
81            } else {
82                Poll::Ready(())
83            }
84        })
85        .await;
86    }
87
88    /// Check if counter is not at capacity. If counter at capacity
89    /// it registers notification for current task.
90    fn poll_available(&self, cx: &mut Context<'_>) -> bool {
91        if self.1.count.get() < self.1.capacity.get() {
92            true
93        } else {
94            let tasks = self.1.tasks.borrow();
95            tasks[self.0].register(cx.waker());
96            false
97        }
98    }
99
100    /// Returns the number of currently held guards.
101    pub fn total(&self) -> usize {
102        self.1.count.get()
103    }
104
105    /// Subscribes `ctx` to capacity changes.
106    ///
107    /// Services whose readiness depends on this counter must call this in
108    /// [`Service::bind`](ntex_service::Service::bind), otherwise their cached
109    /// readiness is not invalidated when the counter fills or frees a slot.
110    pub fn bind(&self, ctx: &BindCtx<'_>) {
111        ctx.subscribe(self.1.changed.get_or_init(ReadyNotifier::new));
112    }
113}
114
115impl Clone for Counter {
116    fn clone(&self) -> Self {
117        let idx = self.1.tasks.borrow_mut().insert(LocalWaker::new());
118        Self(idx, self.1.clone())
119    }
120}
121
122impl Drop for Counter {
123    fn drop(&mut self) {
124        self.1.tasks.borrow_mut().remove(self.0);
125    }
126}
127
128#[derive(Debug)]
129/// An acquired counter slot.
130///
131/// Dropping the guard releases the slot and wakes availability waiters.
132pub struct CounterGuard(Rc<CounterInner>);
133
134impl CounterGuard {
135    fn new(inner: Rc<CounterInner>) -> Self {
136        let guard = CounterGuard(inner);
137        guard.0.inc();
138        guard
139    }
140}
141
142impl Unpin for CounterGuard {}
143
144impl Drop for CounterGuard {
145    fn drop(&mut self) {
146        self.0.dec();
147    }
148}
149
150impl CounterInner {
151    fn inc(&self) {
152        let num = self.count.get() + 1;
153        self.count.set(num);
154        if num == self.capacity.get() {
155            self.notify();
156        }
157    }
158
159    fn dec(&self) {
160        let num = self.count.get();
161        self.count.set(num - 1);
162        if num == self.capacity.get() {
163            self.notify();
164        }
165    }
166
167    fn notify(&self) {
168        let _wake = OnDropFn::new(|| {
169            let tasks = self.tasks.borrow();
170            for (_, task) in &*tasks {
171                task.wake();
172            }
173        });
174        if let Some(changed) = self.changed.get() {
175            changed.notify();
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use std::time::Duration;
183
184    use super::*;
185    use crate::time::sleep;
186
187    #[ntex::test]
188    async fn test_unavailable_is_woken() {
189        let counter = Counter::new(2);
190        let done = Rc::new(Cell::new(false));
191
192        let (c, d) = (counter.clone(), done.clone());
193        crate::spawn(async move {
194            c.unavailable().await;
195            d.set(true);
196        });
197        sleep(Duration::from_millis(10)).await;
198        assert!(!done.get());
199
200        let _g1 = counter.get();
201        sleep(Duration::from_millis(10)).await;
202        assert!(!done.get());
203
204        let _g2 = counter.get();
205        sleep(Duration::from_millis(10)).await;
206        assert!(done.get());
207    }
208
209    #[ntex::test]
210    async fn test_available_is_woken() {
211        let counter = Counter::new(1);
212        let guard = counter.get();
213        let done = Rc::new(Cell::new(false));
214
215        let (c, d) = (counter.clone(), done.clone());
216        crate::spawn(async move {
217            c.available().await;
218            d.set(true);
219        });
220        sleep(Duration::from_millis(10)).await;
221        assert!(!done.get());
222
223        drop(guard);
224        sleep(Duration::from_millis(10)).await;
225        assert!(done.get());
226    }
227
228    #[ntex::test]
229    async fn test_set_capacity() {
230        let counter = Counter::new(1);
231        let guard = counter.get();
232        assert_eq!(counter.total(), 1);
233        assert!(!counter.is_available());
234
235        let counter2 = counter.clone();
236        let hnd = crate::spawn(async move { counter2.available().await });
237        sleep(Duration::from_millis(10)).await;
238        assert!(!hnd.is_finished());
239
240        // raising the capacity wakes waiters
241        counter.set_capacity(2);
242        assert!(counter.is_available());
243        crate::time::timeout(Duration::from_secs(1), hnd)
244            .await
245            .unwrap()
246            .unwrap();
247
248        drop(guard);
249        assert_eq!(counter.total(), 0);
250    }
251}