bump-local 0.1.0

A thread-safe bump allocator backed by bumpalo crate.
Documentation
use std::{
    alloc::Layout,
    sync::{Arc, Condvar, Mutex},
    thread::{self, JoinHandle},
};

use bump_local::Bump;

macro_rules! wg_new {
    ($count:expr) => {
        Arc::new((Mutex::new($count), Condvar::new()))
    };
}

macro_rules! wg_done {
    ($wg:ident) => {
        let (lock, cvar) = &*$wg;
        let mut count = lock.lock().unwrap();
        *count -= 1;
        if *count == 0 {
            cvar.notify_all();
        }
        drop(count);
    };
}

macro_rules! wg_wait {
    ($wg:ident) => {
        let (lock, cvar) = &*$wg;
        let mut count = lock.lock().unwrap();
        while *count > 0 {
            count = cvar.wait(count).unwrap();
        }
        drop(count);
    };
}

macro_rules! broadcast_new {
    ($type:ty) => {
        Arc::new((Mutex::new(None::<$type>), Condvar::new()))
    };
}

macro_rules! broadcast_wait {
    ($signal:ident) => {{
        let (lock, cvar) = &*$signal;
        let mut value = lock.lock().unwrap();
        while (*value).is_none() {
            value = cvar.wait(value).unwrap();
        }
        let result = (*value).as_ref().unwrap().clone();
        drop(value);
        result
    }};
}

macro_rules! broadcast_send {
    ($signal:ident, $value:expr) => {
        let (lock, cvar) = &*$signal;
        let mut guard = lock.lock().unwrap();
        *guard = Some($value);
        cvar.notify_all();
        drop(guard);
    };
}

#[test]
fn reset_all() {
    let mut bump = Bump::builder().bump_capacity(100).build();

    let layouts = [
        Layout::new::<i8>(),
        Layout::new::<i16>(),
        Layout::new::<i32>(),
    ];

    let mut threads = Vec::<JoinHandle<()>>::with_capacity(layouts.len());
    let next_bumps = broadcast_new!(Bump);
    let ready_for_next = wg_new!(layouts.len());
    for layout in layouts {
        let next_bumps = next_bumps.clone();
        let ready_for_next = ready_for_next.clone();
        let bump = bump.clone();

        threads.push(thread::spawn(move || {
            let initial_capacity = bump.local().as_inner().chunk_capacity();

            let _ = bump.local().as_inner().alloc_layout(layout);
            let capacity_after_alloc = bump.local().as_inner().chunk_capacity();

            assert!(
                capacity_after_alloc < initial_capacity,
                "Thread 1: first={}, second={}",
                initial_capacity,
                capacity_after_alloc
            );

            drop(bump);
            wg_done!(ready_for_next);

            let bump = broadcast_wait!(next_bumps);
            let capacity_after_reset = bump.local().as_inner().chunk_capacity();
            assert_eq!(capacity_after_reset, initial_capacity);
        }));
    }

    wg_wait!(ready_for_next);
    bump.reset_all().unwrap();

    broadcast_send!(next_bumps, bump);

    for handle in threads {
        handle.join().unwrap()
    }
}

#[test]
fn local_reuse() {
    let bump = Bump::builder().bump_capacity(100).build();

    let layouts = [
        Layout::new::<i8>(),
        Layout::new::<i16>(),
        Layout::new::<i32>(),
    ];

    let mut threads = Vec::<JoinHandle<()>>::with_capacity(layouts.len());
    let next_bumps = broadcast_new!(Bump);
    let ready_for_next = wg_new!(layouts.len());
    for layout in layouts {
        let next_bumps = next_bumps.clone();
        let ready_for_next = ready_for_next.clone();
        let bump = bump.clone();

        threads.push(thread::spawn(move || {
            let initial_capacity = bump.local().as_inner().chunk_capacity();
            let _ = bump.local().as_inner().alloc_layout(layout);
            drop(bump);
            wg_done!(ready_for_next);

            let bump = broadcast_wait!(next_bumps);
            let capacity_after_alloc = bump.local().as_inner().chunk_capacity();
            assert_eq!(capacity_after_alloc, initial_capacity - layout.size());
        }));
    }

    wg_wait!(ready_for_next);
    broadcast_send!(next_bumps, bump);

    for handle in threads {
        handle.join().unwrap()
    }
}