#![cfg(loom)]
use loom::sync::Arc;
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering, fence};
use loom::sync::{Condvar, Mutex};
use loom::thread;
struct Queue {
shards: Box<[AtomicUsize]>,
mask: usize,
park: Mutex<()>,
notify: Condvar,
parked: AtomicUsize,
shutdown: AtomicBool,
}
impl Queue {
fn new(num_shards: usize) -> Self {
assert!(num_shards.is_power_of_two() && num_shards >= 1);
let shards: Vec<AtomicUsize> = (0..num_shards).map(|_| AtomicUsize::new(0)).collect();
Self {
shards: shards.into_boxed_slice(),
mask: num_shards - 1,
park: Mutex::new(()),
notify: Condvar::new(),
parked: AtomicUsize::new(0),
shutdown: AtomicBool::new(false),
}
}
fn push(&self, shard_hint: usize) {
let idx = shard_hint & self.mask;
self.shards[idx].fetch_add(1, Ordering::Release);
fence(Ordering::SeqCst);
if self.parked.load(Ordering::Acquire) > 0 {
let _g = self.park.lock().unwrap();
self.notify.notify_one();
}
}
fn try_pop(&self, worker_idx: usize) -> Option<()> {
let n = self.mask + 1;
let my_shard = worker_idx & self.mask;
for offset in 0..n {
let idx = (my_shard + offset) & self.mask;
let mut current = self.shards[idx].load(Ordering::Acquire);
while current > 0 {
match self.shards[idx].compare_exchange(
current,
current - 1,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return Some(()),
Err(c) => current = c,
}
}
}
None
}
fn pop_blocking(&self, worker_idx: usize) -> Option<()> {
loop {
if let Some(r) = self.try_pop(worker_idx) {
return Some(r);
}
let mut park = self.park.lock().unwrap();
self.parked.fetch_add(1, Ordering::Release);
fence(Ordering::SeqCst);
if let Some(r) = self.try_pop(worker_idx) {
self.parked.fetch_sub(1, Ordering::Release);
return Some(r);
}
if self.shutdown.load(Ordering::Acquire) {
self.parked.fetch_sub(1, Ordering::Release);
return None;
}
park = self.notify.wait(park).unwrap();
self.parked.fetch_sub(1, Ordering::Release);
drop(park);
}
}
fn shutdown(&self) {
self.shutdown.store(true, Ordering::Release);
let _g = self.park.lock().unwrap();
self.notify.notify_all();
}
}
#[test]
fn one_push_one_worker_two_shards() {
loom::model(|| {
let q = Arc::new(Queue::new(2));
let producer = {
let q = q.clone();
thread::spawn(move || q.push(0))
};
let worker = {
let q = q.clone();
thread::spawn(move || q.pop_blocking(0))
};
producer.join().unwrap();
let got = worker.join().unwrap();
assert_eq!(got, Some(()));
});
}
#[test]
fn push_to_remote_shard() {
loom::model(|| {
let q = Arc::new(Queue::new(2));
let producer = {
let q = q.clone();
thread::spawn(move || q.push(1))
};
let worker = {
let q = q.clone();
thread::spawn(move || q.pop_blocking(0))
};
producer.join().unwrap();
let got = worker.join().unwrap();
assert_eq!(got, Some(()));
});
}
#[test]
fn shutdown_wakes_parked_worker() {
loom::model(|| {
let q = Arc::new(Queue::new(2));
let worker = {
let q = q.clone();
thread::spawn(move || q.pop_blocking(0))
};
q.shutdown();
let got = worker.join().unwrap();
assert_eq!(got, None);
});
}
#[test]
fn shutdown_drains_pending_item() {
loom::model(|| {
let q = Arc::new(Queue::new(2));
q.push(0);
let worker = {
let q = q.clone();
thread::spawn(move || q.pop_blocking(0))
};
q.shutdown();
let got = worker.join().unwrap();
assert_eq!(got, Some(()));
});
}