use std::cell::Cell;
use std::sync::atomic::{AtomicU64, Ordering};
const SID_BLOCK_SIZE: u64 = 1_000_000;
static GLOBAL_SID: AtomicU64 = AtomicU64::new(1);
#[derive(Copy, Clone)]
struct SidBlock {
base: u64,
offset: u64,
}
thread_local! {
static THREAD_LOCAL_SID_BLOCK: Cell<SidBlock> = const { Cell::new(SidBlock {
base: 0,
offset: SID_BLOCK_SIZE,
}) };
}
pub(crate) fn next_sid() -> u64 {
THREAD_LOCAL_SID_BLOCK.with(|cell| {
let mut block = cell.get();
if block.offset >= SID_BLOCK_SIZE {
let base = GLOBAL_SID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |next| {
next.checked_add(SID_BLOCK_SIZE)
})
.expect("Nylon Ring session ID space exhausted");
block = SidBlock { base, offset: 0 };
}
let sid = block.base + block.offset;
block.offset += 1;
cell.set(block);
sid
})
}
#[cfg(test)]
mod tests {
use super::next_sid;
use std::collections::HashSet;
#[test]
fn session_ids_are_unique_across_threads() {
let handles: Vec<_> = (0..4)
.map(|_| std::thread::spawn(|| (0..1_000).map(|_| next_sid()).collect::<Vec<_>>()))
.collect();
let ids: Vec<_> = handles
.into_iter()
.flat_map(|handle| handle.join().unwrap())
.collect();
let unique: HashSet<_> = ids.iter().copied().collect();
assert_eq!(ids.len(), unique.len());
assert!(!unique.contains(&0));
}
}