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}