Skip to main content

census/
lib.rs

1//! # Census
2//!
3//! Census' `Inventory`  makes it possible to track a set of living items of a specific type.
4//! Items are automatically removed from the `Inventory<T>` as the living item's are dropped.
5//!
6//! ```rust
7//! use census::{Inventory, TrackedObject};
8//!
9//! let inventory = Inventory::new();
10//!
11//! //  Each object tracked needs to be registered explicitely in the Inventory.
12//! //  A `TrackedObject<T>` wrapper is then returned.
13//! let one = inventory.track("one".to_string());
14//! let two = inventory.track("two".to_string());
15//!
16//! // A snapshot  of the list of living instances can be obtained...
17//! // (no guarantee on their order)
18//! let living_instances: Vec<TrackedObject<String>> = inventory.list();
19//! assert_eq!(living_instances.len(), 2);
20//! ```
21
22use std::borrow::Borrow;
23use std::fmt;
24use std::ops::Deref;
25
26use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
27
28use std::fmt::{Error, Formatter};
29
30struct Items<T> {
31    alive_count: usize,
32    items: Vec<Weak<InnerTrackedObject<T>>>,
33}
34
35impl<T> Default for Items<T> {
36    fn default() -> Self {
37        Items {
38            alive_count: 0,
39            items: Vec::new(),
40        }
41    }
42}
43
44impl<T> Items<T> {
45    fn record_birth(&mut self) {
46        self.alive_count += 1;
47    }
48
49    fn record_death(&mut self) {
50        self.alive_count -= 1;
51    }
52
53    fn len(&mut self) -> usize {
54        self.alive_count()
55    }
56
57    fn list_arc(&mut self) -> Vec<TrackedObject<T>> {
58        self.items
59            .iter()
60            .flat_map(|weak| weak.upgrade())
61            .map(|v| TrackedObject { inner: v })
62            .collect()
63    }
64
65    fn gc_if_needed(&mut self) {
66        if !self.should_gc() {
67            return;
68        }
69        let mut i = 0;
70        while i < self.items.len() {
71            let should_remove = self.items[i].strong_count() == 0;
72            if should_remove {
73                self.items.swap_remove(i);
74            } else {
75                i += 1;
76            }
77        }
78    }
79
80    fn alive_count(&self) -> usize {
81        self.alive_count
82    }
83
84    fn should_gc(&self) -> bool {
85        self.alive_count * 2 <= self.items.len()
86    }
87}
88
89struct InnerInventory<T> {
90    items: Mutex<Items<T>>,
91    condvar: Condvar,
92}
93
94/// The `Inventory` register and keeps track of all of the objects alive.
95pub struct Inventory<T> {
96    inner: Arc<InnerInventory<T>>,
97}
98
99impl<T> Default for Inventory<T> {
100    fn default() -> Self {
101        Inventory {
102            inner: Arc::new(InnerInventory {
103                items: Mutex::new(Items::default()),
104                condvar: Condvar::new(),
105            }),
106        }
107    }
108}
109
110impl<T> Clone for Inventory<T> {
111    fn clone(&self) -> Self {
112        Inventory {
113            inner: self.inner.clone(),
114        }
115    }
116}
117
118impl<T> Inventory<T> {
119    /// Creates a new inventory.
120    pub fn new() -> Inventory<T> {
121        Inventory::default()
122    }
123
124    fn lock_items(&self) -> MutexGuard<Items<T>> {
125        let mut guard = self.inner.items.lock().unwrap();
126        guard.gc_if_needed();
127        guard
128    }
129
130    /// Returns the number of tracked object.
131    pub fn len(&self) -> usize {
132        self.lock_items().len()
133    }
134
135    /// Takes a snapshot of the list of tracked object.
136    ///
137    /// Note that the list is a simple `Vec` of tracked object.
138    /// As a result, it is a consistent snapshot of the
139    /// list of living instance at the time of the call,
140    ///
141    /// Obviously, instances may have been created after the call.
142    /// They will obviously not appear in the snapshot.
143    ///
144    /// ```rust
145    /// use census::{Inventory, TrackedObject};
146    ///
147    /// let inventory = Inventory::new();
148    ///
149    /// let one = inventory.track("one".to_string());
150    /// let living_instances: Vec<TrackedObject<String>> = inventory.list();
151    /// let two = inventory.track("two".to_string());
152    ///
153    /// // our snapshot is a bit old.
154    /// assert_eq!(living_instances.len(), 1);
155    ///
156    /// // a fresher snapshot would contain our new element.
157    /// assert_eq!(inventory.list().len(), 2);
158    /// ```
159    ///
160    /// Also, the instance in the snapshot itself
161    /// are considered "living".
162    ///
163    /// As a result, as long as a snapshot is not dropped,
164    /// all of its instances will be part of the inventory.
165    ///
166    /// ```rust
167    /// # use census::{Inventory, TrackedObject};
168    ///
169    /// let inventory = Inventory::new();
170    ///
171    /// let one = inventory.track("one".to_string());
172    /// let living_instances: Vec<TrackedObject<String>> = inventory.list();
173    ///
174    /// // let's drop one here
175    /// drop(one);
176    ///
177    /// // The instance is technically still in the inventory
178    /// // as our previous snapshot is extending its life...
179    /// assert_eq!(inventory.list().len(), 1);
180    ///
181    /// // If we drop our previous snapshot however...
182    /// drop(living_instances);
183    ///
184    /// // `one` is really untracked.
185    /// assert!(inventory.list().is_empty());
186    /// ```
187    ///
188    pub fn list(&self) -> Vec<TrackedObject<T>> {
189        self.lock_items().list_arc()
190    }
191
192    /// This function blocks until there are no more items in the inventory.
193    ///
194    /// It is a helper calling
195    /// ```ignore
196    /// self.wait_until_predicate(|count| count == 0)
197    /// ```
198    ///
199    /// Note it is very easy to misuse this function and create a deadlock.
200    /// For instance, if any living TrackedObject is on the stack at the moment of the call,
201    /// it will not get dropped, and the inventory cannot become empty.
202    pub fn wait_until_empty(&self) {
203        self.wait_until_predicate(|count| count == 0)
204    }
205
206    /// This function blocks until the number of items in the repository matches a specific
207    /// predicate.
208    ///
209    /// See also `wait_until_empty`.
210    ///
211    /// Note it is very easy to misuse this function and create a deadlock.
212    /// For instance, if any living TrackedObject is on the stack at the moment of the call,
213    /// it will not get dropped, and the inventory cannot become empty.
214    pub fn wait_until_predicate<F: Fn(usize) -> bool>(&self, predicate_on_count: F) {
215        let mut count = self.lock_items();
216        while !predicate_on_count(count.alive_count()) {
217            count = self.inner.condvar.wait(count).unwrap();
218        }
219    }
220
221    /// Starts tracking a given `T` object.
222    pub fn track(&self, item: T) -> TrackedObject<T> {
223        let item_arc = Arc::new(InnerTrackedObject {
224            census: self.clone(),
225            item,
226        });
227        let item_weak = Arc::downgrade(&item_arc);
228        let mut items_lock = self.lock_items();
229        items_lock.items.push(item_weak);
230        items_lock.record_birth();
231        self.inner.condvar.notify_all();
232        TrackedObject { inner: item_arc }
233    }
234}
235
236/// Your tracked object.
237///
238/// A tracked object contains reference counting logic and an
239/// `Arc` to your object. It is cloneable but calling clone will
240/// not clone your internal object.
241///
242/// Your object cannot be mutated. You can borrow it using
243/// the `Deref` interface.
244#[derive(Clone)]
245pub struct TrackedObject<T> {
246    inner: Arc<InnerTrackedObject<T>>,
247}
248
249struct InnerTrackedObject<T> {
250    census: Inventory<T>,
251    item: T,
252}
253
254impl<T: fmt::Debug> fmt::Debug for TrackedObject<T> {
255    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
256        write!(f, "Tracked({:?})", self.inner.item)
257    }
258}
259
260impl<T> TrackedObject<T> {
261    /// Creates a new object from an existing one.
262    ///
263    /// The new object will be registered
264    /// in your original object's inventory.
265    ///
266    /// ```rust
267    /// use census::{Inventory, TrackedObject};
268    ///
269    /// let inventory = Inventory::new();
270    ///
271    /// let seven = inventory.track(7);
272    /// let fourteen = seven.map(|i| i * 2);
273    /// assert_eq!(*fourteen, 14);
274    ///
275    /// let living_instances = inventory.list();
276    /// assert_eq!(living_instances.len(), 2);
277    /// ```
278    pub fn map<F>(&self, f: F) -> TrackedObject<T>
279    where
280        F: FnOnce(&T) -> T,
281    {
282        let t = f(self);
283        self.inner.census.track(t)
284    }
285}
286
287impl<T> Drop for InnerTrackedObject<T> {
288    fn drop(&mut self) {
289        let mut lock = self.census.lock_items();
290        lock.record_death();
291        self.census.inner.condvar.notify_all();
292    }
293}
294
295impl<T> Deref for TrackedObject<T> {
296    type Target = T;
297
298    fn deref(&self) -> &T {
299        &self.inner.item
300    }
301}
302
303impl<T> AsRef<T> for TrackedObject<T> {
304    fn as_ref(&self) -> &T {
305        &self.inner.item
306    }
307}
308
309impl<T> Borrow<T> for TrackedObject<T> {
310    fn borrow(&self) -> &T {
311        &self.inner.item
312    }
313}
314
315#[cfg(test)]
316mod tests {
317
318    use super::Inventory;
319    use std::sync::mpsc::channel;
320    use std::sync::{Arc, Barrier};
321    use std::thread;
322
323    #[test]
324    fn test_census_map() {
325        let census = Inventory::new();
326        let a = census.track(1);
327        let _b = a.map(|v| v * 7);
328        assert_eq!(census.len(), 2);
329        assert_eq!(
330            census.list().into_iter().map(|m| *m).collect::<Vec<_>>(),
331            vec![1, 7]
332        );
333    }
334
335    #[test]
336    fn test_census() {
337        let census = Inventory::new();
338        let _a = census.track(1);
339        let _b = census.track(3);
340        assert_eq!(census.len(), 2,);
341        assert_eq!(
342            census.list().into_iter().map(|m| *m).collect::<Vec<_>>(),
343            vec![1, 3]
344        );
345    }
346
347    #[test]
348    fn test_census_2() {
349        let census = Inventory::new();
350        {
351            let _a = census.track(1);
352            let _b = census.track(3);
353            // dropping both here
354        }
355        assert_eq!(census.len(), 0);
356        assert!(census.list().is_empty());
357    }
358
359    #[test]
360    fn test_census_3() {
361        let census = Inventory::new();
362        let a = census.track(1);
363        let _a2 = a.clone();
364        drop(a);
365        assert_eq!(census.len(), 1);
366        assert_eq!(
367            census.list().into_iter().map(|m| *m).collect::<Vec<_>>(),
368            vec![1]
369        );
370    }
371
372    #[test]
373    fn test_census_list_extends_life() {
374        let census = Inventory::new();
375        let a = census.track(1);
376        let living = census.list();
377        assert_eq!(living.len(), 1);
378        drop(a);
379        let living_2 = census.list();
380        assert_eq!(living_2.len(), 1);
381        drop(living_2);
382        drop(living);
383        assert_eq!(census.len(), 0);
384        assert!(census.list().is_empty());
385    }
386
387    #[test]
388    fn test_census_race_condition() {
389        let census = Inventory::new();
390        let census_clone = census.clone();
391        thread::spawn(move || {
392            for _ in 0..1_000 {
393                let _a = census_clone.track(1);
394            }
395        });
396        for i in 0..10_000 {
397            println!("i {}", i);
398            census.list();
399        }
400    }
401
402    #[test]
403    fn test_census_concurrent_drop() {
404        let census = Inventory::new();
405        let mut senders = Vec::new();
406        let mut handles = Vec::new();
407        let barrier = Arc::new(Barrier::new(2));
408        for _ in 0..2 {
409            let (send, recv) = channel();
410            let barrier = barrier.clone();
411            handles.push(thread::spawn(move || {
412                for obj in recv {
413                    barrier.wait();
414                    drop(obj);
415                }
416            }));
417            senders.push(send);
418        }
419        for i in 0..50_000 {
420            let tracked = census.track(i);
421            for send in &senders {
422                send.send(tracked.clone()).unwrap();
423            }
424        }
425        drop(senders);
426        for handle in handles {
427            handle.join().unwrap();
428        }
429        assert_eq!(census.len(), 0);
430    }
431
432    fn test_census_changes_iter_util(el: usize) {
433        let census = Inventory::new();
434        for i in 0..el {
435            let tracked = census.track(i);
436            thread::spawn(move || {
437                let _tracked = tracked;
438            });
439        }
440        census.wait_until_empty();
441        assert_eq!(census.len(), 0);
442        assert!(census.list().is_empty());
443    }
444
445    #[test]
446    fn test_census_changes_iter_many() {
447        for i in 1..200 {
448            test_census_changes_iter_util(i);
449        }
450    }
451}