Skip to content

Commit 33f02fb

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 33f02fb

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed

gix/src/status/index_worktree.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::bstr::{BStr, BString};
2+
use crate::status::OwnedOrStaticAtomic;
23
use crate::{config, Repository};
34
use gix_status::index_as_worktree::traits::{CompareBlobs, SubmoduleStatus};
45
use std::sync::atomic::AtomicBool;
@@ -284,6 +285,10 @@ mod submodule_status {
284285
/// It's a crutch that is just there to make single-threaded applications possible at all, as it's not really an iterator
285286
/// anymore. If this matters, better run [Repository::index_worktree_status()] by hand as it provides all control one would need,
286287
/// just not as an iterator.
288+
///
289+
/// Also, even with `parallel` set, the first call to `next()` will block until there is an item available, without a chance
290+
/// to interrupt unless [`status::Platform::should_interrupt_*()`](crate::status::Platform::should_interrupt_shared()) was
291+
/// configured.
287292
pub struct Iter {
288293
#[cfg(feature = "parallel")]
289294
#[allow(clippy::type_complexity)]
@@ -292,7 +297,7 @@ pub struct Iter {
292297
std::thread::JoinHandle<Result<iter::Outcome, crate::status::index_worktree::Error>>,
293298
)>,
294299
#[cfg(feature = "parallel")]
295-
should_interrupt: std::sync::Arc<AtomicBool>,
300+
should_interrupt: OwnedOrStaticAtomic,
296301
/// Without parallelization, the iterator has to buffer all changes in advance.
297302
#[cfg(not(feature = "parallel"))]
298303
items: std::vec::IntoIter<iter::Item>,
@@ -307,10 +312,8 @@ pub mod iter {
307312
use crate::bstr::BString;
308313
use crate::config::cache::util::ApplyLeniencyDefault;
309314
use crate::status::index_worktree::{iter, BuiltinSubmoduleStatus};
310-
use crate::status::{index_worktree, Platform};
315+
use crate::status::{index_worktree, OwnedOrStaticAtomic, Platform};
311316
use crate::worktree::IndexPersistedOrInMemory;
312-
use std::sync::atomic::AtomicBool;
313-
use std::sync::Arc;
314317

315318
pub(super) enum ApplyChange {
316319
SetSizeToZero,
@@ -564,7 +567,6 @@ pub mod iter {
564567
Some(index) => index,
565568
};
566569

567-
let should_interrupt = Arc::new(AtomicBool::default());
568570
let skip_hash = self
569571
.repo
570572
.config
@@ -574,6 +576,7 @@ pub mod iter {
574576
.transpose()
575577
.with_lenient_default(self.repo.config.lenient_config)?
576578
.unwrap_or_default();
579+
let should_interrupt = self.should_interrupt.clone().unwrap_or_default();
577580
let submodule = BuiltinSubmoduleStatus::new(self.repo.clone().into_sync(), self.submodules)?;
578581
#[cfg(feature = "parallel")]
579582
{
@@ -726,10 +729,27 @@ pub mod iter {
726729
#[cfg(feature = "parallel")]
727730
impl Drop for super::Iter {
728731
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());
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: 31 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,33 @@ 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 { flag: Arc<AtomicBool>, private: bool },
23+
Shared(&'static AtomicBool),
24+
}
25+
26+
impl Default for OwnedOrStaticAtomic {
27+
fn default() -> Self {
28+
OwnedOrStaticAtomic::Owned {
29+
flag: Arc::new(AtomicBool::default()),
30+
private: true,
31+
}
32+
}
33+
}
34+
35+
impl Deref for OwnedOrStaticAtomic {
36+
type Target = std::sync::atomic::AtomicBool;
37+
38+
fn deref(&self) -> &Self::Target {
39+
match self {
40+
OwnedOrStaticAtomic::Owned { flag, .. } => flag,
41+
OwnedOrStaticAtomic::Shared(flag) => flag,
42+
}
43+
}
1444
}
1545

1646
/// How to obtain a submodule's status.
@@ -71,6 +101,7 @@ impl Repository {
71101
progress,
72102
index: None,
73103
submodules: Submodule::default(),
104+
should_interrupt: None,
74105
index_worktree_options: index_worktree::Options {
75106
sorting: None,
76107
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)