1use crate::{GitError, Result};
23use std::error::Error as StdError;
24use std::fmt;
25use std::io::{self, Read};
26use std::process::Child;
27use std::sync::atomic::{AtomicBool, Ordering};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub enum StreamControl {
37 #[default]
38 Continue,
39 Stop,
40}
41
42impl StreamControl {
43 #[inline]
45 pub const fn is_stop(self) -> bool {
46 matches!(self, Self::Stop)
47 }
48
49 #[inline]
51 pub const fn is_continue(self) -> bool {
52 matches!(self, Self::Continue)
53 }
54}
55
56#[derive(Debug, Default)]
64pub struct AtomicCancel {
65 cancelled: AtomicBool,
66}
67
68impl AtomicCancel {
69 #[inline]
71 pub const fn new() -> Self {
72 Self {
73 cancelled: AtomicBool::new(false),
74 }
75 }
76
77 #[inline]
79 pub fn cancel(&self) {
80 self.cancelled.store(true, Ordering::Release);
81 }
82
83 #[inline]
85 pub fn clear(&self) {
86 self.cancelled.store(false, Ordering::Release);
87 }
88
89 #[inline]
91 pub fn is_cancelled(&self) -> bool {
92 self.cancelled.load(Ordering::Acquire)
93 }
94}
95
96#[derive(Debug, Clone, Copy, Default)]
104pub struct CancelFlag<'a> {
105 source: Option<&'a AtomicCancel>,
106}
107
108impl CancelFlag<'static> {
109 #[inline]
111 pub const fn never() -> Self {
112 Self { source: None }
113 }
114}
115
116impl<'a> CancelFlag<'a> {
117 #[inline]
119 pub const fn new(source: &'a AtomicCancel) -> Self {
120 Self {
121 source: Some(source),
122 }
123 }
124
125 #[inline]
127 pub const fn never_dyn() -> CancelFlag<'static> {
128 CancelFlag::never()
129 }
130
131 #[inline]
133 pub fn is_cancelled(self) -> bool {
134 self.source.is_some_and(AtomicCancel::is_cancelled)
135 }
136
137 #[inline]
139 pub fn check(self) -> Result<()> {
140 if self.is_cancelled() {
141 Err(GitError::Cancelled)
142 } else {
143 Ok(())
144 }
145 }
146
147 #[inline]
149 pub fn control(self) -> StreamControl {
150 if self.is_cancelled() {
151 StreamControl::Stop
152 } else {
153 StreamControl::Continue
154 }
155 }
156
157 #[inline]
159 pub fn as_ref(self) -> CancelFlag<'a> {
160 self
161 }
162
163 #[inline]
165 pub fn source(self) -> Option<&'a AtomicCancel> {
166 self.source
167 }
168}
169
170pub type DynCancelFlag<'a> = CancelFlag<'a>;
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub struct OperationCancelled;
180
181impl fmt::Display for OperationCancelled {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.write_str("operation cancelled")
184 }
185}
186
187impl StdError for OperationCancelled {}
188
189#[derive(Debug)]
194pub struct CancellableRead<'a, R> {
195 inner: R,
196 cancel: CancelFlag<'a>,
197}
198
199impl<'a, R> CancellableRead<'a, R> {
200 #[inline]
202 pub fn new(inner: R, cancel: CancelFlag<'a>) -> Self {
203 Self { inner, cancel }
204 }
205
206 #[inline]
208 pub fn get_ref(&self) -> &R {
209 &self.inner
210 }
211
212 #[inline]
214 pub fn get_mut(&mut self) -> &mut R {
215 &mut self.inner
216 }
217
218 #[inline]
220 pub fn into_inner(self) -> R {
221 self.inner
222 }
223
224 #[inline]
226 pub fn cancel(&self) -> CancelFlag<'a> {
227 self.cancel
228 }
229}
230
231impl<R: Read> Read for CancellableRead<'_, R> {
232 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
233 if self.cancel.is_cancelled() {
234 return Err(cancelled_io_error());
235 }
236 self.inner.read(buf)
237 }
238}
239
240#[inline]
245pub fn cancelled_io_error() -> io::Error {
246 io::Error::other(OperationCancelled)
247}
248
249#[inline]
251pub fn is_cancelled_io(err: &io::Error) -> bool {
252 err.get_ref()
253 .is_some_and(|inner| inner.downcast_ref::<OperationCancelled>().is_some())
254}
255
256#[inline]
259pub fn map_cancel_io(err: io::Error) -> GitError {
260 if is_cancelled_io(&err) {
261 GitError::Cancelled
262 } else {
263 GitError::from(err)
264 }
265}
266
267#[inline]
272pub fn is_cancelled_error(err: &GitError) -> bool {
273 err.is_cancelled()
274}
275
276#[inline]
278pub fn kill_child_if_cancelled(child: &mut Child, cancel: CancelFlag<'_>) {
279 if cancel.is_cancelled() {
280 let _ = child.kill();
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use std::io::{Cursor, Read};
288
289 #[test]
290 fn never_flag_is_never_cancelled() {
291 let flag = CancelFlag::never();
292 assert!(!flag.is_cancelled());
293 assert!(flag.check().is_ok());
294 assert_eq!(flag.control(), StreamControl::Continue);
295 }
296
297 #[test]
298 fn atomic_cancel_trips_flag() {
299 let source = AtomicCancel::new();
300 let flag = CancelFlag::new(&source);
301 assert!(!flag.is_cancelled());
302 source.cancel();
303 assert!(flag.is_cancelled());
304 assert_eq!(flag.check(), Err(GitError::Cancelled));
305 assert_eq!(flag.control(), StreamControl::Stop);
306 source.clear();
307 assert!(!flag.is_cancelled());
308 }
309
310 #[test]
311 fn cancellable_read_fails_with_operation_cancelled_not_interrupted() {
312 let source = AtomicCancel::new();
313 let data = b"hello world";
314 let mut reader = CancellableRead::new(Cursor::new(&data[..]), CancelFlag::new(&source));
315 let mut buf = [0u8; 5];
316 assert_eq!(reader.read(&mut buf).expect("read"), 5);
317 source.cancel();
318 let err = reader.read(&mut buf).expect_err("cancelled");
319 assert_ne!(
320 err.kind(),
321 io::ErrorKind::Interrupted,
322 "Interrupted is retried by read_exact / pkt-line"
323 );
324 assert!(is_cancelled_io(&err));
325 assert_eq!(map_cancel_io(err), GitError::Cancelled);
326 }
327
328 #[test]
329 fn read_exact_does_not_spin_on_cancel() {
330 let source = AtomicCancel::new();
331 source.cancel();
332 let mut reader = CancellableRead::new(Cursor::new(&b"abcd"[..]), CancelFlag::new(&source));
333 let mut buf = [0u8; 4];
334 let err = reader.read_exact(&mut buf).expect_err("cancelled");
336 assert!(is_cancelled_io(&err));
337 }
338
339 #[test]
340 fn never_dyn_alias_matches_never() {
341 assert!(!CancelFlag::never().is_cancelled());
342 }
343
344 #[test]
345 fn kill_child_if_cancelled_is_noop_when_not_cancelled() {
346 let mut child = std::process::Command::new("sleep")
347 .arg("60")
348 .stdout(std::process::Stdio::null())
349 .stderr(std::process::Stdio::null())
350 .spawn()
351 .expect("spawn sleep");
352 let source = AtomicCancel::new();
353 kill_child_if_cancelled(&mut child, CancelFlag::new(&source));
354 assert!(child.try_wait().expect("try_wait").is_none());
355 let _ = child.kill();
356 let _ = child.wait();
357 }
358
359 #[test]
360 fn kill_child_if_cancelled_kills_when_flag_set() {
361 let mut child = std::process::Command::new("sleep")
362 .arg("60")
363 .stdout(std::process::Stdio::null())
364 .stderr(std::process::Stdio::null())
365 .spawn()
366 .expect("spawn sleep");
367 let source = AtomicCancel::new();
368 source.cancel();
369 kill_child_if_cancelled(&mut child, CancelFlag::new(&source));
370 let status = child.wait().expect("wait after kill");
371 assert!(!status.success());
372 }
373}