Skip to main content

ntex_util/channel/
condition.rs

1use std::{cell, fmt, future::Future, future::poll_fn, pin::Pin, task::Context, task::Poll};
2
3use slab::Slab;
4
5use super::cell::Cell;
6use crate::task::LocalWaker;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub enum ConditionResult<T> {
10    Value(T),
11    /// Condition is completed and locked (no new notifications)
12    Locked,
13    /// Condition object is dropped (no new notifications)
14    Dropped,
15}
16
17#[derive(Copy, Clone, PartialEq, Eq, Debug)]
18enum State {
19    Normal,
20    Locked,
21    Dropped,
22}
23
24/// Condition allows to notify multiple waiters at the same time
25pub struct Condition<T = ()> {
26    inner: Cell<Inner<T>>,
27}
28
29/// Waits for result from condition
30pub struct Waiter<T = ()> {
31    token: usize,
32    inner: Cell<Inner<T>>,
33}
34
35struct Inner<T> {
36    data: Slab<Option<Item<T>>>,
37    count: usize,
38    state: State,
39}
40
41struct Item<T> {
42    val: cell::Cell<ConditionResult<T>>,
43    waker: LocalWaker,
44}
45
46impl Default for Condition<()> {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl<T> Clone for Condition<T> {
53    fn clone(&self) -> Self {
54        let inner = self.inner.clone();
55        inner.get_mut().count += 1;
56        Self { inner }
57    }
58}
59
60impl<T> fmt::Debug for Condition<T> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.debug_struct("Condition")
63            .field("state", &self.inner.get_ref().state)
64            .finish()
65    }
66}
67
68impl<T> Condition<T> {
69    /// Construct new condition instance
70    pub fn new() -> Condition<T> {
71        Condition {
72            inner: Cell::new(Inner {
73                data: Slab::new(),
74                count: 1,
75                state: State::Normal,
76            }),
77        }
78    }
79}
80
81impl<T: Clone> Condition<T> {
82    /// Get condition waiter
83    pub fn wait(&self) -> Waiter<T> {
84        let token = self.inner.get_mut().data.insert(None);
85        Waiter {
86            token,
87            inner: self.inner.clone(),
88        }
89    }
90
91    /// Notify all waiters
92    pub fn notify(&self, val: T) {
93        let inner = self.inner.get_ref();
94        for (_, item) in &inner.data {
95            if let Some(item) = item
96                && item.waker.wake_checked()
97            {
98                item.val.set(ConditionResult::Value(val.clone()));
99            }
100        }
101    }
102
103    /// Notify all waiters.
104    ///
105    /// All subsequent waiter readiness checks always returns `Locked`
106    pub fn notify_and_lock(&self, val: T) {
107        self.inner.get_mut().state = State::Locked;
108        self.notify(val);
109    }
110}
111
112impl<T: Default> Condition<T> {
113    /// Notify all waiters
114    pub fn notify_default(&self) {
115        let inner = self.inner.get_ref();
116        for (_, item) in &inner.data {
117            if let Some(item) = item
118                && item.waker.wake_checked()
119            {
120                item.val.set(ConditionResult::Value(T::default()));
121            }
122        }
123    }
124}
125
126impl<T> Drop for Condition<T> {
127    fn drop(&mut self) {
128        let inner = self.inner.get_mut();
129        inner.count -= 1;
130        if inner.count == 0 {
131            inner.state = State::Dropped;
132            for (_, item) in &inner.data {
133                if let Some(item) = item
134                    && item.waker.wake_checked()
135                {
136                    item.val.set(ConditionResult::Dropped);
137                }
138            }
139        }
140    }
141}
142
143impl<T> Waiter<T> {
144    /// Returns readiness state of the condition.
145    pub async fn ready(&self) -> ConditionResult<T> {
146        poll_fn(|cx| self.poll_ready(cx)).await
147    }
148
149    /// Returns readiness state of the condition.
150    pub fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<ConditionResult<T>> {
151        let parent = self.inner.get_mut();
152        let inner = unsafe { parent.data.get_unchecked_mut(self.token) };
153
154        if inner.is_none() {
155            if parent.state == State::Normal {
156                let waker = LocalWaker::default();
157                waker.register(cx.waker());
158                *inner = Some(Item {
159                    waker,
160                    val: cell::Cell::new(ConditionResult::Locked),
161                });
162                return Poll::Pending;
163            }
164        } else {
165            let item = inner.as_mut().unwrap();
166            if !item.waker.register(cx.waker()) {
167                return Poll::Ready(item.val.replace(ConditionResult::Locked));
168            }
169        }
170
171        match parent.state {
172            State::Normal => Poll::Pending,
173            State::Locked => Poll::Ready(ConditionResult::Locked),
174            State::Dropped => Poll::Ready(ConditionResult::Dropped),
175        }
176    }
177}
178
179impl<T> Clone for Waiter<T> {
180    fn clone(&self) -> Self {
181        let token = self.inner.get_mut().data.insert(None);
182        Waiter {
183            token,
184            inner: self.inner.clone(),
185        }
186    }
187}
188
189impl<T: Default> Future for Waiter<T> {
190    type Output = ConditionResult<T>;
191
192    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
193        self.get_mut().poll_ready(cx)
194    }
195}
196
197impl<T: Default> fmt::Debug for Waiter<T> {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        f.debug_struct("Waiter").finish()
200    }
201}
202
203impl<T> Drop for Waiter<T> {
204    fn drop(&mut self) {
205        self.inner.get_mut().data.remove(self.token);
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::future::lazy;
213
214    #[ntex::test]
215    #[allow(clippy::unit_cmp)]
216    async fn test_condition() {
217        let cond = Condition::<()>::new();
218        let mut waiter = cond.wait();
219        assert_eq!(
220            lazy(|cx| Pin::new(&mut waiter).poll(cx)).await,
221            Poll::Pending
222        );
223        cond.notify_default();
224        assert!(format!("{cond:?}").contains("Condition"));
225        assert!(format!("{waiter:?}").contains("Waiter"));
226        assert_eq!(waiter.await, ConditionResult::Value(()));
227
228        let mut waiter = cond.wait();
229        assert_eq!(
230            lazy(|cx| Pin::new(&mut waiter).poll(cx)).await,
231            Poll::Pending
232        );
233        let mut waiter2 = waiter.clone();
234        assert_eq!(
235            lazy(|cx| Pin::new(&mut waiter2).poll(cx)).await,
236            Poll::Pending
237        );
238
239        drop(cond);
240        assert_eq!(waiter.await, ConditionResult::Dropped);
241        assert_eq!(waiter2.await, ConditionResult::Dropped);
242    }
243
244    #[ntex::test]
245    async fn test_condition_poll() {
246        let cond = Condition::default().clone();
247        let waiter = cond.wait();
248        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
249        cond.notify_default();
250        waiter.ready().await;
251
252        let waiter2 = waiter.clone();
253        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
254        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
255        assert_eq!(lazy(|cx| waiter2.poll_ready(cx)).await, Poll::Pending);
256        assert_eq!(lazy(|cx| waiter2.poll_ready(cx)).await, Poll::Pending);
257
258        drop(cond);
259        assert_eq!(
260            lazy(|cx| waiter.poll_ready(cx)).await,
261            Poll::Ready(ConditionResult::Dropped)
262        );
263        assert_eq!(
264            lazy(|cx| waiter.poll_ready(cx)).await,
265            Poll::Ready(ConditionResult::Dropped)
266        );
267        assert_eq!(
268            lazy(|cx| waiter2.poll_ready(cx)).await,
269            Poll::Ready(ConditionResult::Dropped)
270        );
271        assert_eq!(
272            lazy(|cx| waiter2.poll_ready(cx)).await,
273            Poll::Ready(ConditionResult::Dropped)
274        );
275    }
276
277    #[ntex::test]
278    async fn test_condition_with() {
279        let cond = Condition::<String>::new();
280        let waiter = cond.wait();
281        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
282        cond.notify("TEST".into());
283        assert_eq!(
284            waiter.ready().await,
285            ConditionResult::Value("TEST".to_string())
286        );
287
288        let waiter2 = waiter.clone();
289        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
290        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
291        assert_eq!(lazy(|cx| waiter2.poll_ready(cx)).await, Poll::Pending);
292        assert_eq!(lazy(|cx| waiter2.poll_ready(cx)).await, Poll::Pending);
293
294        drop(cond);
295        assert_eq!(
296            lazy(|cx| waiter.poll_ready(cx)).await,
297            Poll::Ready(ConditionResult::Dropped)
298        );
299        assert_eq!(
300            lazy(|cx| waiter.poll_ready(cx)).await,
301            Poll::Ready(ConditionResult::Dropped)
302        );
303        assert_eq!(
304            lazy(|cx| waiter2.poll_ready(cx)).await,
305            Poll::Ready(ConditionResult::Dropped)
306        );
307        assert_eq!(
308            lazy(|cx| waiter2.poll_ready(cx)).await,
309            Poll::Ready(ConditionResult::Dropped)
310        );
311    }
312
313    #[ntex::test]
314    async fn notify_ready() {
315        let cond = Condition::default().clone();
316        let waiter = cond.wait();
317        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
318
319        cond.notify_and_lock(());
320        assert_eq!(
321            lazy(|cx| waiter.poll_ready(cx)).await,
322            Poll::Ready(ConditionResult::Value(()))
323        );
324        assert_eq!(
325            lazy(|cx| waiter.poll_ready(cx)).await,
326            Poll::Ready(ConditionResult::Locked)
327        );
328        assert_eq!(
329            lazy(|cx| waiter.poll_ready(cx)).await,
330            Poll::Ready(ConditionResult::Locked)
331        );
332
333        let waiter2 = cond.wait();
334        assert_eq!(
335            lazy(|cx| waiter2.poll_ready(cx)).await,
336            Poll::Ready(ConditionResult::Locked)
337        );
338    }
339
340    #[ntex::test]
341    async fn notify_with_and_lock_ready() {
342        // with
343        let cond = Condition::<String>::new();
344        let waiter = cond.wait();
345        let waiter2 = cond.wait();
346        assert_eq!(lazy(|cx| waiter.poll_ready(cx)).await, Poll::Pending);
347        assert_eq!(lazy(|cx| waiter2.poll_ready(cx)).await, Poll::Pending);
348
349        cond.notify_and_lock("TEST".into());
350        assert_eq!(
351            lazy(|cx| waiter.poll_ready(cx)).await,
352            Poll::Ready(ConditionResult::Value("TEST".into()))
353        );
354        assert_eq!(
355            lazy(|cx| waiter.poll_ready(cx)).await,
356            Poll::Ready(ConditionResult::Locked)
357        );
358        assert_eq!(
359            lazy(|cx| waiter.poll_ready(cx)).await,
360            Poll::Ready(ConditionResult::Locked)
361        );
362        assert_eq!(
363            lazy(|cx| waiter2.poll_ready(cx)).await,
364            Poll::Ready(ConditionResult::Value("TEST".into()))
365        );
366        assert_eq!(
367            lazy(|cx| waiter2.poll_ready(cx)).await,
368            Poll::Ready(ConditionResult::Locked)
369        );
370
371        let waiter2 = cond.wait();
372        assert_eq!(
373            lazy(|cx| waiter2.poll_ready(cx)).await,
374            Poll::Ready(ConditionResult::Locked)
375        );
376    }
377}