1use std::cell::RefCell;
33use std::collections::HashMap;
34use std::fmt;
35use std::rc::Rc;
36use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
37use std::sync::{Arc, Mutex};
38use std::thread::{self, ThreadId};
39
40thread_local! {
41 static WORK: RefCell<HashMap<u64, Rc<dyn Fn()>>> = RefCell::new(HashMap::new());
43}
44
45static NEXT: AtomicU64 = AtomicU64::new(1);
46
47static ABANDONED: Mutex<Vec<(ThreadId, u64)>> = Mutex::new(Vec::new());
50static ANY_ABANDONED: AtomicBool = AtomicBool::new(false);
51
52struct Slot {
54 id: u64,
55 owner: ThreadId,
56}
57
58impl Drop for Slot {
59 fn drop(&mut self) {
60 if thread::current().id() == self.owner {
61 let removed = WORK.try_with(|work| work.borrow_mut().remove(&self.id));
64 drop(removed);
65 } else if let Ok(mut abandoned) = ABANDONED.lock() {
66 abandoned.push((self.owner, self.id));
67 ANY_ABANDONED.store(true, Ordering::Release);
68 }
69 }
70}
71
72fn collect_abandoned() {
74 if !ANY_ABANDONED.load(Ordering::Acquire) {
75 return;
76 }
77 let me = thread::current().id();
78 let mine: Vec<u64> = match ABANDONED.lock() {
79 Ok(mut abandoned) => {
80 let (mine, others): (Vec<_>, Vec<_>) =
81 abandoned.drain(..).partition(|(owner, _)| *owner == me);
82 ANY_ABANDONED.store(!others.is_empty(), Ordering::Release);
83 *abandoned = others;
84 mine.into_iter().map(|(_, id)| id).collect()
85 }
86 Err(_) => return,
87 };
88 let removed: Vec<_> = WORK.with(|work| {
89 let mut work = work.borrow_mut();
90 mine.iter().filter_map(|id| work.remove(id)).collect()
91 });
92 drop(removed);
93}
94
95#[derive(Clone)]
100pub struct Callback {
101 slot: Arc<Slot>,
102}
103
104impl Callback {
105 pub fn new(work: impl Fn() + 'static) -> Self {
108 collect_abandoned();
109 let id = NEXT.fetch_add(1, Ordering::Relaxed);
110 WORK.with(|table| table.borrow_mut().insert(id, Rc::new(work)));
111 Self {
112 slot: Arc::new(Slot {
113 id,
114 owner: thread::current().id(),
115 }),
116 }
117 }
118
119 pub fn call(&self) {
124 collect_abandoned();
125 let work = WORK.with(|table| table.borrow().get(&self.slot.id).cloned());
126 match work {
127 Some(work) => work(),
128 None => debug_assert!(
129 thread::current().id() == self.slot.owner,
130 "a callback was run on a different thread from the one that made it"
131 ),
132 }
133 }
134
135 pub fn ptr_eq(&self, other: &Callback) -> bool {
138 self.slot.id == other.slot.id
139 }
140}
141
142impl<F: Fn() + 'static> From<F> for Callback {
143 fn from(work: F) -> Self {
144 Callback::new(work)
145 }
146}
147
148impl PartialEq for Callback {
149 fn eq(&self, other: &Self) -> bool {
152 self.ptr_eq(other)
153 }
154}
155
156impl fmt::Debug for Callback {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str("Callback(..)")
159 }
160}
161
162pub trait IntoMsg<Msg>: Sized {
180 fn defer(work: impl Fn() -> Self + 'static) -> Msg;
182}
183
184impl<Msg> IntoMsg<Msg> for Msg {
185 fn defer(work: impl Fn() -> Msg + 'static) -> Msg {
186 work()
187 }
188}
189
190impl IntoMsg<Callback> for () {
191 fn defer(work: impl Fn() + 'static) -> Callback {
192 Callback::new(work)
193 }
194}
195
196pub(crate) fn handler1<A, R, Msg>(f: impl Fn(A) -> R + 'static) -> impl Fn(A) -> Msg + 'static
198where
199 A: Clone + 'static,
200 R: IntoMsg<Msg>,
201{
202 let f = Rc::new(f);
203 move |a| {
204 let f = f.clone();
205 R::defer(move || f(a.clone()))
206 }
207}
208
209pub(crate) fn handler2<A, B, R, Msg>(
211 f: impl Fn(A, B) -> R + 'static,
212) -> impl Fn(A, B) -> Msg + 'static
213where
214 A: Clone + 'static,
215 B: Clone + 'static,
216 R: IntoMsg<Msg>,
217{
218 let f = Rc::new(f);
219 move |a, b| {
220 let f = f.clone();
221 R::defer(move || f(a.clone(), b.clone()))
222 }
223}
224
225pub(crate) fn handler4<A, B, C, D, R, Msg>(
227 f: impl Fn(A, B, C, D) -> R + 'static,
228) -> impl Fn(A, B, C, D) -> Msg + 'static
229where
230 A: Clone + 'static,
231 B: Clone + 'static,
232 C: Clone + 'static,
233 D: Clone + 'static,
234 R: IntoMsg<Msg>,
235{
236 let f = Rc::new(f);
237 move |a, b, c, d| {
238 let f = f.clone();
239 R::defer(move || f(a.clone(), b.clone(), c.clone(), d.clone()))
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use std::cell::Cell;
247
248 #[test]
249 fn a_callback_runs_its_work_every_time_and_through_every_clone() {
250 let n = Rc::new(Cell::new(0));
251 let inner = n.clone();
252 let cb = Callback::new(move || inner.set(inner.get() + 1));
253 cb.call();
254 cb.clone().call();
255 assert_eq!(n.get(), 2);
256 }
257
258 #[test]
259 fn a_closure_converts_where_a_callback_is_asked_for() {
260 fn takes(cb: impl Into<Callback>) -> Callback {
261 cb.into()
262 }
263 let hit = Rc::new(Cell::new(false));
264 let flag = hit.clone();
265 takes(move || flag.set(true)).call();
266 assert!(hit.get());
267 let cb = Callback::new(|| {});
269 assert!(takes(cb.clone()).ptr_eq(&cb));
270 }
271
272 #[test]
273 fn equality_is_identity_not_lookalike() {
274 let a = Callback::new(|| {});
275 assert_eq!(a, a.clone());
276 assert_ne!(a, Callback::new(|| {}));
277 }
278
279 #[test]
280 fn a_callback_is_a_message_that_may_cross_threads() {
281 fn message<T: Send + Sync + Clone + 'static>() {}
282 message::<Callback>();
283 }
284
285 #[test]
286 fn the_closure_lives_until_the_last_callback_to_it_goes() {
287 struct Flag(Rc<Cell<bool>>);
288 impl Drop for Flag {
289 fn drop(&mut self) {
290 self.0.set(true);
291 }
292 }
293 let dropped = Rc::new(Cell::new(false));
294 let held = Flag(dropped.clone());
295 let cb = Callback::new(move || {
296 let _ = &held;
297 });
298 let clone = cb.clone();
299 drop(cb);
300 assert!(!dropped.get(), "a clone still holds it");
301 clone.call();
302 drop(clone);
303 assert!(dropped.get(), "and with the last one, it is let go");
304 }
305
306 #[test]
307 fn a_callback_dropped_on_another_thread_is_let_go_by_its_owner() {
308 struct Flag(Rc<Cell<bool>>);
309 impl Drop for Flag {
310 fn drop(&mut self) {
311 self.0.set(true);
312 }
313 }
314 let dropped = Rc::new(Cell::new(false));
315 let held = Flag(dropped.clone());
316 let cb = Callback::new(move || {
317 let _ = &held;
318 });
319 thread::spawn(move || drop(cb)).join().unwrap();
320 assert!(!dropped.get(), "the closure cannot be dropped from there");
321 let _ = Callback::new(|| {});
323 assert!(dropped.get());
324 }
325
326 #[test]
327 #[cfg(debug_assertions)]
328 #[should_panic(expected = "different thread")]
329 fn running_a_callback_elsewhere_is_reported_in_a_debug_build() {
330 let cb = Callback::new(|| {});
331 thread::spawn(move || cb.call())
332 .join()
333 .unwrap_or_else(|e| std::panic::resume_unwind(e));
334 }
335
336 #[test]
337 fn a_callback_that_owns_callbacks_can_be_dropped() {
338 let inner = Callback::new(|| {});
339 let outer = Callback::new(move || inner.call());
340 outer.call();
341 drop(outer);
342 }
343}