#![cfg(loom)]
use loom::sync::Arc;
use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use loom::sync::{Condvar, Mutex};
use loom::thread;
use std::collections::VecDeque;
struct Queue {
shards: Box<[Mutex<VecDeque<u32>>]>,
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<Mutex<VecDeque<u32>>> =
(0..num_shards).map(|_| Mutex::new(VecDeque::new())).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, item: u32) {
let idx = shard_hint & self.mask;
self.shards[idx].lock().unwrap().push_back(item);
if self.parked.load(Ordering::Acquire) > 0 {
let _g = self.park.lock().unwrap();
self.notify.notify_one();
}
}
fn pop_blocking(&self, worker_idx: usize) -> Option<u32> {
let n = self.mask + 1;
let my_shard = worker_idx & self.mask;
loop {
for offset in 0..n {
let idx = (my_shard + offset) & self.mask;
if let Some(r) = self.shards[idx].lock().unwrap().pop_front() {
return Some(r);
}
}
let mut park = self.park.lock().unwrap();
self.parked.fetch_add(1, Ordering::Release);
let mut found = None;
for offset in 0..n {
let idx = (my_shard + offset) & self.mask;
if let Some(r) = self.shards[idx].lock().unwrap().pop_front() {
found = Some(r);
break;
}
}
if let Some(r) = found {
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, 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(1));
});
}
#[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, 7))
};
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(7));
});
}
#[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, 9);
let worker = {
let q = q.clone();
thread::spawn(move || q.pop_blocking(0))
};
q.shutdown();
let got = worker.join().unwrap();
assert_eq!(got, Some(9));
});
}