1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::fmt::{self, Debug};
use std::ops::Deref;
use std::sync::Mutex;

use crate::DropWatch;

/// Vigil is a smart pointer that implements DropWatch for any type.
pub struct Vigil<T: ?Sized> {
    ptr: Box<VigilInner<T>>,
}

impl<T: ?Sized> Vigil<T> {
    fn from_inner(ptr: Box<VigilInner<T>>) -> Self {
        Self { ptr }
    }

    fn inner(&self) -> &VigilInner<T> {
        &self.ptr
    }
}

impl<T> Vigil<T> {
    pub fn new(data: T) -> Vigil<T> {
        let x = Box::new(VigilInner { observers: Mutex::new(Vec::new()), data });
        Self::from_inner(x)
    }
}

impl<T: ?Sized> DropWatch<T> for Vigil<T> {
    fn watch(this: &Self, drop_fn: impl FnOnce(&T) + 'static) {
        this.inner().observers.lock().unwrap().push(Box::new(drop_fn));
    }
}

#[derive(Default)]
struct VigilInner<T: ?Sized> {
    observers: Mutex<Vec<Box<dyn FnOnce(&T)>>>,
    data: T,
}

impl<T: ?Sized> Drop for Vigil<T> {
    fn drop(&mut self) {
        for rite in self.inner().observers.lock().unwrap().drain(..) {
            (rite)(&**self);
        }
    }
}

impl<T: ?Sized> Deref for Vigil<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.inner().data
    }
}

impl<T: fuchsia_inspect_derive::Unit> fuchsia_inspect_derive::Unit for Vigil<T> {
    type Data = T::Data;
    fn inspect_create(&self, parent: &fuchsia_inspect::Node, name: impl AsRef<str>) -> Self::Data {
        self.inner().data.inspect_create(parent, name)
    }

    fn inspect_update(&self, data: &mut Self::Data) {
        self.inner().data.inspect_update(data)
    }
}

impl<T: Debug> Debug for Vigil<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Vigil({:?})", self.inner().data)
    }
}

#[cfg(test)]
mod tests {
    use std::rc::Rc;
    use std::sync::atomic::{AtomicUsize, Ordering};

    use super::*;

    #[test]
    fn rites_upon_drop() {
        let ghosts = Rc::new(Mutex::new(String::new()));

        let v = Vigil::new('👻');

        Vigil::watch(&v, {
            let ghosts = ghosts.clone();
            move |thing| ghosts.lock().unwrap().push(thing.clone())
        });

        Vigil::watch(&v, {
            let ghosts = ghosts.clone();
            move |thing| ghosts.lock().unwrap().push(thing.clone())
        });

        drop(v);

        // Dropping the vigil should have dropped all the Rc clones.
        let unwrapped = Rc::try_unwrap(ghosts).unwrap();

        let ghosts_result = unwrapped.into_inner().unwrap();

        assert_eq!("👻👻", ghosts_result);
    }
    struct DropIncrementer(Rc<AtomicUsize>);

    impl Drop for DropIncrementer {
        fn drop(&mut self) {
            let _ = self.0.fetch_add(1, Ordering::Relaxed);
        }
    }

    #[test]
    fn drops_stored_data() {
        let shared_count = Rc::new(AtomicUsize::new(0));
        let v = Vigil::new(DropIncrementer(shared_count.clone()));
        drop(v);
        assert_eq!(1, shared_count.load(Ordering::Relaxed));
    }
}