nylon-ring-host 0.1.4

Dynamic plugin host for the nylon-ring ABI
Documentation
use loom::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use loom::sync::{Arc, Mutex};
use loom::thread;

struct CallGate {
    in_flight: [AtomicUsize; 2],
}

impl CallGate {
    fn try_begin(&self, shard: usize) -> bool {
        const CLOSED: usize = 1 << (usize::BITS - 1);
        self.in_flight[shard]
            .fetch_update(Ordering::Acquire, Ordering::Relaxed, |state| {
                (state & CLOSED == 0).then_some(state + 1)
            })
            .is_ok()
    }

    fn active_calls(&self) -> usize {
        const ACTIVE_MASK: usize = (1 << (usize::BITS - 1)) - 1;
        self.in_flight
            .iter()
            .map(|counter| counter.load(Ordering::Acquire) & ACTIVE_MASK)
            .sum()
    }

    fn stop(&self) {
        const CLOSED: usize = 1 << (usize::BITS - 1);
        for counter in &self.in_flight {
            counter.fetch_or(CLOSED, Ordering::AcqRel);
        }
    }
}

#[test]
fn loom_call_gate_drains_across_unload_race() {
    loom::model(|| {
        let gate = Arc::new(CallGate {
            in_flight: [AtomicUsize::new(0), AtomicUsize::new(0)],
        });
        let caller_gate = gate.clone();
        let caller = thread::spawn(move || caller_gate.try_begin(1));
        let unload_gate = gate.clone();
        let unload = thread::spawn(move || unload_gate.stop());

        let admitted = caller.join().unwrap();
        unload.join().unwrap();
        if admitted {
            gate.in_flight[1].fetch_sub(1, Ordering::Release);
        }
        assert!(!gate.try_begin(0));
        assert!(!gate.try_begin(1));
        assert_eq!(gate.active_calls(), 0);
    });
}

#[test]
fn loom_callback_router_completes_terminal_stream_once() {
    loom::model(|| {
        let pending = Arc::new(Mutex::new(Some(())));
        let completions = Arc::new(AtomicUsize::new(0));
        let handles: Vec<_> = (0..2)
            .map(|_| {
                let pending = pending.clone();
                let completions = completions.clone();
                thread::spawn(move || {
                    if pending.lock().unwrap().take().is_some() {
                        completions.fetch_add(1, Ordering::AcqRel);
                    }
                })
            })
            .collect();
        for handle in handles {
            handle.join().unwrap();
        }
        assert_eq!(completions.load(Ordering::Acquire), 1);
    });
}

#[test]
fn loom_sid_blocks_do_not_overlap() {
    loom::model(|| {
        const BLOCK: u64 = 8;
        let next = Arc::new(AtomicU64::new(1));
        let first_counter = next.clone();
        let first = thread::spawn(move || first_counter.fetch_add(BLOCK, Ordering::Relaxed));
        let second_counter = next.clone();
        let second = thread::spawn(move || second_counter.fetch_add(BLOCK, Ordering::Relaxed));
        let first = first.join().unwrap();
        let second = second.join().unwrap();
        assert_ne!(first, second);
        assert!(first + BLOCK <= second || second + BLOCK <= first);
    });
}