#[cfg(all(test, pgwire_loom))]
use loom::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[cfg(not(all(test, pgwire_loom)))]
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
pub(crate) struct ProgressCell {
applied: AtomicU64,
signalled: AtomicBool,
pending_since: AtomicU64,
timed: bool,
}
impl ProgressCell {
pub(crate) fn new(start: u64, timed: bool) -> Self {
Self {
applied: AtomicU64::new(start),
signalled: AtomicBool::new(false),
pending_since: AtomicU64::new(0),
timed,
}
}
pub(crate) fn load(&self) -> u64 {
self.applied.load(Ordering::Acquire)
}
pub(crate) fn raise(&self, new: u64, now: impl FnOnce() -> u64) -> bool {
let mut cur = self.applied.load(Ordering::Relaxed);
while new > cur {
match self
.applied
.compare_exchange_weak(cur, new, Ordering::SeqCst, Ordering::Relaxed)
{
Ok(_) => {
if self.timed && self.pending_since.load(Ordering::SeqCst) == 0 {
let _ = self.pending_since.compare_exchange(
0,
now(),
Ordering::SeqCst,
Ordering::SeqCst,
);
}
return !self.signalled.load(Ordering::SeqCst)
&& !self.signalled.swap(true, Ordering::SeqCst);
}
Err(observed) => cur = observed,
}
}
false
}
pub(crate) fn take_signal(&self) -> u64 {
self.signalled.store(false, Ordering::SeqCst);
self.applied.load(Ordering::SeqCst)
}
pub(crate) fn take_pending(&self, sent: u64, loaded_at: u64) -> u64 {
let since = self.pending_since.swap(0, Ordering::SeqCst);
if self.applied.load(Ordering::SeqCst) > sent {
let _ = self.pending_since.compare_exchange(
0,
loaded_at,
Ordering::SeqCst,
Ordering::SeqCst,
);
}
since
}
pub(crate) fn pending_since(&self) -> u64 {
self.pending_since.load(Ordering::SeqCst)
}
pub(crate) fn carry_pending(&self, since: u64) {
if !self.timed || since == 0 {
return;
}
let mut cur = self.pending_since.load(Ordering::SeqCst);
while cur == 0 || since < cur {
match self.pending_since.compare_exchange_weak(
cur,
since,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => return,
Err(actual) => cur = actual,
}
}
}
}
#[cfg(all(test, not(pgwire_loom)))]
mod tests {
use super::ProgressCell;
#[test]
fn a_carried_pending_start_keeps_the_oldest() {
let cell = ProgressCell::new(0, true);
cell.carry_pending(0);
assert_eq!(cell.pending_since(), 0, "0 means nothing pending");
cell.carry_pending(50);
assert_eq!(cell.pending_since(), 50);
cell.carry_pending(70);
assert_eq!(cell.pending_since(), 50, "a newer start is ignored");
cell.carry_pending(20);
assert_eq!(cell.pending_since(), 20, "an older start wins");
let untimed = ProgressCell::new(0, false);
untimed.carry_pending(20);
assert_eq!(untimed.pending_since(), 0, "untimed keeps nothing");
}
}
#[cfg(all(test, pgwire_loom))]
mod loom_model {
use super::ProgressCell;
use loom::sync::atomic::{AtomicU64, Ordering};
use loom::sync::Arc;
use loom::thread;
#[test]
fn a_raise_concurrent_with_send_or_clear_is_never_lost() {
loom::model(|| {
let cell = Arc::new(ProgressCell::new(0, false));
let generation = Arc::new(AtomicU64::new(0));
let consumer = {
let (cell, generation) = (Arc::clone(&cell), Arc::clone(&generation));
thread::spawn(move || {
for lsn in [1, 2] {
if cell.raise(lsn, || 1) {
generation.fetch_add(1, Ordering::SeqCst);
}
}
})
};
let mut last_sent = 0;
let mut seen = 0;
let mut pending = false;
for _ in 0..2 {
seen = generation.load(Ordering::SeqCst);
let applied = cell.take_signal();
pending = applied > last_sent;
if pending {
last_sent = applied; pending = false;
}
}
consumer.join().unwrap();
let newest = cell.load();
let woken = generation.load(Ordering::SeqCst) != seen;
assert!(
last_sent == newest || pending || woken,
"lost raise: newest {newest}, sent {last_sent}, no wake pending"
);
});
}
#[test]
fn a_raise_concurrent_with_a_report_keeps_a_pending_start() {
loom::model(|| {
let cell = Arc::new(ProgressCell::new(0, true));
assert!(cell.raise(1, || 10)); let consumer = {
let cell = Arc::clone(&cell);
thread::spawn(move || {
cell.raise(2, || 30);
})
};
let since = cell.take_pending(1, 20);
consumer.join().unwrap();
assert_eq!(since, 10, "the write reports the oldest start");
assert_eq!(cell.load(), 2);
assert_ne!(cell.pending_since(), 0, "the newer raise lost its start");
});
}
}