vigil/
vigil.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_sync::Mutex;
6use std::fmt::{self, Debug};
7use std::ops::Deref;
8
9use crate::DropWatch;
10
11/// Vigil is a smart pointer that implements DropWatch for any type.
12pub struct Vigil<T: ?Sized> {
13    ptr: Box<VigilInner<T>>,
14}
15
16impl<T: ?Sized> Vigil<T> {
17    fn from_inner(ptr: Box<VigilInner<T>>) -> Self {
18        Self { ptr }
19    }
20
21    fn inner(&self) -> &VigilInner<T> {
22        &self.ptr
23    }
24}
25
26impl<T> Vigil<T> {
27    pub fn new(data: T) -> Vigil<T> {
28        let x = Box::new(VigilInner { observers: Mutex::new(Vec::new()), data });
29        Self::from_inner(x)
30    }
31}
32
33impl<T: ?Sized> DropWatch<T> for Vigil<T> {
34    fn watch(this: &Self, drop_fn: impl FnOnce(&T) + 'static) {
35        this.inner().observers.lock().push(Box::new(drop_fn));
36    }
37}
38
39#[derive(Default)]
40struct VigilInner<T: ?Sized> {
41    observers: Mutex<Vec<Box<dyn FnOnce(&T)>>>,
42    data: T,
43}
44
45impl<T: ?Sized> Drop for Vigil<T> {
46    fn drop(&mut self) {
47        for rite in self.inner().observers.lock().drain(..) {
48            (rite)(&**self);
49        }
50    }
51}
52
53impl<T: ?Sized> Deref for Vigil<T> {
54    type Target = T;
55
56    fn deref(&self) -> &Self::Target {
57        &self.inner().data
58    }
59}
60
61impl<T: fuchsia_inspect_derive::Unit> fuchsia_inspect_derive::Unit for Vigil<T> {
62    type Data = T::Data;
63    fn inspect_create(&self, parent: &fuchsia_inspect::Node, name: impl AsRef<str>) -> Self::Data {
64        self.inner().data.inspect_create(parent, name)
65    }
66
67    fn inspect_update(&self, data: &mut Self::Data) {
68        self.inner().data.inspect_update(data)
69    }
70}
71
72impl<T: Debug> Debug for Vigil<T> {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "Vigil({:?})", self.inner().data)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use std::rc::Rc;
81    use std::sync::atomic::{AtomicUsize, Ordering};
82
83    use super::*;
84
85    #[test]
86    fn rites_upon_drop() {
87        let ghosts = Rc::new(Mutex::new(String::new()));
88
89        let v = Vigil::new('👻');
90
91        Vigil::watch(&v, {
92            let ghosts = ghosts.clone();
93            move |thing| ghosts.lock().push(thing.clone())
94        });
95
96        Vigil::watch(&v, {
97            let ghosts = ghosts.clone();
98            move |thing| ghosts.lock().push(thing.clone())
99        });
100
101        drop(v);
102
103        // Dropping the vigil should have dropped all the Rc clones.
104        let unwrapped = Rc::try_unwrap(ghosts).unwrap();
105        let ghosts_result = unwrapped.into_inner();
106
107        assert_eq!("👻👻", ghosts_result);
108    }
109    struct DropIncrementer(Rc<AtomicUsize>);
110
111    impl Drop for DropIncrementer {
112        fn drop(&mut self) {
113            let _ = self.0.fetch_add(1, Ordering::Relaxed);
114        }
115    }
116
117    #[test]
118    fn drops_stored_data() {
119        let shared_count = Rc::new(AtomicUsize::new(0));
120        let v = Vigil::new(DropIncrementer(shared_count.clone()));
121        drop(v);
122        assert_eq!(1, shared_count.load(Ordering::Relaxed));
123    }
124}