1use crate::checkpoint::sync::{Arc, AtomicU8, Ordering};
4use crate::record::PartitionId;
5use std::fmt;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct BatchId {
12 pub partition: PartitionId,
14 pub epoch: u32,
16 pub seq: u64,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
23#[repr(u8)]
24pub enum AckStatus {
25 Delivered = 0,
28 Failed = 1,
31}
32
33#[derive(Clone, Copy, Debug)]
36pub struct AckMsg {
37 pub id: BatchId,
39 pub last_offset: i64,
42 pub status: AckStatus,
44}
45
46#[derive(Clone)]
51pub(crate) enum AckTx {
52 Channel(crossbeam_channel::Sender<AckMsg>),
54 #[cfg(loom)]
56 Recorder(Arc<loom::sync::Mutex<Vec<AckMsg>>>),
57}
58
59impl AckTx {
60 fn send(&self, msg: AckMsg) {
61 match self {
62 AckTx::Channel(tx) => {
65 let _ = tx.send(msg);
66 }
67 #[cfg(loom)]
68 AckTx::Recorder(rec) => rec.lock().unwrap().push(msg),
69 }
70 }
71}
72
73impl fmt::Debug for AckTx {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str("AckTx")
76 }
77}
78
79#[derive(Debug)]
86struct AckState {
87 id: BatchId,
88 last_offset: i64,
89 status: AtomicU8,
90 tx: AckTx,
91}
92
93impl Drop for AckState {
94 fn drop(&mut self) {
95 let status = if self.status.load(Ordering::Relaxed) == AckStatus::Delivered as u8 {
96 AckStatus::Delivered
97 } else {
98 AckStatus::Failed
99 };
100 self.tx.send(AckMsg {
101 id: self.id,
102 last_offset: self.last_offset,
103 status,
104 });
105 }
106}
107
108#[derive(Clone, Debug)]
111pub struct AckRef(Arc<AckState>);
112
113impl AckRef {
114 pub(crate) fn new(id: BatchId, last_offset: i64, tx: AckTx) -> Self {
117 AckRef(Arc::new(AckState {
118 id,
119 last_offset,
120 status: AtomicU8::new(AckStatus::Delivered as u8),
121 tx,
122 }))
123 }
124
125 pub fn fail(&self) {
128 self.0
129 .status
130 .store(AckStatus::Failed as u8, Ordering::Relaxed);
131 }
132
133 #[must_use]
135 pub fn batch_id(&self) -> BatchId {
136 self.0.id
137 }
138
139 #[doc(hidden)]
142 #[must_use]
143 pub fn test_pair() -> (Self, crossbeam_channel::Receiver<AckMsg>) {
144 let (tx, rx) = crossbeam_channel::unbounded();
145 (
146 Self::new(
147 BatchId {
148 partition: PartitionId(0),
149 epoch: 0,
150 seq: 0,
151 },
152 0,
153 AckTx::Channel(tx),
154 ),
155 rx,
156 )
157 }
158}
159
160#[cfg(all(test, not(loom)))]
161mod tests {
162 use super::*;
163
164 fn make(tx: crossbeam_channel::Sender<AckMsg>, seq: u64, last_offset: i64) -> AckRef {
165 AckRef::new(
166 BatchId {
167 partition: PartitionId(1),
168 epoch: 7,
169 seq,
170 },
171 last_offset,
172 AckTx::Channel(tx),
173 )
174 }
175
176 #[test]
177 fn resolves_once_on_last_drop() {
178 let (tx, rx) = crossbeam_channel::unbounded();
179 let ack = make(tx, 3, 99);
180 let clones: Vec<_> = (0..8).map(|_| ack.clone()).collect();
181 drop(ack);
182 assert!(rx.try_recv().is_err(), "must not resolve while clones live");
183 drop(clones);
184 let msg = rx.try_recv().expect("resolved on last drop");
185 assert_eq!(msg.id.seq, 3);
186 assert_eq!(msg.last_offset, 99);
187 assert_eq!(msg.status, AckStatus::Delivered);
188 assert!(rx.try_recv().is_err(), "resolves exactly once");
189 }
190
191 #[test]
192 fn any_failure_poisons_the_batch() {
193 let (tx, rx) = crossbeam_channel::unbounded();
194 let ack = make(tx, 0, 10);
195 let sibling = ack.clone();
196 sibling.fail();
197 drop(sibling);
198 drop(ack);
199 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Failed);
200 }
201
202 #[test]
203 fn dropped_checkpointer_does_not_panic() {
204 let (tx, rx) = crossbeam_channel::unbounded();
205 let ack = make(tx, 0, 0);
206 drop(rx);
207 drop(ack); }
209}
210
211#[derive(Debug, Default)]
223pub struct AckSet(Vec<AckRef>);
224
225impl AckSet {
226 #[must_use]
228 pub fn new() -> Self {
229 AckSet(Vec::new())
230 }
231
232 pub fn push(&mut self, ack: AckRef) {
234 self.0.push(ack);
235 }
236
237 pub fn absorb(&mut self, mut other: AckSet) {
239 self.0.append(&mut other.0);
240 }
241
242 #[must_use]
244 pub fn is_empty(&self) -> bool {
245 self.0.is_empty()
246 }
247
248 #[must_use]
250 pub fn len(&self) -> usize {
251 self.0.len()
252 }
253
254 pub fn deliver(mut self) {
258 self.0.clear();
259 }
260}
261
262impl Drop for AckSet {
263 fn drop(&mut self) {
264 for ack in &self.0 {
265 ack.fail();
266 }
267 }
268}
269
270impl From<Vec<AckRef>> for AckSet {
271 fn from(acks: Vec<AckRef>) -> Self {
272 AckSet(acks)
273 }
274}
275
276#[cfg(all(test, not(loom)))]
277mod ack_set_tests {
278 use super::*;
279
280 #[test]
281 fn dropping_a_set_fails_its_batches() {
282 let (ack, rx) = AckRef::test_pair();
283 let mut set = AckSet::new();
284 set.push(ack.clone());
285 drop(ack);
286 drop(set);
287 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Failed);
288 }
289
290 #[test]
291 fn delivering_a_set_resolves_delivered() {
292 let (ack, rx) = AckRef::test_pair();
293 let mut set = AckSet::new();
294 set.push(ack.clone());
295 drop(ack);
296 set.deliver();
297 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Delivered);
298 }
299
300 #[test]
301 fn absorb_moves_handles_without_resolving() {
302 let (ack, rx) = AckRef::test_pair();
303 let mut a = AckSet::new();
304 a.push(ack.clone());
305 drop(ack);
306 let mut b = AckSet::new();
307 b.absorb(a);
308 assert!(rx.try_recv().is_err(), "absorb must not resolve");
309 assert_eq!(b.len(), 1);
310 b.deliver();
311 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Delivered);
312 }
313}