Skip to main content

event_listener/
intrusive.rs

1//! Intrusive linked list-based implementation of `event-listener`.
2//!
3//! This implementation crates an intrusive linked list of listeners. This list
4//! is secured using either a libstd mutex or a critical section.
5
6use crate::notify::{GenericNotify, Internal, Notification};
7use crate::sync::atomic::Ordering;
8use crate::sync::cell::{Cell, UnsafeCell};
9use crate::{RegisterResult, State, TaskRef};
10
11#[cfg(feature = "critical-section")]
12use core::cell::RefCell;
13#[cfg(all(feature = "std", not(feature = "critical-section")))]
14use core::ops::{Deref, DerefMut};
15
16use core::marker::PhantomPinned;
17use core::mem;
18use core::pin::Pin;
19use core::ptr::NonNull;
20
21pub(super) struct List<T>(
22    /// libstd-based implementation uses a normal Mutex to secure the data.
23    #[cfg(all(feature = "std", not(feature = "critical-section")))]
24    crate::sync::Mutex<Inner<T>>,
25    /// Critical-section-based implementation uses a CS cell that wraps a RefCell.
26    #[cfg(feature = "critical-section")]
27    critical_section::Mutex<RefCell<Inner<T>>>,
28    /// Spinlock implementation uses a spinlock.
29    #[cfg(not(any(feature = "std", feature = "critical-section")))]
30    spinlock::Spinlock<Inner<T>>,
31);
32
33struct Inner<T> {
34    /// The head of the linked list.
35    head: Option<NonNull<Link<T>>>,
36
37    /// The tail of the linked list.
38    tail: Option<NonNull<Link<T>>>,
39
40    /// The first unnotified listener.
41    next: Option<NonNull<Link<T>>>,
42
43    /// Total number of listeners.
44    len: usize,
45
46    /// The number of notified listeners.
47    notified: usize,
48}
49
50impl<T> List<T> {
51    /// Create a new, empty event listener list.
52    pub(super) fn new() -> Self {
53        let inner = Inner {
54            head: None,
55            tail: None,
56            next: None,
57            len: 0,
58            notified: 0,
59        };
60
61        #[cfg(feature = "critical-section")]
62        {
63            Self(critical_section::Mutex::new(RefCell::new(inner)))
64        }
65
66        #[cfg(all(not(feature = "critical-section"), feature = "std"))]
67        {
68            Self(crate::sync::Mutex::new(inner))
69        }
70
71        #[cfg(not(any(feature = "std", feature = "critical-section")))]
72        {
73            Self(spinlock::Spinlock::new(inner))
74        }
75    }
76
77    /// Get the total number of listeners without blocking.
78    #[cfg(all(feature = "std", not(feature = "critical-section")))]
79    pub(crate) fn try_total_listeners(&self) -> Option<usize> {
80        self.0.try_lock().ok().map(|list| list.len)
81    }
82
83    /// Get the total number of listeners without blocking.
84    #[cfg(feature = "critical-section")]
85    pub(crate) fn try_total_listeners(&self) -> Option<usize> {
86        Some(self.total_listeners())
87    }
88
89    /// Get the total number of listeners without blocking.
90    #[cfg(not(any(feature = "std", feature = "critical-section")))]
91    pub(crate) fn try_total_listeners(&self) -> Option<usize> {
92        self.0.try_with(|list| list.len)
93    }
94
95    /// Get the total number of listeners with blocking.
96    #[cfg(all(feature = "std", not(feature = "critical-section")))]
97    pub(crate) fn total_listeners(&self) -> usize {
98        self.0.lock().unwrap_or_else(|e| e.into_inner()).len
99    }
100
101    /// Get the total number of listeners with blocking.
102    #[cfg(feature = "critical-section")]
103    #[allow(unused)]
104    pub(crate) fn total_listeners(&self) -> usize {
105        critical_section::with(|cs| self.0.borrow(cs).borrow().len)
106    }
107}
108
109impl<T> crate::Inner<T> {
110    #[cfg(all(feature = "std", not(feature = "critical-section")))]
111    fn with_inner<R>(&self, f: impl FnOnce(&mut Inner<T>) -> R) -> R {
112        struct ListLock<'a, 'b, T> {
113            lock: crate::sync::MutexGuard<'a, Inner<T>>,
114            inner: &'b crate::Inner<T>,
115        }
116
117        impl<T> Deref for ListLock<'_, '_, T> {
118            type Target = Inner<T>;
119
120            fn deref(&self) -> &Self::Target {
121                &self.lock
122            }
123        }
124
125        impl<T> DerefMut for ListLock<'_, '_, T> {
126            fn deref_mut(&mut self) -> &mut Self::Target {
127                &mut self.lock
128            }
129        }
130
131        impl<T> Drop for ListLock<'_, '_, T> {
132            fn drop(&mut self) {
133                update_notified(&self.inner.notified, &self.lock);
134            }
135        }
136
137        let mut list = ListLock {
138            inner: self,
139            lock: self.list.0.lock().unwrap_or_else(|e| e.into_inner()),
140        };
141        f(&mut list)
142    }
143
144    #[cfg(any(not(feature = "std"), feature = "critical-section"))]
145    fn with_inner<R>(&self, f: impl FnOnce(&mut Inner<T>) -> R) -> R {
146        struct ListWrapper<'a, T> {
147            inner: &'a crate::Inner<T>,
148            list: &'a mut Inner<T>,
149        }
150
151        impl<T> Drop for ListWrapper<'_, T> {
152            fn drop(&mut self) {
153                update_notified(&self.inner.notified, self.list);
154            }
155        }
156
157        #[cfg(feature = "critical-section")]
158        return critical_section::with(move |cs| {
159            let mut list = self.list.0.borrow_ref_mut(cs);
160            let wrapper = ListWrapper {
161                inner: self,
162                list: &mut *list,
163            };
164
165            f(wrapper.list)
166        });
167
168        #[cfg(not(feature = "critical-section"))]
169        self.list.0.with(move |list| {
170            let wrapper = ListWrapper {
171                inner: self,
172                list: &mut *list,
173            };
174
175            f(wrapper.list)
176        })
177    }
178
179    /// Add a new listener to the list.
180    pub(crate) fn insert(&self, mut listener: Pin<&mut Option<Listener<T>>>) {
181        self.with_inner(|inner| {
182            listener.as_mut().set(Some(Listener {
183                link: UnsafeCell::new(Link {
184                    state: Cell::new(State::Created),
185                    prev: Cell::new(inner.tail),
186                    next: Cell::new(None),
187                }),
188                _pin: PhantomPinned,
189            }));
190            let listener = listener.as_pin_mut().unwrap();
191
192            {
193                let entry_guard = listener.link.get();
194                // SAFETY: We are locked, so we can access the inner `link`.
195                let entry = unsafe { entry_guard.deref() };
196
197                // Replace the tail with the new entry.
198                match inner.tail.replace(entry.into()) {
199                    None => inner.head = Some(entry.into()),
200                    Some(t) => unsafe { t.as_ref().next.set(Some(entry.into())) },
201                };
202            }
203
204            // If there are no unnotified entries, this is the first one.
205            if inner.next.is_none() {
206                inner.next = inner.tail;
207            }
208
209            // Bump the entry count.
210            inner.len += 1;
211        });
212    }
213
214    /// Remove a listener from the list.
215    pub(crate) fn remove(
216        &self,
217        listener: Pin<&mut Option<Listener<T>>>,
218        propagate: bool,
219    ) -> Option<State<T>> {
220        self.with_inner(|inner| inner.remove(listener, propagate))
221    }
222
223    /// Notifies a number of entries.
224    #[cold]
225    pub(crate) fn notify(&self, notify: impl Notification<Tag = T>) -> usize {
226        self.with_inner(|inner| inner.notify(notify))
227    }
228
229    /// Register a task to be notified when the event is triggered.
230    ///
231    /// Returns `true` if the listener was already notified, and `false` otherwise. If the listener
232    /// isn't inserted, returns `None`.
233    pub(crate) fn register(
234        &self,
235        mut listener: Pin<&mut Option<Listener<T>>>,
236        task: TaskRef<'_>,
237    ) -> RegisterResult<T> {
238        self.with_inner(|inner| {
239            let entry_guard = match listener.as_mut().as_pin_mut() {
240                Some(listener) => listener.link.get(),
241                None => return RegisterResult::NeverInserted,
242            };
243            // SAFETY: We are locked, so we can access the inner `link`.
244            let entry = unsafe { entry_guard.deref() };
245
246            // Take out the state and check it.
247            match entry.state.replace(State::NotifiedTaken) {
248                State::Notified { tag, .. } => {
249                    // We have been notified, remove the listener.
250                    inner.remove(listener, false);
251                    RegisterResult::Notified(tag)
252                }
253
254                State::Task(other_task) => {
255                    // Only replace the task if it's different.
256                    entry.state.set(State::Task({
257                        if !task.will_wake(other_task.as_task_ref()) {
258                            task.into_task()
259                        } else {
260                            other_task
261                        }
262                    }));
263
264                    RegisterResult::Registered
265                }
266
267                _ => {
268                    // We have not been notified, register the task.
269                    entry.state.set(State::Task(task.into_task()));
270                    RegisterResult::Registered
271                }
272            }
273        })
274    }
275}
276
277impl<T> Inner<T> {
278    fn remove(
279        &mut self,
280        mut listener: Pin<&mut Option<Listener<T>>>,
281        propagate: bool,
282    ) -> Option<State<T>> {
283        let entry_guard = listener.as_mut().as_pin_mut()?.link.get();
284        let entry = unsafe { entry_guard.deref() };
285
286        let prev = entry.prev.get();
287        let next = entry.next.get();
288
289        // Unlink from the previous entry.
290        match prev {
291            None => self.head = next,
292            Some(p) => unsafe {
293                p.as_ref().next.set(next);
294            },
295        }
296
297        // Unlink from the next entry.
298        match next {
299            None => self.tail = prev,
300            Some(n) => unsafe {
301                n.as_ref().prev.set(prev);
302            },
303        }
304
305        // If this was the first unnotified entry, update the next pointer.
306        if self.next == Some(entry.into()) {
307            self.next = next;
308        }
309
310        // The entry is now fully unlinked, so we can now take it out safely.
311        let entry = unsafe {
312            listener
313                .get_unchecked_mut()
314                .take()
315                .unwrap()
316                .link
317                .into_inner()
318        };
319
320        // This State::Created is immediately dropped and exists as a workaround for the absence of
321        // loom::cell::Cell::into_inner. The intent is `let mut state = entry.state.into_inner();`
322        //
323        // refs: https://github.com/tokio-rs/loom/pull/341
324        let mut state = entry.state.replace(State::Created);
325
326        // Update the notified count.
327        if state.is_notified() {
328            self.notified -= 1;
329
330            if propagate {
331                let state = mem::replace(&mut state, State::NotifiedTaken);
332                if let State::Notified { additional, tag } = state {
333                    let tags = {
334                        let mut tag = Some(tag);
335                        move || tag.take().expect("tag already taken")
336                    };
337                    self.notify(GenericNotify::new(1, additional, tags));
338                }
339            }
340        }
341        self.len -= 1;
342
343        Some(state)
344    }
345
346    #[cold]
347    fn notify(&mut self, mut notify: impl Notification<Tag = T>) -> usize {
348        let mut n = notify.count(Internal::new());
349        let is_additional = notify.is_additional(Internal::new());
350
351        if !is_additional {
352            if n < self.notified {
353                return 0;
354            }
355            n -= self.notified;
356        }
357
358        let original_count = n;
359        while n > 0 {
360            n -= 1;
361
362            // Notify the next entry.
363            match self.next {
364                None => return original_count - n - 1,
365
366                Some(e) => {
367                    // Get the entry and move the pointer forwards.
368                    let entry = unsafe { e.as_ref() };
369                    self.next = entry.next.get();
370
371                    // Set the state to `Notified` and notify.
372                    let tag = notify.next_tag(Internal::new());
373                    if let State::Task(task) = entry.state.replace(State::Notified {
374                        additional: is_additional,
375                        tag,
376                    }) {
377                        task.wake();
378                    }
379
380                    // Bump the notified count.
381                    self.notified += 1;
382                }
383            }
384        }
385
386        original_count - n
387    }
388}
389
390fn update_notified<T>(slot: &crate::sync::atomic::AtomicUsize, list: &Inner<T>) {
391    // Update the notified count.
392    let notified = if list.notified < list.len {
393        list.notified
394    } else {
395        usize::MAX
396    };
397
398    slot.store(notified, Ordering::Release);
399}
400
401pub(crate) struct Listener<T> {
402    /// The inner link in the linked list.
403    ///
404    /// # Safety
405    ///
406    /// This can only be accessed while the central mutex is locked.
407    link: UnsafeCell<Link<T>>,
408
409    /// This listener cannot be moved after being pinned.
410    _pin: PhantomPinned,
411}
412
413struct Link<T> {
414    /// The current state of the listener.
415    state: Cell<State<T>>,
416
417    /// The previous link in the linked list.
418    prev: Cell<Option<NonNull<Link<T>>>>,
419
420    /// The next link in the linked list.
421    next: Cell<Option<NonNull<Link<T>>>>,
422}
423
424/// Dangerous spinlock for when neither std nor critical-section are enabled.
425#[cfg(not(any(feature = "std", feature = "critical-section")))]
426mod spinlock {
427    use crate::sync::atomic::{AtomicBool, Ordering};
428    use crate::sync::cell::UnsafeCell;
429
430    /// A spinlock.
431    pub(super) struct Spinlock<T> {
432        /// Is this spinlock locked?
433        locked: AtomicBool,
434
435        /// The data contained within the lock.
436        data: UnsafeCell<T>,
437    }
438
439    impl<T> Spinlock<T> {
440        /// Create a new [`Spinlock`].
441        #[inline]
442        pub(super) fn new(data: T) -> Self {
443            Self {
444                locked: AtomicBool::new(false),
445                data: UnsafeCell::new(data),
446            }
447        }
448
449        /// Try to acquire a spinlock, run a closure, then release.
450        #[inline]
451        pub(super) fn try_with<R>(&self, f: impl FnOnce(&mut T) -> R) -> Option<R> {
452            if self
453                .locked
454                .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
455                .is_ok()
456            {
457                let _guard = CallOnDrop(|| self.locked.store(false, Ordering::Release));
458                // SAFETY: We have the lock held.
459                Some(f(unsafe { self.data.get().deref_mut() }))
460            } else {
461                None
462            }
463        }
464
465        /// Run an operation with the spinlock held.
466        #[inline]
467        pub(super) fn with<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
468            loop {
469                if self
470                    .locked
471                    .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
472                    .is_ok()
473                {
474                    let _guard = CallOnDrop(|| self.locked.store(false, Ordering::Release));
475                    // SAFETY: We have the lock held.
476                    return f(unsafe { self.data.get().deref_mut() });
477                }
478
479                while self.locked.load(Ordering::Relaxed) {
480                    core::hint::spin_loop();
481                }
482            }
483        }
484    }
485
486    struct CallOnDrop<F: FnMut()>(F);
487    impl<F: FnMut()> Drop for CallOnDrop<F> {
488        #[inline]
489        fn drop(&mut self) {
490            (self.0)();
491        }
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498    use futures_lite::pin;
499
500    #[cfg(target_family = "wasm")]
501    use wasm_bindgen_test::wasm_bindgen_test as test;
502
503    macro_rules! make_listeners {
504        ($($id:ident),*) => {
505            $(
506                let $id = Option::<Listener<()>>::None;
507                pin!($id);
508            )*
509        };
510    }
511
512    #[test]
513    fn insert() {
514        let inner = crate::Inner::new();
515        make_listeners!(listen1, listen2, listen3);
516
517        // Register the listeners.
518        inner.insert(listen1.as_mut());
519        inner.insert(listen2.as_mut());
520        inner.insert(listen3.as_mut());
521
522        assert_eq!(inner.list.try_total_listeners(), Some(3));
523
524        // Remove one.
525        assert_eq!(inner.remove(listen2, false), Some(State::Created));
526        assert_eq!(inner.list.try_total_listeners(), Some(2));
527
528        // Remove another.
529        assert_eq!(inner.remove(listen1, false), Some(State::Created));
530        assert_eq!(inner.list.try_total_listeners(), Some(1));
531    }
532
533    #[test]
534    fn drop_non_notified() {
535        let inner = crate::Inner::new();
536        make_listeners!(listen1, listen2, listen3);
537
538        // Register the listeners.
539        inner.insert(listen1.as_mut());
540        inner.insert(listen2.as_mut());
541        inner.insert(listen3.as_mut());
542
543        // Notify one.
544        inner.notify(GenericNotify::new(1, false, || ()));
545
546        // Remove one.
547        inner.remove(listen3, true);
548
549        // Remove the rest.
550        inner.remove(listen1, true);
551        inner.remove(listen2, true);
552    }
553}