use moirai_utils::cache::CacheAligned;
use std::sync::atomic::{AtomicU64, Ordering};
const BITS_PER_WORD: usize = 64;
pub(super) struct IdleBitset {
words: Box<[CacheAligned<AtomicU64>]>,
}
impl IdleBitset {
pub(super) fn new(worker_count: usize) -> Self {
let word_count = worker_count.div_ceil(BITS_PER_WORD).max(1);
let words = (0..word_count)
.map(|_| CacheAligned::new(AtomicU64::new(0)))
.collect::<Vec<_>>()
.into_boxed_slice();
Self { words }
}
#[inline]
pub(super) fn set(&self, id: usize) {
let (word, bit) = (id / BITS_PER_WORD, 1u64 << (id % BITS_PER_WORD));
self.words[word].fetch_or(bit, Ordering::SeqCst);
}
#[inline]
pub(super) fn clear(&self, id: usize) {
let (word, bit) = (id / BITS_PER_WORD, 1u64 << (id % BITS_PER_WORD));
self.words[word].fetch_and(!bit, Ordering::SeqCst);
}
pub(super) fn claim_one(&self, worker_count: usize) -> Option<usize> {
for (word_index, word) in self.words.iter().enumerate() {
let mut idle = word.load(Ordering::SeqCst);
while idle != 0 {
let bit_index = idle.trailing_zeros() as usize;
let id = word_index * BITS_PER_WORD + bit_index;
if id >= worker_count {
break;
}
let mask = 1u64 << bit_index;
match word.compare_exchange_weak(
idle,
idle & !mask,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => return Some(id),
Err(actual) => idle = actual,
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_bitset_claims_nothing() {
let set = IdleBitset::new(128);
assert_eq!(set.claim_one(128), None);
}
#[test]
fn set_then_claim_returns_id_once() {
let set = IdleBitset::new(128);
set.set(7);
assert_eq!(set.claim_one(128), Some(7));
assert_eq!(set.claim_one(128), None);
}
#[test]
fn clear_removes_bit() {
let set = IdleBitset::new(64);
set.set(3);
set.clear(3);
assert_eq!(set.claim_one(64), None);
}
#[test]
fn addresses_workers_beyond_first_word() {
let set = IdleBitset::new(200);
for &id in &[0usize, 63, 64, 65, 127, 128, 199] {
set.set(id);
}
let mut claimed = Vec::new();
while let Some(id) = set.claim_one(200) {
claimed.push(id);
}
claimed.sort_unstable();
assert_eq!(claimed, vec![0, 63, 64, 65, 127, 128, 199]);
}
#[test]
fn partial_trailing_word_yields_no_phantom_ids() {
let set = IdleBitset::new(70);
set.set(69);
set.words[1].fetch_or(1u64 << 10, Ordering::SeqCst); assert_eq!(set.claim_one(70), Some(69));
assert_eq!(set.claim_one(70), None);
}
#[test]
fn single_worker_uses_one_word() {
let set = IdleBitset::new(1);
assert_eq!(set.words.len(), 1);
set.set(0);
assert_eq!(set.claim_one(1), Some(0));
}
}