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::Arc;
28use std::sync::atomic::{AtomicBool, Ordering};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum StreamControl {
38 #[default]
39 Continue,
40 Stop,
41}
42
43impl StreamControl {
44 #[inline]
46 pub const fn is_stop(self) -> bool {
47 matches!(self, Self::Stop)
48 }
49
50 #[inline]
52 pub const fn is_continue(self) -> bool {
53 matches!(self, Self::Continue)
54 }
55}
56
57#[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]
269pub fn is_cancelled_error(err: &GitError) -> bool {
270 matches!(err, GitError::Cancelled)
271}
272
273#[inline]
275pub fn kill_child_if_cancelled(child: &mut Child, cancel: CancelFlag<'_>) {
276 if cancel.is_cancelled() {
277 let _ = child.kill();
278 }
279}
280
281#[inline]
283pub fn cancel_flag_from_arc(source: &Arc<AtomicCancel>) -> CancelFlag<'_> {
284 CancelFlag::new(source.as_ref())
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use std::io::{Cursor, Read};
291
292 #[test]
293 fn never_flag_is_never_cancelled() {
294 let flag = CancelFlag::never();
295 assert!(!flag.is_cancelled());
296 assert!(flag.check().is_ok());
297 assert_eq!(flag.control(), StreamControl::Continue);
298 }
299
300 #[test]
301 fn atomic_cancel_trips_flag() {
302 let source = AtomicCancel::new();
303 let flag = CancelFlag::new(&source);
304 assert!(!flag.is_cancelled());
305 source.cancel();
306 assert!(flag.is_cancelled());
307 assert_eq!(flag.check(), Err(GitError::Cancelled));
308 assert_eq!(flag.control(), StreamControl::Stop);
309 source.clear();
310 assert!(!flag.is_cancelled());
311 }
312
313 #[test]
314 fn cancellable_read_fails_with_operation_cancelled_not_interrupted() {
315 let source = AtomicCancel::new();
316 let data = b"hello world";
317 let mut reader = CancellableRead::new(Cursor::new(&data[..]), CancelFlag::new(&source));
318 let mut buf = [0u8; 5];
319 assert_eq!(reader.read(&mut buf).expect("read"), 5);
320 source.cancel();
321 let err = reader.read(&mut buf).expect_err("cancelled");
322 assert_ne!(
323 err.kind(),
324 io::ErrorKind::Interrupted,
325 "Interrupted is retried by read_exact / pkt-line"
326 );
327 assert!(is_cancelled_io(&err));
328 assert_eq!(map_cancel_io(err), GitError::Cancelled);
329 }
330
331 #[test]
332 fn read_exact_does_not_spin_on_cancel() {
333 let source = AtomicCancel::new();
334 source.cancel();
335 let mut reader = CancellableRead::new(Cursor::new(&b"abcd"[..]), CancelFlag::new(&source));
336 let mut buf = [0u8; 4];
337 let err = reader.read_exact(&mut buf).expect_err("cancelled");
339 assert!(is_cancelled_io(&err));
340 }
341
342 #[test]
343 fn never_dyn_alias_matches_never() {
344 assert!(!CancelFlag::never().is_cancelled());
345 }
346
347 #[test]
348 fn kill_child_if_cancelled_is_noop_when_not_cancelled() {
349 let mut child = std::process::Command::new("sleep")
350 .arg("60")
351 .stdout(std::process::Stdio::null())
352 .stderr(std::process::Stdio::null())
353 .spawn()
354 .expect("spawn sleep");
355 let source = AtomicCancel::new();
356 kill_child_if_cancelled(&mut child, CancelFlag::new(&source));
357 assert!(child.try_wait().expect("try_wait").is_none());
358 let _ = child.kill();
359 let _ = child.wait();
360 }
361
362 #[test]
363 fn kill_child_if_cancelled_kills_when_flag_set() {
364 let mut child = std::process::Command::new("sleep")
365 .arg("60")
366 .stdout(std::process::Stdio::null())
367 .stderr(std::process::Stdio::null())
368 .spawn()
369 .expect("spawn sleep");
370 let source = AtomicCancel::new();
371 source.cancel();
372 kill_child_if_cancelled(&mut child, CancelFlag::new(&source));
373 let status = child.wait().expect("wait after kill");
374 assert!(!status.success());
375 }
376}