Skip to content

Commit f1ba7bd

Browse files
committed
Allow configuration of interrupts in status iter
Otherwise users might not have too much delay until an interrupt is possible, wasting a lot of time.
1 parent 98b3680 commit f1ba7bd

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

gix/src/status/index_worktree.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ mod submodule_status {
284284
/// It's a crutch that is just there to make single-threaded applications possible at all, as it's not really an iterator
285285
/// anymore. If this matters, better run [Repository::index_worktree_status()] by hand as it provides all control one would need,
286286
/// just not as an iterator.
287+
///
288+
/// Also, even with `parallel` set, the first call to `next()` will block until there is an item available, without a chance
289+
/// to interrupt unless [`status::Platform::should_interrupt_*()`](crate::status::Platform::should_interrupt_shared()) was
290+
/// configured.
287291
pub struct Iter {
288292
#[cfg(feature = "parallel")]
289293
#[allow(clippy::type_complexity)]
@@ -292,7 +296,7 @@ pub struct Iter {
292296
std::thread::JoinHandle<Result<iter::Outcome, crate::status::index_worktree::Error>>,
293297
)>,
294298
#[cfg(feature = "parallel")]
295-
should_interrupt: std::sync::Arc<AtomicBool>,
299+
should_interrupt: crate::status::OwnedOrStaticAtomic,
296300
/// Without parallelization, the iterator has to buffer all changes in advance.
297301
#[cfg(not(feature = "parallel"))]
298302
items: std::vec::IntoIter<iter::Item>,
@@ -309,8 +313,6 @@ pub mod iter {
309313
use crate::status::index_worktree::{iter, BuiltinSubmoduleStatus};
310314
use crate::status::{index_worktree, Platform};
311315
use crate::worktree::IndexPersistedOrInMemory;
312-
use std::sync::atomic::AtomicBool;
313-
use std::sync::Arc;
314316

315317
pub(super) enum ApplyChange {
316318
SetSizeToZero,
@@ -564,7 +566,6 @@ pub mod iter {
564566
Some(index) => index,
565567
};
566568

567-
let should_interrupt = Arc::new(AtomicBool::default());
568569
let skip_hash = self
569570
.repo
570571
.config
@@ -574,6 +575,7 @@ pub mod iter {
574575
.transpose()
575576
.with_lenient_default(self.repo.config.lenient_config)?
576577
.unwrap_or_default();
578+
let should_interrupt = self.should_interrupt.clone().unwrap_or_default();
577579
let submodule = BuiltinSubmoduleStatus::new(self.repo.clone().into_sync(), self.submodules)?;
578580
#[cfg(feature = "parallel")]
579581
{
@@ -726,10 +728,28 @@ pub mod iter {
726728
#[cfg(feature = "parallel")]
727729
impl Drop for super::Iter {
728730
fn drop(&mut self) {
729-
self.should_interrupt.store(true, std::sync::atomic::Ordering::Relaxed);
730-
// Allow to temporarily 'leak' the producer to not block on drop, nobody
731-
// is interested in the result of the thread anymore.
732-
drop(self.rx_and_join.take());
731+
use crate::status::OwnedOrStaticAtomic;
732+
let Some((rx, handle)) = self.rx_and_join.take() else {
733+
return;
734+
};
735+
let prev = self.should_interrupt.swap(true, std::sync::atomic::Ordering::Relaxed);
736+
let undo = match &self.should_interrupt {
737+
OwnedOrStaticAtomic::Shared(flag) => *flag,
738+
OwnedOrStaticAtomic::Owned { flag, private: false } => flag.as_ref(),
739+
OwnedOrStaticAtomic::Owned { private: true, .. } => {
740+
// Leak the handle to let it shut down in the background, so drop returns more quickly.
741+
drop((rx, handle));
742+
return;
743+
}
744+
};
745+
// Wait until there is time to respond before we undo the change.
746+
handle.join().ok();
747+
undo.fetch_update(
748+
std::sync::atomic::Ordering::SeqCst,
749+
std::sync::atomic::Ordering::SeqCst,
750+
|current| current.then_some(prev),
751+
)
752+
.ok();
733753
}
734754
}
735755

gix/src/status/mod.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use crate::{config, Repository};
22
pub use gix_status as plumbing;
3+
use std::ops::Deref;
4+
use std::sync::atomic::AtomicBool;
5+
use std::sync::Arc;
36

47
/// A structure to hold options configuring the status request, which can then be turned into an iterator.
58
pub struct Platform<'repo, Progress>
@@ -11,6 +14,37 @@ where
1114
index: Option<crate::worktree::IndexPersistedOrInMemory>,
1215
submodules: Submodule,
1316
index_worktree_options: index_worktree::Options,
17+
should_interrupt: Option<OwnedOrStaticAtomic>,
18+
}
19+
20+
#[derive(Clone)]
21+
enum OwnedOrStaticAtomic {
22+
Owned {
23+
flag: Arc<AtomicBool>,
24+
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
25+
private: bool,
26+
},
27+
Shared(&'static AtomicBool),
28+
}
29+
30+
impl Default for OwnedOrStaticAtomic {
31+
fn default() -> Self {
32+
OwnedOrStaticAtomic::Owned {
33+
flag: Arc::new(AtomicBool::default()),
34+
private: true,
35+
}
36+
}
37+
}
38+
39+
impl Deref for OwnedOrStaticAtomic {
40+
type Target = std::sync::atomic::AtomicBool;
41+
42+
fn deref(&self) -> &Self::Target {
43+
match self {
44+
OwnedOrStaticAtomic::Owned { flag, .. } => flag,
45+
OwnedOrStaticAtomic::Shared(flag) => flag,
46+
}
47+
}
1448
}
1549

1650
/// How to obtain a submodule's status.
@@ -71,6 +105,7 @@ impl Repository {
71105
progress,
72106
index: None,
73107
submodules: Submodule::default(),
108+
should_interrupt: None,
74109
index_worktree_options: index_worktree::Options {
75110
sorting: None,
76111
dirwalk_options: Some(self.dirwalk_options()?),

gix/src/status/platform.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::status::{index_worktree, Platform, Submodule};
1+
use crate::status::{index_worktree, OwnedOrStaticAtomic, Platform, Submodule};
2+
use std::sync::atomic::AtomicBool;
23

34
/// Builder
45
impl<'repo, Progress> Platform<'repo, Progress>
@@ -16,6 +17,26 @@ where
1617
self
1718
}
1819

20+
/// Set the interrupt flag to `should_interrupt`, which typically is an application-wide flag
21+
/// that is ultimately controlled by user interrupts.
22+
///
23+
/// If it is `true`, the iteration will stop immediately.
24+
pub fn should_interrupt_shared(mut self, should_interrupt: &'static AtomicBool) -> Self {
25+
self.should_interrupt = Some(OwnedOrStaticAtomic::Shared(should_interrupt));
26+
self
27+
}
28+
29+
/// Set the interrupt flag to `should_interrupt`, as controlled by the caller.
30+
///
31+
/// If it is `true`, the iteration will stop immediately.
32+
pub fn should_interrupt_owned(mut self, should_interrupt: std::sync::Arc<AtomicBool>) -> Self {
33+
self.should_interrupt = Some(OwnedOrStaticAtomic::Owned {
34+
flag: should_interrupt,
35+
private: false,
36+
});
37+
self
38+
}
39+
1940
/// Configure how the `submodule_status` is obtained when looking at submodules that are still mentioned in the index.
2041
// If `None` is given, no submodule status check is performed.
2142
pub fn index_worktree_submodules(mut self, submodules: impl Into<Option<Submodule>>) -> Self {

0 commit comments

Comments
 (0)