use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use moq_net::{Hop, Hops, Pattern, Patterns, origin};
thread_local! {
static ALLOCS: Cell<usize> = const { Cell::new(0) };
}
struct Counting;
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let _ = ALLOCS.try_with(|allocs| allocs.set(allocs.get() + 1));
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static ALLOCATOR: Counting = Counting;
fn peer_route(peer: u64, cost: u64) -> origin::Route {
let mut hops = Hops::new();
hops.push(Hop::new(peer).unwrap()).unwrap();
origin::Route::default().with_hops(hops).with_cost(cost)
}
fn churn_allocs(root: &str, cursors: usize) -> usize {
let (producer, _driver) = origin::Producer::new(origin::Config::default());
let _winner = producer.dynamic("room/a", peer_route(1, 1)).unwrap();
let all = Patterns::from(Pattern::all());
let mut cursors: Vec<_> = (0..cursors)
.map(|_| producer.consume().scope(root, &all).unwrap().announced())
.collect();
for cursor in &mut cursors {
while cursor.try_next().is_some() {}
}
let cycle = || drop(producer.dynamic("room/a", peer_route(2, 5)).unwrap());
for _ in 0..4 {
cycle();
}
let before = ALLOCS.with(Cell::get);
cycle();
let allocs = ALLOCS.with(Cell::get) - before;
for cursor in &mut cursors {
assert!(cursor.try_next().is_none(), "the winner is unchanged");
}
allocs
}
#[test]
fn an_unchanged_winner_allocates_nothing_per_cursor() {
for root in ["", "room", "room/a"] {
let one = churn_allocs(root, 1);
for cursors in [20, 100] {
assert_eq!(
churn_allocs(root, cursors),
one,
"{cursors} cursors rooted at {root:?} allocated more than one"
);
}
}
}