use std::borrow::Borrow;
use std::mem;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
struct InnerInventory<T> {
items: Mutex<Vec<TrackedObject<T>>>,
condvar: Condvar,
}
impl<T> Default for InnerInventory<T> {
fn default() -> Self {
InnerInventory {
items: Mutex::new(Vec::new()),
condvar: Condvar::default(),
}
}
}
enum ChangesIteratorState<'a, T> {
Started(MutexGuard<'a, Vec<TrackedObject<T>>>),
NotStarted,
}
struct ChangesIterator<'a, T> {
inventory: &'a InnerInventory<T>,
state: ChangesIteratorState<'a, T>,
items: Vec<TrackedObject<T>>, }
impl<'a, T> ChangesIteratorState<'a, T> {
fn advance(
self,
inventory: &'a InnerInventory<T>,
objs_to_drop: Vec<TrackedObject<T>>,
) -> (ChangesIteratorState<'a, T>, Vec<TrackedObject<T>>) {
match self {
ChangesIteratorState::NotStarted => {
let items_guard = inventory.items.lock().unwrap();
let items_copy = items_guard.clone();
(ChangesIteratorState::Started(items_guard), items_copy)
}
ChangesIteratorState::Started(mut guard) => {
let mut has_changed = false;
for obj in objs_to_drop {
has_changed |= obj.consume(&mut guard);
}
if has_changed {
let items_copy = guard.clone();
(ChangesIteratorState::Started(guard), items_copy)
} else {
guard = inventory.condvar.wait(guard).unwrap();
let items_copy = guard.clone();
(ChangesIteratorState::Started(guard), items_copy)
}
}
}
}
}
impl<'a, T> Iterator for ChangesIterator<'a, T> {
type Item = Vec<TrackedObject<T>>;
fn next(&mut self) -> Option<Self::Item> {
let state = mem::replace(&mut self.state, ChangesIteratorState::NotStarted);
let objs = mem::replace(&mut self.items, vec![]);
let (new_state, items) = state.advance(self.inventory, objs);
self.items = items.clone();
self.state = new_state;
Some(items)
}
}
pub struct Inventory<T> {
inner: Arc<InnerInventory<T>>,
}
impl<T> Default for Inventory<T> {
fn default() -> Self {
Inventory {
inner: Arc::new(InnerInventory::default()),
}
}
}
impl<T> Clone for Inventory<T> {
fn clone(&self) -> Self {
Inventory {
inner: self.inner.clone(),
}
}
}
impl<T> Inventory<T> {
pub fn new() -> Inventory<T> {
Inventory::default()
}
pub fn list(&self) -> Vec<TrackedObject<T>> {
self.inner.items.lock().expect("Lock poisoned").clone()
}
pub fn changes_iter<'a>(&'a self) -> impl 'a + Iterator<Item = Vec<TrackedObject<T>>> {
ChangesIterator {
inventory: &self.inner,
state: ChangesIteratorState::NotStarted,
items: Vec::new(),
}
}
pub fn track(&self, t: T) -> TrackedObject<T> {
let self_clone: Inventory<T> = (*self).clone();
let mut wlock = self
.inner
.items
.lock()
.expect("Inventory lock poisoned on write");
let idx = wlock.len();
let managed_object = TrackedObject {
census: self_clone,
inner: Arc::new(Inner {
val: t,
count: AtomicUsize::new(0),
idx: AtomicUsize::new(idx),
}),
consumed: false,
};
wlock.push(managed_object.clone());
managed_object
}
fn remove_with_lock(
&self,
el: &TrackedObject<T>,
wlock: &mut MutexGuard<Vec<TrackedObject<T>>>,
) {
let ref_count = el.inner.count.load(Ordering::SeqCst);
if ref_count != 0 {
return;
}
let pos = el.index();
if pos + 1 == wlock.len() {
wlock.pop();
} else {
wlock.swap_remove(pos);
wlock[pos].set_index(pos);
}
}
}
impl<T> Drop for TrackedObject<T> {
fn drop(&mut self) {
if self.consumed {
return;
}
let count_before = self.inner.count.fetch_sub(1, Ordering::SeqCst);
if count_before == 1 {
let mut wlock = self
.census
.inner
.items
.lock()
.expect("Inventory lock poisoned on read");
self.census.remove_with_lock(self, &mut wlock);
self.census.inner.condvar.notify_all();
}
}
}
impl<T> Clone for TrackedObject<T> {
fn clone(&self) -> Self {
self.inner.count.fetch_add(1, Ordering::SeqCst);
TrackedObject {
census: self.census.clone(),
inner: self.inner.clone(),
consumed: self.consumed,
}
}
}
impl<T> TrackedObject<T> {
fn consume(mut self, guard: &mut MutexGuard<Vec<TrackedObject<T>>>) -> bool {
self.consumed = true; let count_before = self.inner.count.fetch_sub(1, Ordering::SeqCst);
if count_before > 1 {
return false;
}
self.census.remove_with_lock(&self, guard);
true
}
}
struct Inner<T> {
val: T,
count: AtomicUsize,
idx: AtomicUsize,
}
pub struct TrackedObject<T> {
census: Inventory<T>,
inner: Arc<Inner<T>>,
consumed: bool,
}
impl<T> TrackedObject<T> {
fn index(&self) -> usize {
self.inner.idx.load(Ordering::SeqCst)
}
fn set_index(&self, pos: usize) {
self.inner.idx.store(pos, Ordering::SeqCst);
}
pub fn map<F>(&self, f: F) -> TrackedObject<T>
where
F: FnOnce(&T) -> T,
{
let t = f(&*self);
self.census.track(t)
}
}
impl<T> Deref for TrackedObject<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner.val
}
}
impl<T> AsRef<T> for TrackedObject<T> {
fn as_ref(&self) -> &T {
&self.inner.val
}
}
impl<T> Borrow<T> for TrackedObject<T> {
fn borrow(&self) -> &T {
&self.inner.val
}
}
#[cfg(test)]
mod tests {
use super::Inventory;
use std::thread;
#[test]
fn test_census_map() {
let census = Inventory::new();
let a = census.track(1);
let _b = a.map(|v| v * 7);
assert_eq!(
census.list().into_iter().map(|m| *m).collect::<Vec<_>>(),
vec![1, 7]
);
}
#[test]
fn test_census() {
let census = Inventory::new();
let _a = census.track(1);
let _b = census.track(3);
assert_eq!(
census.list().into_iter().map(|m| *m).collect::<Vec<_>>(),
vec![1, 3]
);
}
#[test]
fn test_census_2() {
let census = Inventory::new();
{
let _a = census.track(1);
let _b = census.track(3);
}
assert!(census.list().is_empty());
}
#[test]
fn test_census_3() {
let census = Inventory::new();
let a = census.track(1);
let _a2 = a.clone();
drop(a);
assert_eq!(
census.list().into_iter().map(|m| *m).collect::<Vec<_>>(),
vec![1]
);
}
#[test]
fn test_census_list_extends_life() {
let census = Inventory::new();
let a = census.track(1);
let living = census.list();
assert_eq!(living.len(), 1);
drop(a);
let living_2 = census.list();
assert_eq!(living_2.len(), 1);
drop(living_2);
drop(living);
assert!(census.list().is_empty());
}
#[test]
fn test_census_race_condition() {
let census = Inventory::new();
let census_clone = census.clone();
thread::spawn(move || {
for _ in 0..1_000 {
let _a = census_clone.track(1);
}
});
for _ in 0..10_000 {
census.list();
}
}
fn test_census_changes_iter_util(el: usize) {
let census = Inventory::new();
for _ in 0..el {
let tracked = census.track(1);
thread::spawn(move || {
let _tracked = tracked;
});
}
for objs in census.changes_iter() {
if objs.len() == 0 {
break;
}
}
}
#[test]
fn test_census_changes_iter() {
for _ in 0..100 {
for i in 1..20 {
test_census_changes_iter_util(i);
}
}
}
}