Skip to main content

listener_holder/
lib.rs

1//! A thread-safe holder for an optional listener (single observer).
2//!
3//! Use this when you have one optional callback/listener that may be set or cleared at runtime,
4//! and you want to notify it from any thread without repeating `RwLock<Option<...>>` boilerplate.
5//!
6//! The implementation uses [arc-swap](https://docs.rs/arc-swap) for a lock-free read path. See
7//! the [CONCURRENCY.md](https://github.com/0barman/listener-holder/blob/main/docs/CONCURRENCY.md) document in the repo for a comparison with `RwLock<Option<Arc<L>>>`.
8//!
9//! # Type requirements
10//!
11//! `L` must be `Send + Sync` so that the holder can be shared across threads. The holder itself
12//! is `Send + Sync` when `L: Send + Sync`.
13//!
14//! # Memory and dropping
15//!
16//! The holder stores at most one `Arc<L>`. Calling [`set`](ListenerHolder::set)(`None`) or
17//! replacing the listener drops the previous `Arc` (reference count decremented). Dropping the
18//! holder drops its current `Arc`. Cloning the holder or [`get`](ListenerHolder::get) clones
19//! the `Arc`; the listener is freed when all `Arc` references are dropped. There is no custom
20//! [`Drop`] and no intentional leak.
21//!
22/// Thread-safe holder for a single optional listener of type `L`.
23///
24/// The listener is stored as `Option<Arc<L>>` in an [ArcSwapOption], so getting and replacing
25/// the listener use atomic operations (lock-free). `L` must be `Send + Sync`; the holder is
26/// `Send + Sync` when `L` is.
27///
28/// # Example
29///
30/// ```rust
31/// use listener_holder::ListenerHolder;
32/// use std::sync::Arc;
33///
34/// struct MyListener;
35/// impl MyListener {
36///     fn on_event(&self, msg: &str) {
37///         println!("event: {}", msg);
38///     }
39/// }
40///
41/// let holder: ListenerHolder<MyListener> = ListenerHolder::new();
42/// holder.set(Some(Arc::new(MyListener)));
43///
44/// holder.with_listener(|l| l.on_event("hello")); // prints "event: hello"
45/// holder.set(None);  // clear
46/// holder.with_listener(|l| l.on_event("ignored")); // no-op
47/// ```
48use arc_swap::ArcSwapOption;
49use std::sync::Arc;
50
51#[derive(Default)]
52pub struct ListenerHolder<L> {
53    inner: ArcSwapOption<L>,
54}
55
56impl<L> ListenerHolder<L>
57where
58    L: Send + Sync,
59{
60    /// Creates a new holder with no listener.
61    pub fn new() -> Self {
62        Self {
63            inner: ArcSwapOption::from(None),
64        }
65    }
66
67    /// Sets or clears the listener. Pass `Some(arc)` to set, `None` to clear.
68    pub fn set(&self, listener: Option<Arc<L>>) {
69        self.inner.store(listener);
70    }
71
72    /// If a listener is present, calls `f` with a reference to it and returns `Some(result)`.
73    /// If no listener is set, returns `None` without calling `f`.
74    ///
75    /// The current listener is loaded with a lock-free atomic load; the closure runs without
76    /// holding any lock. If the closure panics, the holder is left unchanged and other threads
77    /// can continue to use it.
78    pub fn with_listener<F, R>(&self, f: F) -> Option<R>
79    where
80        F: FnOnce(&L) -> R,
81    {
82        let guard = self.inner.load();
83        let arc = guard.as_ref()?;
84        Some(f(arc.as_ref()))
85    }
86
87    /// Returns a clone of the current listener's `Arc`, or `None` if none is set.
88    ///
89    /// The returned `Arc` keeps the listener alive until it is dropped, even if the holder
90    /// is later cleared. Useful when you need to call the listener outside the holder
91    /// (e.g. in an async context).
92    pub fn get(&self) -> Option<Arc<L>> {
93        self.inner.load_full()
94    }
95}
96
97impl<L> std::fmt::Debug for ListenerHolder<L> {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("ListenerHolder").finish_non_exhaustive()
100    }
101}
102
103impl<L> Clone for ListenerHolder<L> {
104    /// Clones the holder; the clone gets a snapshot of the current listener at the time of
105    /// cloning. Subsequent [`set`](ListenerHolder::set) calls on either holder do not affect
106    /// the other.
107    fn clone(&self) -> Self {
108        let current = self.inner.load_full();
109        Self {
110            inner: ArcSwapOption::from(current),
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::sync::atomic::{AtomicU32, Ordering};
119
120    #[derive(Default)]
121    struct Counter {
122        count: AtomicU32,
123    }
124    impl Counter {
125        fn add(&self, n: u32) {
126            self.count.fetch_add(n, Ordering::SeqCst);
127        }
128        fn get(&self) -> u32 {
129            self.count.load(Ordering::SeqCst)
130        }
131    }
132
133    #[test]
134    fn set_and_with_listener() {
135        let holder: ListenerHolder<Counter> = ListenerHolder::new();
136        assert!(holder.with_listener(|c| c.get()).is_none());
137
138        let c = Arc::new(Counter::default());
139        holder.set(Some(c.clone()));
140        holder.with_listener(|l| l.add(1));
141        holder.with_listener(|l| l.add(2));
142        assert_eq!(c.get(), 3);
143
144        holder.set(None);
145        assert!(holder.with_listener(|c| c.get()).is_none());
146        assert_eq!(c.get(), 3);
147    }
148
149    #[test]
150    fn get_returns_clone() {
151        let holder: ListenerHolder<Counter> = ListenerHolder::new();
152        let c = Arc::new(Counter::default());
153        holder.set(Some(c.clone()));
154        let out = holder.get().unwrap();
155        out.add(1);
156        assert_eq!(c.get(), 1);
157    }
158
159    #[test]
160    fn clone_holder_copies_listener() {
161        let a = ListenerHolder::new();
162        a.set(Some(Arc::new(Counter::default())));
163        let b = a.clone();
164        a.set(None);
165        assert!(a.with_listener(|_| ()).is_none());
166        assert!(b.with_listener(|c| c.get()).is_some());
167    }
168
169    #[test]
170    fn with_listener_panic_does_not_poison_holder() {
171        let holder: ListenerHolder<Counter> = ListenerHolder::new();
172        holder.set(Some(Arc::new(Counter::default())));
173        // 临时取消 panic hook,避免预期的 panic 被打印到 stderr
174        let prev = std::panic::take_hook();
175        std::panic::set_hook(Box::new(|_| {}));
176        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
177            holder.with_listener(|_| panic!("test panic"));
178        }));
179        std::panic::set_hook(prev);
180        assert!(result.is_err()); // 确认发生了 panic
181                                  // Holder still usable; listener still set
182        assert!(holder.with_listener(|c| c.get()).is_some());
183    }
184}