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)]
224pub struct AckSet(Vec<AckRef>);
225
226impl AckSet {
227 #[must_use]
229 pub fn new() -> Self {
230 AckSet(Vec::new())
231 }
232
233 pub fn push(&mut self, ack: AckRef) {
235 self.0.push(ack);
236 }
237
238 pub fn absorb(&mut self, mut other: AckSet) {
240 self.0.append(&mut other.0);
241 }
242
243 #[must_use]
245 pub fn is_empty(&self) -> bool {
246 self.0.is_empty()
247 }
248
249 #[must_use]
251 pub fn len(&self) -> usize {
252 self.0.len()
253 }
254
255 pub fn deliver(mut self) {
259 self.0.clear();
260 }
261}
262
263impl Drop for AckSet {
264 fn drop(&mut self) {
265 for ack in &self.0 {
266 ack.fail();
267 }
268 }
269}
270
271impl From<Vec<AckRef>> for AckSet {
272 fn from(acks: Vec<AckRef>) -> Self {
273 AckSet(acks)
274 }
275}
276
277#[cfg(all(test, not(loom)))]
278mod ack_set_tests {
279 use super::*;
280
281 #[test]
282 fn dropping_a_set_fails_its_batches() {
283 let (ack, rx) = AckRef::test_pair();
284 let mut set = AckSet::new();
285 set.push(ack.clone());
286 drop(ack);
287 drop(set);
288 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Failed);
289 }
290
291 #[test]
292 fn delivering_a_set_resolves_delivered() {
293 let (ack, rx) = AckRef::test_pair();
294 let mut set = AckSet::new();
295 set.push(ack.clone());
296 drop(ack);
297 set.deliver();
298 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Delivered);
299 }
300
301 #[test]
302 fn absorb_moves_handles_without_resolving() {
303 let (ack, rx) = AckRef::test_pair();
304 let mut a = AckSet::new();
305 a.push(ack.clone());
306 drop(ack);
307 let mut b = AckSet::new();
308 b.absorb(a);
309 assert!(rx.try_recv().is_err(), "absorb must not resolve");
310 assert_eq!(b.len(), 1);
311 b.deliver();
312 assert_eq!(rx.try_recv().unwrap().status, AckStatus::Delivered);
313 }
314}