use crate::checkpoint::sync::{Arc, AtomicU8, Ordering};
use crate::record::PartitionId;
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BatchId {
pub partition: PartitionId,
pub epoch: u32,
pub seq: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum AckStatus {
Delivered = 0,
Failed = 1,
}
#[derive(Clone, Copy, Debug)]
pub struct AckMsg {
pub id: BatchId,
pub last_offset: i64,
pub status: AckStatus,
}
#[derive(Clone)]
pub(crate) enum AckTx {
Channel(crossbeam_channel::Sender<AckMsg>),
#[cfg(loom)]
Recorder(Arc<loom::sync::Mutex<Vec<AckMsg>>>),
}
impl AckTx {
fn send(&self, msg: AckMsg) {
match self {
AckTx::Channel(tx) => {
let _ = tx.send(msg);
}
#[cfg(loom)]
AckTx::Recorder(rec) => rec.lock().unwrap().push(msg),
}
}
}
impl fmt::Debug for AckTx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("AckTx")
}
}
#[derive(Debug)]
struct AckState {
id: BatchId,
last_offset: i64,
status: AtomicU8,
tx: AckTx,
}
impl Drop for AckState {
fn drop(&mut self) {
let status = if self.status.load(Ordering::Relaxed) == AckStatus::Delivered as u8 {
AckStatus::Delivered
} else {
AckStatus::Failed
};
self.tx.send(AckMsg {
id: self.id,
last_offset: self.last_offset,
status,
});
}
}
#[derive(Clone, Debug)]
pub struct AckRef(Arc<AckState>);
impl AckRef {
pub(crate) fn new(id: BatchId, last_offset: i64, tx: AckTx) -> Self {
AckRef(Arc::new(AckState {
id,
last_offset,
status: AtomicU8::new(AckStatus::Delivered as u8),
tx,
}))
}
pub fn fail(&self) {
self.0
.status
.store(AckStatus::Failed as u8, Ordering::Relaxed);
}
#[must_use]
pub fn batch_id(&self) -> BatchId {
self.0.id
}
#[doc(hidden)]
#[must_use]
pub fn test_pair() -> (Self, crossbeam_channel::Receiver<AckMsg>) {
let (tx, rx) = crossbeam_channel::unbounded();
(
Self::new(
BatchId {
partition: PartitionId(0),
epoch: 0,
seq: 0,
},
0,
AckTx::Channel(tx),
),
rx,
)
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
fn make(tx: crossbeam_channel::Sender<AckMsg>, seq: u64, last_offset: i64) -> AckRef {
AckRef::new(
BatchId {
partition: PartitionId(1),
epoch: 7,
seq,
},
last_offset,
AckTx::Channel(tx),
)
}
#[test]
fn resolves_once_on_last_drop() {
let (tx, rx) = crossbeam_channel::unbounded();
let ack = make(tx, 3, 99);
let clones: Vec<_> = (0..8).map(|_| ack.clone()).collect();
drop(ack);
assert!(rx.try_recv().is_err(), "must not resolve while clones live");
drop(clones);
let msg = rx.try_recv().expect("resolved on last drop");
assert_eq!(msg.id.seq, 3);
assert_eq!(msg.last_offset, 99);
assert_eq!(msg.status, AckStatus::Delivered);
assert!(rx.try_recv().is_err(), "resolves exactly once");
}
#[test]
fn any_failure_poisons_the_batch() {
let (tx, rx) = crossbeam_channel::unbounded();
let ack = make(tx, 0, 10);
let sibling = ack.clone();
sibling.fail();
drop(sibling);
drop(ack);
assert_eq!(rx.try_recv().unwrap().status, AckStatus::Failed);
}
#[test]
fn dropped_checkpointer_does_not_panic() {
let (tx, rx) = crossbeam_channel::unbounded();
let ack = make(tx, 0, 0);
drop(rx);
drop(ack); }
}
#[derive(Debug, Default)]
pub struct AckSet(Vec<AckRef>);
impl AckSet {
#[must_use]
pub fn new() -> Self {
AckSet(Vec::new())
}
pub fn push(&mut self, ack: AckRef) {
self.0.push(ack);
}
pub fn absorb(&mut self, mut other: AckSet) {
self.0.append(&mut other.0);
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn deliver(mut self) {
self.0.clear();
}
}
impl Drop for AckSet {
fn drop(&mut self) {
for ack in &self.0 {
ack.fail();
}
}
}
impl From<Vec<AckRef>> for AckSet {
fn from(acks: Vec<AckRef>) -> Self {
AckSet(acks)
}
}
#[cfg(all(test, not(loom)))]
mod ack_set_tests {
use super::*;
#[test]
fn dropping_a_set_fails_its_batches() {
let (ack, rx) = AckRef::test_pair();
let mut set = AckSet::new();
set.push(ack.clone());
drop(ack);
drop(set);
assert_eq!(rx.try_recv().unwrap().status, AckStatus::Failed);
}
#[test]
fn delivering_a_set_resolves_delivered() {
let (ack, rx) = AckRef::test_pair();
let mut set = AckSet::new();
set.push(ack.clone());
drop(ack);
set.deliver();
assert_eq!(rx.try_recv().unwrap().status, AckStatus::Delivered);
}
#[test]
fn absorb_moves_handles_without_resolving() {
let (ack, rx) = AckRef::test_pair();
let mut a = AckSet::new();
a.push(ack.clone());
drop(ack);
let mut b = AckSet::new();
b.absorb(a);
assert!(rx.try_recv().is_err(), "absorb must not resolve");
assert_eq!(b.len(), 1);
b.deliver();
assert_eq!(rx.try_recv().unwrap().status, AckStatus::Delivered);
}
}