cycle_ptr 0.1.0

Smart pointers, with cycles
//! A test that tries to thrash the Garbage Collector, by spawning a lot of threads that do a lot of operations.
//! The idea is that if there's a race condition in the code, we might find it.
//!
//! (Although this test doesn't test all code paths, it mostly tests the GC task,
//! so it's possible that a race condition can hide elsewhere.)

use cycle_ptr::prelude::*;
use cycle_ptr::sync::{GcMtMemberPtr, GcMtPtr, GenerationRef, Metadata, Weak};
use std::num::NonZero;
use std::sync::{Condvar, Mutex};
use std::thread;

const GENERATIONS: usize = 37;
const SIZE: usize = 100;

struct Xs {
    generations: Vec<GenerationRef>,

    vec_of_x: Mutex<(Vec<GcMtPtr<Mutex<X>>>, usize)>,
    vec_of_x_cnd: Condvar,

    vec_of_weak_x: Mutex<(Vec<Weak<Mutex<X>>>, usize)>,
    vec_of_weak_x_cnd: Condvar,
}

impl Xs {
    fn append(&self, x: GcMtPtr<Mutex<X>>) {
        self.vec_of_weak_x
            .lock()
            .unwrap()
            .0
            .push(GcMtPtr::downgrade(&x));
        self.vec_of_weak_x_cnd.notify_one();

        self.vec_of_x.lock().unwrap().0.push(x);
        self.vec_of_x_cnd.notify_one();
    }
}

impl Default for Xs {
    fn default() -> Self {
        let generations = (0..GENERATIONS).map(|_| GenerationRef::default()).collect();

        Self {
            generations,
            vec_of_x: Mutex::default(),
            vec_of_x_cnd: Condvar::default(),
            vec_of_weak_x: Mutex::default(),
            vec_of_weak_x_cnd: Condvar::default(),
        }
    }
}

struct X {
    gc_metadata: Metadata,
    vec_of_y: Vec<YPtr>,
}

impl X {
    fn new(generation: &GenerationRef) -> GcMtPtr<Mutex<Self>> {
        generation.make(|gc_metadata| {
            Mutex::new(X {
                gc_metadata,
                vec_of_y: Vec::new(),
            })
        })
    }

    fn append(&mut self, y: GcMtPtr<Mutex<Y>>) {
        self.vec_of_y.push(self.gc_metadata.new_pointer(y));
    }
}

struct Y {
    gc_metadata: Metadata,
    vec_of_z: Vec<ZPtr>,
}

impl Y {
    fn new(generation: &GenerationRef) -> GcMtPtr<Mutex<Self>> {
        generation.make(|gc_metadata| {
            Mutex::new(Y {
                gc_metadata,
                vec_of_z: Vec::new(),
            })
        })
    }

    fn append(&mut self, z: GcMtPtr<Mutex<Z>>) {
        self.vec_of_z.push(self.gc_metadata.new_pointer(z));
    }
}

struct Z {
    #[expect(dead_code, reason = "we don't use it, but we just like the symmetry")]
    gc_metadata: Metadata,
}

impl Z {
    fn new(generation: &GenerationRef) -> GcMtPtr<Mutex<Self>> {
        generation.make(|gc_metadata| Mutex::new(Z { gc_metadata }))
    }
}

type YPtr = GcMtMemberPtr<Mutex<Y>>;
type ZPtr = GcMtMemberPtr<Mutex<Z>>;

fn produce_thread(xs: &Xs) {
    for i in 0..SIZE {
        let generation = &xs.generations[i % xs.generations.len()];
        let x = X::new(generation);
        for _ in 0..SIZE {
            let y = Y::new(generation);
            x.lock().unwrap().append(y.clone());
            for _ in 0..SIZE {
                let z = Z::new(generation);
                y.lock().unwrap().append(z);
            }
        }
        xs.append(x);
    }

    {
        let mut vec = xs.vec_of_x.lock().unwrap();
        vec.1 = vec.1.strict_sub(1);
        xs.vec_of_x_cnd.notify_all();
    }
    {
        let mut vec = xs.vec_of_weak_x.lock().unwrap();
        vec.1 = vec.1.strict_sub(1);
        xs.vec_of_weak_x_cnd.notify_all();
    }
    eprintln!("produce {:?} done", thread::current().id());
}

fn consume_thread(xs: &Xs) {
    loop {
        let mut vec_of_x = xs
            .vec_of_x_cnd
            .wait_while(xs.vec_of_x.lock().unwrap(), |vec| {
                vec.0.is_empty() && vec.1 != 0
            })
            .unwrap();
        if let Some(x) = vec_of_x.0.pop() {
            drop(vec_of_x); // release the lock
            drop(x);
        } else if vec_of_x.1 == 0 {
            break;
        }
    }
    eprintln!("consume {:?} done", thread::current().id());
}

fn weak_promote_thread(xs: &Xs) {
    loop {
        let mut vec_of_weak_x = xs
            .vec_of_weak_x_cnd
            .wait_while(xs.vec_of_weak_x.lock().unwrap(), |vec| {
                vec.0.is_empty() && vec.1 != 0
            })
            .unwrap();
        if let Some(weak_x) = vec_of_weak_x.0.pop() {
            drop(vec_of_weak_x); // release the lock
            if let Ok(x) = Weak::upgrade(&weak_x) {
                xs.vec_of_x.lock().unwrap().0.push(x);
                xs.vec_of_x_cnd.notify_one();
            }
        } else if vec_of_weak_x.1 == 0 {
            break;
        }
    }

    {
        let mut vec = xs.vec_of_x.lock().unwrap();
        vec.1 = vec.1.strict_sub(1);
        xs.vec_of_x_cnd.notify_all();
    }
    eprintln!("weak promote {:?} done", thread::current().id());
}

fn main() {
    let num_threads: usize = NonZero::new(
        thread::available_parallelism()
            .map(|x| x.into())
            .unwrap_or(4_usize)
            / 3,
    )
    .map(|x| x.into())
    .unwrap_or(1);

    let xs = Xs::default();
    xs.vec_of_x.lock().unwrap().1 = 2 * num_threads;
    xs.vec_of_weak_x.lock().unwrap().1 = num_threads;

    thread::scope(|scope| {
        for _ in 0..num_threads {
            scope.spawn(|| consume_thread(&xs));
            scope.spawn(|| weak_promote_thread(&xs));
            scope.spawn(|| produce_thread(&xs));
        }
    });
}