use std::collections::{BTreeMap, VecDeque};
use std::sync::atomic::Ordering;
use crate::sync::{spin_until, Arc, AtomicBool, AtomicU64, AtomicUsize, Condvar, Mutex};
pub struct Ticket<T, S> {
pub item: T,
slot: Mutex<Option<S>>,
ready: AtomicBool,
}
struct QueueState<T, S> {
waiting: VecDeque<Arc<Ticket<T, S>>>,
leader_active: bool,
}
pub struct GroupQueue<T, S> {
state: Mutex<QueueState<T, S>>,
cv: Condvar,
queued: AtomicUsize,
leader_busy: AtomicBool,
last_group: AtomicUsize,
}
pub enum Turn<'a, T, S: Clone> {
Followed(S, Arc<Ticket<T, S>>),
Lead(Leadership<'a, T, S>),
}
pub struct Leadership<'a, T, S: Clone> {
queue: &'a GroupQueue<T, S>,
pub group: Vec<Arc<Ticket<T, S>>>,
abandoned: &'a dyn Fn() -> S,
finished: bool,
}
impl<T, S> Default for GroupQueue<T, S> {
fn default() -> Self {
GroupQueue {
state: Mutex::new(QueueState {
waiting: VecDeque::new(),
leader_active: false,
}),
cv: Condvar::new(),
queued: AtomicUsize::new(0),
leader_busy: AtomicBool::new(false),
last_group: AtomicUsize::new(1),
}
}
}
impl<T, S: Clone> GroupQueue<T, S> {
pub fn enter<'a>(
&'a self,
item: T,
gather: bool,
max_bytes: usize,
size: impl Fn(&T) -> usize,
abandoned: &'a dyn Fn() -> S,
) -> Turn<'a, T, S> {
let me = Arc::new(Ticket {
item,
slot: Mutex::new(None),
ready: AtomicBool::new(false),
});
let mut queue = self.state.lock();
queue.waiting.push_back(me.clone());
self.queued.fetch_add(1, Ordering::Relaxed);
let mut spun = false;
loop {
if me.ready.load(Ordering::Acquire) {
let slot = me.slot.lock().take().expect("ready means set");
return Turn::Followed(slot, me);
}
let at_head = queue.waiting.front().is_some_and(|p| Arc::ptr_eq(p, &me));
if at_head && !queue.leader_active {
break;
}
if !spun {
spun = true;
drop(queue);
spin_until(|| {
me.ready.load(Ordering::Acquire) || !self.leader_busy.load(Ordering::Acquire)
});
queue = self.state.lock();
continue;
}
spun = false;
self.cv.wait(&mut queue);
}
queue.leader_active = true;
self.leader_busy.store(true, Ordering::Release);
let want = self.last_group.load(Ordering::Relaxed);
if gather && queue.waiting.len() < want {
drop(queue);
spin_until(|| self.queued.load(Ordering::Acquire) >= want);
queue = self.state.lock();
}
let mut group = Vec::new();
let mut bytes = 0;
while let Some(next) = queue.waiting.front() {
let next_size = size(&next.item);
if !group.is_empty() && bytes + next_size > max_bytes {
break;
}
bytes += next_size;
group.push(queue.waiting.pop_front().expect("front exists"));
}
self.queued.fetch_sub(group.len(), Ordering::Relaxed);
self.last_group.store(group.len(), Ordering::Relaxed);
drop(queue);
debug_assert!(Arc::ptr_eq(&group[0], &me), "the leader heads its group");
Turn::Lead(Leadership {
queue: self,
group,
abandoned,
finished: false,
})
}
}
impl<T, S: Clone> Leadership<'_, T, S> {
pub fn finish(
mut self,
mut slot: impl FnMut(usize, &Ticket<T, S>) -> S,
) -> (S, Vec<Arc<Ticket<T, S>>>) {
let mut mine = None;
let mut queue = self.queue.state.lock();
for (i, ticket) in self.group.iter().enumerate() {
let s = slot(i, ticket);
if i == 0 {
mine = Some(s);
} else {
*ticket.slot.lock() = Some(s);
ticket.ready.store(true, Ordering::Release);
}
}
self.finished = true;
queue.leader_active = false;
self.queue.leader_busy.store(false, Ordering::Release);
drop(queue);
self.queue.cv.notify_all();
(
mine.expect("the leader is in its own group"),
std::mem::take(&mut self.group),
)
}
}
impl<T, S: Clone> Drop for Leadership<'_, T, S> {
fn drop(&mut self) {
if self.finished {
return;
}
let slot = (self.abandoned)();
let mut queue = self.queue.state.lock();
for ticket in &self.group[1..] {
*ticket.slot.lock() = Some(slot.clone());
ticket.ready.store(true, Ordering::Release);
}
queue.leader_active = false;
self.queue.leader_busy.store(false, Ordering::Release);
drop(queue);
self.queue.cv.notify_all();
}
}
pub struct Publisher {
visible: CacheLine<AtomicU64>,
done: Mutex<BTreeMap<u64, u64>>,
cv: Condvar,
}
#[repr(align(128))]
struct CacheLine<T>(T);
impl<T> std::ops::Deref for CacheLine<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl Publisher {
pub fn new(visible: u64) -> Self {
Publisher {
visible: CacheLine(AtomicU64::new(visible)),
done: Mutex::new(BTreeMap::new()),
cv: Condvar::new(),
}
}
pub fn visible(&self) -> u64 {
self.visible.load(Ordering::Acquire)
}
pub fn publish(&self, first: u64, last: u64) {
let mut done = self.done.lock();
done.insert(first, last);
let mut visible = self.visible.load(Ordering::Acquire);
let start = visible;
while let Some(last) = done.remove(&(visible + 1)) {
visible = last;
}
if visible == start {
drop(done);
self.wait(last);
return;
}
self.visible.store(visible, Ordering::Release);
drop(done);
self.cv.notify_all();
}
pub fn wait(&self, seq: u64) {
if spin_until(|| self.visible.load(Ordering::Acquire) >= seq) {
return;
}
let mut done = self.done.lock();
while self.visible.load(Ordering::Acquire) < seq {
self.cv.wait(&mut done);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn groups_finished_out_of_order_become_visible_in_order() {
let p = Arc::new(Publisher::new(0));
let late = {
let p = p.clone();
std::thread::spawn(move || p.publish(3, 4))
};
while p.done.lock().is_empty() {
std::thread::yield_now();
}
assert_eq!(p.visible(), 0, "nothing visible over an unfinished group");
p.publish(1, 2);
late.join().unwrap();
assert_eq!(p.visible(), 4);
}
#[test]
fn a_leader_hands_its_group_their_slots() {
let q: Arc<GroupQueue<u32, u32>> = Arc::new(GroupQueue::default());
let abandoned = || u32::MAX;
let turn = q.enter(7, false, usize::MAX, |_| 1, &abandoned);
let mine = match turn {
Turn::Lead(lead) => {
assert_eq!(lead.group.len(), 1);
lead.finish(|i, t| t.item * 10 + i as u32).0
}
Turn::Followed(..) => panic!("alone, it leads"),
};
assert_eq!(mine, 70);
}
}
#[cfg(all(test, loom))]
mod loom_tests {
use super::*;
use loom::thread;
fn model(f: impl Fn() + Sync + Send + 'static) {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(3);
builder.check(f);
}
#[test]
fn visibility_never_runs_ahead_of_inserts() {
model(|| {
let p = Arc::new(Publisher::new(0));
let inserted: Arc<[AtomicBool; 3]> = Arc::new([
AtomicBool::new(false),
AtomicBool::new(false),
AtomicBool::new(false),
]);
let writers: Vec<_> = (1..=2u64)
.map(|seq| {
let (p, inserted) = (p.clone(), inserted.clone());
thread::spawn(move || {
inserted[seq as usize].store(true, Ordering::Release);
p.publish(seq, seq);
let v = p.visible();
assert!(v >= seq, "returned before its write was visible");
for s in 1..=v {
assert!(
inserted[s as usize].load(Ordering::Acquire),
"saw {s} early"
);
}
})
})
.collect();
let v = p.visible();
for seq in 1..=v {
assert!(
inserted[seq as usize].load(Ordering::Acquire),
"saw {seq} before insert"
);
}
for w in writers {
w.join().unwrap();
}
assert_eq!(p.visible(), 2);
});
}
#[test]
fn one_leader_at_a_time_and_every_item_handled_once() {
model(|| {
let q: Arc<GroupQueue<u32, (u32, u32)>> = Arc::new(GroupQueue::default());
let leading = Arc::new(AtomicBool::new(false));
let handled = Arc::new(Mutex::new(Vec::new()));
let writers: Vec<_> = (0..3u32)
.map(|id| {
let (q, leading, handled) = (q.clone(), leading.clone(), handled.clone());
thread::spawn(move || {
let abandoned = || (u32::MAX, u32::MAX);
let slot = match q.enter(id, false, 2, |_| 1, &abandoned) {
Turn::Lead(lead) => {
assert!(!leading.swap(true, Ordering::AcqRel), "two leaders");
handled.lock().extend(lead.group.iter().map(|t| t.item));
let n = lead.group.len();
lead.finish(|i, t| {
if i + 1 == n {
leading.store(false, Ordering::Release);
}
(t.item, i as u32)
})
.0
}
Turn::Followed(slot, _) => slot,
};
assert_eq!(slot.0, id, "a slot meant for another writer");
})
})
.collect();
for w in writers {
w.join().unwrap();
}
let mut handled = handled.lock().clone();
handled.sort();
assert_eq!(handled, vec![0, 1, 2]);
});
}
#[test]
fn groups_publish_and_a_switch_waits_for_them() {
struct Group {
first: u64,
last: u64,
memtable: u64,
remaining: AtomicUsize,
}
struct Commit {
last_seq: u64,
memtable: u64,
}
model(|| {
let q: Arc<GroupQueue<u64, Option<(Arc<Group>, u64)>>> =
Arc::new(GroupQueue::default());
let p = Arc::new(Publisher::new(0));
let commit = Arc::new(Mutex::new(Commit {
last_seq: 0,
memtable: 0,
}));
let current_memtable = Arc::new(AtomicU64::new(0));
let writers: Vec<_> = (0..2u64)
.map(|_| {
let (q, p, commit, current) = (
q.clone(),
p.clone(),
commit.clone(),
current_memtable.clone(),
);
thread::spawn(move || {
let abandoned = || None;
let slot = match q.enter(1, false, usize::MAX, |_| 1, &abandoned) {
Turn::Lead(lead) => {
let mut c = commit.lock();
if c.last_seq > 0 {
p.wait(c.last_seq);
c.memtable += 1;
current.store(c.memtable, Ordering::Release);
}
let first = c.last_seq + 1;
c.last_seq += lead.group.len() as u64;
let group = Arc::new(Group {
first,
last: c.last_seq,
memtable: c.memtable,
remaining: AtomicUsize::new(lead.group.len()),
});
drop(c);
lead.finish(|i, _| Some((group.clone(), first + i as u64)))
.0
}
Turn::Followed(slot, _) => slot,
};
let (group, seq) = slot.expect("not abandoned");
assert_eq!(group.memtable, current.load(Ordering::Acquire));
assert!(seq <= group.last);
if group.remaining.fetch_sub(1, Ordering::AcqRel) == 1 {
p.publish(group.first, group.last);
} else {
p.wait(group.last);
}
assert!(p.visible() >= seq);
})
})
.collect();
for w in writers {
w.join().unwrap();
}
assert_eq!(p.visible(), 2);
});
}
#[test]
fn gathering_a_group_loses_and_doubles_nothing() {
model(|| {
let q: Arc<GroupQueue<u32, u32>> = Arc::new(GroupQueue::default());
q.last_group.store(2, Ordering::Relaxed);
let handled = Arc::new(Mutex::new(Vec::new()));
let writers: Vec<_> = (0..3u32)
.map(|id| {
let (q, handled) = (q.clone(), handled.clone());
thread::spawn(move || {
let abandoned = || u32::MAX;
let turn = q.enter(id, true, usize::MAX, |_| 1, &abandoned);
let slot = match turn {
Turn::Lead(lead) => {
handled.lock().extend(lead.group.iter().map(|t| t.item));
lead.finish(|_, t| t.item).0
}
Turn::Followed(slot, _) => slot,
};
assert_eq!(slot, id);
})
})
.collect();
for w in writers {
w.join().unwrap();
}
let mut handled = handled.lock().clone();
handled.sort();
assert_eq!(handled, vec![0, 1, 2]);
assert_eq!(q.queued.load(Ordering::Relaxed), 0);
});
}
#[test]
fn an_abandoned_group_does_not_wedge_the_queue() {
model(|| {
let q: Arc<GroupQueue<u32, bool>> = Arc::new(GroupQueue::default());
let writers: Vec<_> = (0..2u32)
.map(|id| {
let q = q.clone();
thread::spawn(move || {
let abandoned = || false;
let turn = q.enter(id, false, usize::MAX, |_| 1, &abandoned);
match turn {
Turn::Lead(lead) if id == 0 => drop(lead),
Turn::Lead(lead) => {
let _ = lead.finish(|_, _| true);
}
Turn::Followed(..) => {}
}
})
})
.collect();
for w in writers {
w.join().unwrap();
}
});
}
}