Skip to main content

intrusive_listeners/
lib.rs

1//! A compact reentrant type-erased event-listener registry for `no_std` with `alloc`.
2//!
3//! A [`Registry<T>`](local::Registry) holds an intrusive list of variable-sized
4//! listeners for events of type `T::Event`, linked by thin pointers. Each listener
5//! is registered through a pinned `&Registry`, where it is moved into a single heap
6//! allocation, put into the list, and referred to by a single-word handle.
7//!
8//! The handle is returned as a [`Guard`](local::Guard) by [`register`](local::Registry::register).
9//! It is an RAII scope guard that unregisters its listener when dropped. The
10//! cancellation is scan-free O(1) thanks to the intrusiveness. A guard may
11//! safely outlive its registry, in which case dropping it does nothing.
12//!
13//! Events can be passed to listeners either by value with [`ByVal<T>`](ByVal) (cloned
14//! per listener) or by reference with [`ByRef<T>`](ByRef). You can also implement the
15//! [`EventFamily`] yourself for event types that borrow from the sender's stack.
16//!
17//! Some features of each registry can be selected at compile time via the [`Policy`](locked::Policy) trait.
18//! The library also provides a `Future` adapter that yields the immediate next event
19//! for use by async code, but will not provide a `Stream` adapter since there is no
20//! queue or backpressure.
21//!
22//! # Reentrancy
23//!
24//! This implementation tolerates all kinds of recursive update scenarios.
25//! The expected behaviors are listed below.
26//!
27//! - **Listener cancelling itself in its callback**\
28//!   The in-progress call will run to completion and will be destructed afterwards.
29//! - **Listener cancelling other listeners**\
30//!   Cancelled listeners further down the queue will not receive the in-flight event.
31//! - **Listener calling [`broadcast`](local::Registry::broadcast) again in its callback (recursive notification)**\
32//!   Callbacks for the new event will immediately run inside the nested `broadcast` call.
33//! - **Registering new listeners inside a listener callback**\
34//!   With [`LIFO`] (default) ordering, the new listeners will not receive the in-flight event.
35//!   Otherwise ([`FIFO`]), they will.
36//! - **Accessing the registry in listener's destructor**\
37//!   Listener's destructor may freely register, broadcast, or cancel any listener, including itself.
38//!
39//! # Cleaning up empty registry
40//!
41//! A [`last_listener_cancelled`](local::Policy::last_listener_cancelled) callback can be
42//! provided to a registry: it will be called when cancelling the last listener leaves the
43//! registry empty. It is meant as a clean-up hook: when a registry lives in a parent data
44//! structure, the hook lets you remove the now-unused registry from that structure.
45//! For the multithreaded variant, [`try_seal`](locked::Registry::try_seal) is provided to
46//! atomically confirm it is still empty and disable further registration.
47//!
48//! # Caveats
49//!
50//! - This crate requires a nightly compiler (needed for accessing vtable pointers).
51//! - Does not support panic-unwind. Although unwinding shouldn't cause UB, it will cause leaks and deadlocks.
52
53#![no_std]
54#![feature(ptr_metadata, local_waker, unsafe_pinned)]
55
56use core::marker::PhantomData;
57use core::ptr::null;
58
59extern crate alloc;
60
61pub trait EventFamily {
62    type Event<'a>: Clone
63    where Self: 'a;
64}
65
66pub struct ByVal<T: Clone>(PhantomData<fn(T) -> T>);
67
68impl<T: Clone> Default for ByVal<T> {
69    fn default() -> Self { Self(PhantomData) }
70}
71
72impl<T: Clone> EventFamily for ByVal<T> {
73    type Event<'a>
74        = T
75    where T: 'a;
76}
77
78pub struct ByRef<T>(PhantomData<fn(T) -> T>);
79
80impl<T> Default for ByRef<T> {
81    fn default() -> Self { Self(PhantomData) }
82}
83
84impl<T> EventFamily for ByRef<T> {
85    type Event<'a>
86        = &'a T
87    where T: 'a;
88}
89
90pub trait Listener<T: EventFamily> {
91    fn accept(&self, event: T::Event<'_>);
92}
93
94impl<T: EventFamily, F: for<'a> Fn(T::Event<'a>)> Listener<T> for F {
95    fn accept(&self, event: T::Event<'_>) { self(event) }
96}
97
98mod private {
99    pub trait Private {}
100}
101
102pub struct FIFO;
103pub struct LIFO;
104
105impl private::Private for FIFO {}
106impl private::Private for LIFO {}
107
108pub trait Ordering: private::Private {
109    type Tail: Copy + Default;
110    const FIFO: bool;
111    fn from_tail(x: Self::Tail) -> *const ();
112    fn into_tail(x: *const ()) -> Self::Tail;
113}
114
115impl Ordering for LIFO {
116    type Tail = ();
117    const FIFO: bool = false;
118    fn from_tail((): ()) -> *const () { null() }
119    fn into_tail(_: *const ()) {}
120}
121
122impl Ordering for FIFO {
123    type Tail = *const ();
124    const FIFO: bool = true;
125    fn from_tail(x: *const ()) -> *const () { x }
126    fn into_tail(x: *const ()) -> *const () { x }
127}
128
129// Bits stored in `Node::state`.
130const ALIVE: usize = 1;
131const RECURSIVE_CANCEL: usize = 2;
132const RECURSIVE_VISIT: usize = 4;
133
134/// Single-threaded implementation.
135pub mod local;
136
137#[cfg(feature = "lock_api")]
138/// Thread-safe implementation via a per-registry lock.
139pub mod locked;
140
141// Run tests using:
142// - MIRIFLAGS=-Zmiri-many-seeds cargo miri test
143// - MIRIFLAGS="-Zmiri-tree-borrows -Zmiri-many-seeds" cargo miri test