kvbm_engine/offload/
cancel.rs1use std::sync::Arc;
16
17use tokio::sync::watch;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum CancelState {
22 Active,
24 Requested,
26 Draining {
28 in_flight: usize,
30 },
31 Confirmed,
33}
34
35impl CancelState {
36 pub fn is_cancelled(&self) -> bool {
38 !matches!(self, CancelState::Active)
39 }
40
41 pub fn is_draining(&self) -> bool {
43 matches!(self, CancelState::Draining { .. })
44 }
45
46 pub fn is_confirmed(&self) -> bool {
48 matches!(self, CancelState::Confirmed)
49 }
50}
51
52#[derive(Clone)]
58pub struct CancellationToken {
59 request_tx: Arc<watch::Sender<bool>>,
61 state_rx: watch::Receiver<CancelState>,
63}
64
65impl CancellationToken {
66 pub fn new() -> (Self, CancelStateUpdater) {
72 let (request_tx, request_rx) = watch::channel(false);
73 let (state_tx, state_rx) = watch::channel(CancelState::Active);
74
75 let token = CancellationToken {
76 request_tx: Arc::new(request_tx),
77 state_rx,
78 };
79
80 let updater = CancelStateUpdater {
81 request_rx,
82 state_tx,
83 };
84
85 (token, updater)
86 }
87
88 pub fn request(&self) {
93 let _ = self.request_tx.send(true);
94 }
95
96 pub fn is_requested(&self) -> bool {
98 *self.request_tx.borrow()
99 }
100
101 pub fn state(&self) -> CancelState {
103 *self.state_rx.borrow()
104 }
105
106 pub fn is_confirmed(&self) -> bool {
108 self.state().is_confirmed()
109 }
110
111 pub fn wait_confirmed(&self) -> CancelConfirmation {
115 CancelConfirmation {
116 state_rx: self.state_rx.clone(),
117 }
118 }
119}
120
121pub struct CancelStateUpdater {
125 request_rx: watch::Receiver<bool>,
127 state_tx: watch::Sender<CancelState>,
129}
130
131impl CancelStateUpdater {
132 pub fn is_requested(&self) -> bool {
134 *self.request_rx.borrow()
135 }
136
137 pub async fn wait_for_request(&mut self) {
139 while !*self.request_rx.borrow() {
140 if self.request_rx.changed().await.is_err() {
141 break;
143 }
144 }
145 }
146
147 pub fn state(&self) -> CancelState {
149 *self.state_tx.borrow()
150 }
151
152 pub fn set_requested(&self) {
154 let _ = self.state_tx.send(CancelState::Requested);
155 }
156
157 pub fn set_draining(&self, in_flight: usize) {
159 let _ = self.state_tx.send(CancelState::Draining { in_flight });
160 }
161
162 pub fn update_draining(&self, in_flight: usize) {
164 if in_flight == 0 {
165 self.set_confirmed();
166 } else {
167 let _ = self.state_tx.send(CancelState::Draining { in_flight });
168 }
169 }
170
171 pub fn set_confirmed(&self) {
173 let _ = self.state_tx.send(CancelState::Confirmed);
174 }
175
176 pub fn subscribe(&self) -> watch::Receiver<CancelState> {
178 self.state_tx.subscribe()
179 }
180}
181
182pub struct CancelConfirmation {
186 state_rx: watch::Receiver<CancelState>,
187}
188
189impl CancelConfirmation {
190 pub async fn wait(mut self) {
194 loop {
195 if self.state_rx.borrow().is_confirmed() {
197 return;
198 }
199
200 if self.state_rx.changed().await.is_err() {
202 return;
204 }
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_cancel_state_transitions() {
215 let state = CancelState::Active;
216 assert!(!state.is_cancelled());
217 assert!(!state.is_draining());
218 assert!(!state.is_confirmed());
219
220 let state = CancelState::Requested;
221 assert!(state.is_cancelled());
222 assert!(!state.is_draining());
223 assert!(!state.is_confirmed());
224
225 let state = CancelState::Draining { in_flight: 5 };
226 assert!(state.is_cancelled());
227 assert!(state.is_draining());
228 assert!(!state.is_confirmed());
229
230 let state = CancelState::Confirmed;
231 assert!(state.is_cancelled());
232 assert!(!state.is_draining());
233 assert!(state.is_confirmed());
234 }
235
236 #[test]
237 fn test_cancellation_token_request() {
238 let (token, _updater) = CancellationToken::new();
239
240 assert!(!token.is_requested());
241 assert_eq!(token.state(), CancelState::Active);
242
243 token.request();
244
245 assert!(token.is_requested());
246 }
247
248 #[test]
249 fn test_cancellation_updater_state() {
250 let (token, updater) = CancellationToken::new();
251
252 assert_eq!(token.state(), CancelState::Active);
253
254 updater.set_requested();
255 assert_eq!(token.state(), CancelState::Requested);
256
257 updater.set_draining(3);
258 assert_eq!(token.state(), CancelState::Draining { in_flight: 3 });
259
260 updater.update_draining(1);
261 assert_eq!(token.state(), CancelState::Draining { in_flight: 1 });
262
263 updater.update_draining(0);
264 assert_eq!(token.state(), CancelState::Confirmed);
265 }
266
267 #[tokio::test]
268 async fn test_cancel_confirmation_immediate() {
269 let (token, updater) = CancellationToken::new();
270
271 updater.set_confirmed();
273
274 token.wait_confirmed().wait().await;
276 assert!(token.is_confirmed());
277 }
278
279 #[tokio::test]
280 async fn test_cancel_confirmation_delayed() {
281 let (token, updater) = CancellationToken::new();
282
283 let confirmation = token.wait_confirmed();
284
285 let updater_clone = updater.state_tx.clone();
287 tokio::spawn(async move {
288 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
289 let _ = updater_clone.send(CancelState::Confirmed);
290 });
291
292 tokio::time::timeout(tokio::time::Duration::from_millis(100), confirmation.wait())
294 .await
295 .expect("Should complete within timeout");
296
297 assert!(token.is_confirmed());
298 }
299
300 #[tokio::test]
303 async fn test_confirmation_blocked_during_draining() {
304 let (token, updater) = CancellationToken::new();
305
306 token.request();
307 updater.set_draining(2);
308
309 let confirmation = token.wait_confirmed();
311 let result =
312 tokio::time::timeout(tokio::time::Duration::from_millis(30), confirmation.wait()).await;
313 assert!(result.is_err(), "Should timeout while in_flight > 0");
314
315 assert_eq!(token.state(), CancelState::Draining { in_flight: 2 });
317 }
318
319 #[test]
321 fn test_draining_zero_confirms() {
322 let (token, updater) = CancellationToken::new();
323
324 token.request();
325 updater.set_draining(1);
326 assert_eq!(token.state(), CancelState::Draining { in_flight: 1 });
327
328 updater.update_draining(0);
330 assert_eq!(token.state(), CancelState::Confirmed);
331 }
332
333 #[test]
335 fn test_full_draining_sequence() {
336 let (token, updater) = CancellationToken::new();
337
338 assert_eq!(token.state(), CancelState::Active);
340
341 token.request();
343 assert!(token.is_requested());
344
345 updater.set_draining(3);
347 assert_eq!(token.state(), CancelState::Draining { in_flight: 3 });
348
349 updater.update_draining(2);
351 assert_eq!(token.state(), CancelState::Draining { in_flight: 2 });
352
353 updater.update_draining(1);
354 assert_eq!(token.state(), CancelState::Draining { in_flight: 1 });
355
356 updater.update_draining(0);
358 assert!(token.is_confirmed());
359 }
360}