Skip to content

Commit 36d34bd

Browse files
committed
feat: add interrupt feature to reduce dependencies
1 parent 721c377 commit 36d34bd

File tree

6 files changed

+82
-65
lines changed

6 files changed

+82
-65
lines changed

gix/Cargo.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ default = ["max-performance-safe", "comfort", "basic", "extras"]
5151
basic = ["blob-diff", "revision", "index"]
5252

5353
## Various additional features and capabilities that are not necessarily part of what most users would need.
54-
extras = ["worktree-stream", "worktree-archive", "revparse-regex", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials"]
54+
extras = ["worktree-stream", "worktree-archive", "revparse-regex", "mailmap", "excludes", "attributes", "worktree-mutation", "credentials", "interrupt"]
5555

5656
## Various progress-related features that improve the look of progress message units.
5757
comfort = ["gix-features/progress-unit-bytes", "gix-features/progress-unit-human-numbers"]
@@ -61,6 +61,9 @@ comfort = ["gix-features/progress-unit-bytes", "gix-features/progress-unit-human
6161
#! A component is a distinct feature which may be comprised of one or more methods around a particular topic.
6262
#! Providers of libraries should only activate the components they need.
6363

64+
## Utilities for interrupting computations and cleaning up tempfiles.
65+
interrupt = ["dep:signal-hook", "gix-tempfile/signals"]
66+
6467
## Access to `.git/index` files.
6568
index = ["dep:gix-index"]
6669

@@ -192,7 +195,7 @@ gix-utils = { version = "^0.1.5", path = "../gix-utils" }
192195
gix-fs = { version = "^0.5.0", path = "../gix-fs" }
193196
gix-ref = { version = "^0.35.0", path = "../gix-ref" }
194197
gix-discover = { version = "^0.23.0", path = "../gix-discover" }
195-
gix-tempfile = { version = "^8.0.0", path = "../gix-tempfile", default-features = false, features = ["signals"] }
198+
gix-tempfile = { version = "^8.0.0", path = "../gix-tempfile", default-features = false }
196199
gix-lock = { version = "^8.0.0", path = "../gix-lock" }
197200
gix-validate = { version = "^0.8.0", path = "../gix-validate" }
198201
gix-sec = { version = "^0.9.0", path = "../gix-sec" }
@@ -241,7 +244,7 @@ gix-transport = { version = "^0.35.0", path = "../gix-transport", optional = tru
241244
# Just to get the progress-tree feature
242245
prodash = { version = "26.2", optional = true, default-features = false, features = ["progress-tree"] }
243246
once_cell = "1.14.0"
244-
signal-hook = { version = "0.3.9", default-features = false }
247+
signal-hook = { version = "0.3.9", default-features = false, optional = true }
245248
thiserror = "1.0.26"
246249
log = "0.4.14"
247250
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}

gix/examples/interrupt-handler-allows-graceful-shutdown.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
use std::path::Path;
2-
3-
use gix_tempfile::{AutoRemove, ContainingDirectory};
1+
#[cfg(not(feature = "interrupt"))]
2+
fn main() -> anyhow::Result<()> {
3+
anyhow::bail!("Needs 'interrupt' feature toggle to be enabled");
4+
}
45

6+
#[cfg(feature = "interrupt")]
57
fn main() -> anyhow::Result<()> {
8+
use gix_tempfile::{AutoRemove, ContainingDirectory};
69
gix::interrupt::init_handler(1, || {})?;
710
eprintln!("About to emit the first term signal");
8-
let tempfile_path = Path::new("example-file.tmp");
11+
let tempfile_path = std::path::Path::new("example-file.tmp");
912
let _keep_tempfile = gix_tempfile::mark_at(tempfile_path, ContainingDirectory::Exists, AutoRemove::Tempfile)?;
1013

1114
signal_hook::low_level::raise(signal_hook::consts::SIGTERM)?;

gix/examples/reversible-interrupt-handlers.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#[cfg(not(feature = "interrupt"))]
2+
fn main() -> anyhow::Result<()> {
3+
anyhow::bail!("Needs 'interrupt' feature toggle to be enabled");
4+
}
5+
6+
#[cfg(feature = "interrupt")]
17
fn main() -> anyhow::Result<()> {
28
{
39
let _deregister_on_drop = gix::interrupt::init_handler(1, || {})?.auto_deregister();

gix/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub use gix_url::Url;
122122
pub use gix_utils as utils;
123123
pub use hash::{oid, ObjectId};
124124

125+
#[cfg(feature = "interrupt")]
125126
pub mod interrupt;
126127

127128
mod ext;

gix/tests/interrupt.rs

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,62 @@
1-
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
2-
3-
use signal_hook::consts::SIGTERM;
4-
5-
#[test]
6-
fn multi_registration() -> gix_testtools::Result {
7-
static V1: AtomicUsize = AtomicUsize::new(0);
8-
static V2: AtomicBool = AtomicBool::new(false);
9-
10-
let reg1 = gix::interrupt::init_handler(3, || {
11-
V1.fetch_add(1, Ordering::SeqCst);
12-
})
13-
.expect("succeeds");
14-
assert!(!gix::interrupt::is_triggered());
15-
assert_eq!(V1.load(Ordering::Relaxed), 0);
16-
let reg2 =
17-
gix::interrupt::init_handler(2, || V2.store(true, Ordering::SeqCst)).expect("multi-initialization is OK");
18-
assert!(!V2.load(Ordering::Relaxed));
19-
20-
signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
21-
assert!(gix::interrupt::is_triggered(), "this happens automatically");
22-
assert_eq!(V1.load(Ordering::Relaxed), 1, "the first trigger is invoked");
23-
assert!(!V2.load(Ordering::Relaxed), "the second trigger was ignored");
24-
25-
reg1.deregister()?;
26-
signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
27-
assert_eq!(V1.load(Ordering::Relaxed), 2, "the first trigger is still invoked");
28-
29-
assert!(gix::interrupt::is_triggered(), "this happens automatically");
30-
// now the registration is actually removed.
31-
reg2.with_reset(true).deregister()?;
32-
assert!(
33-
!gix::interrupt::is_triggered(),
34-
"the deregistration succeeded and this is an optional side-effect"
35-
);
36-
37-
let reg1 = gix::interrupt::init_handler(3, || {
38-
V1.fetch_add(1, Ordering::SeqCst);
39-
})
40-
.expect("succeeds");
41-
assert_eq!(V1.load(Ordering::Relaxed), 2, "nothing changed yet");
42-
let reg2 =
43-
gix::interrupt::init_handler(2, || V2.store(true, Ordering::SeqCst)).expect("multi-initialization is OK");
44-
assert!(!V2.load(Ordering::Relaxed));
45-
46-
signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
47-
assert_eq!(V1.load(Ordering::Relaxed), 3, "the first trigger is invoked");
48-
assert!(!V2.load(Ordering::Relaxed), "the second trigger was ignored");
49-
50-
reg2.auto_deregister();
51-
reg1.with_reset(true).auto_deregister();
52-
53-
assert!(
54-
!gix::interrupt::is_triggered(),
55-
"the deregistration succeeded and this is an optional side-effect"
56-
);
57-
58-
Ok(())
1+
#[cfg(feature = "interrupt")]
2+
mod needs_feature {
3+
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
4+
5+
use signal_hook::consts::SIGTERM;
6+
7+
#[test]
8+
fn multi_registration() -> gix_testtools::Result {
9+
static V1: AtomicUsize = AtomicUsize::new(0);
10+
static V2: AtomicBool = AtomicBool::new(false);
11+
12+
let reg1 = gix::interrupt::init_handler(3, || {
13+
V1.fetch_add(1, Ordering::SeqCst);
14+
})
15+
.expect("succeeds");
16+
assert!(!gix::interrupt::is_triggered());
17+
assert_eq!(V1.load(Ordering::Relaxed), 0);
18+
let reg2 =
19+
gix::interrupt::init_handler(2, || V2.store(true, Ordering::SeqCst)).expect("multi-initialization is OK");
20+
assert!(!V2.load(Ordering::Relaxed));
21+
22+
signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
23+
assert!(gix::interrupt::is_triggered(), "this happens automatically");
24+
assert_eq!(V1.load(Ordering::Relaxed), 1, "the first trigger is invoked");
25+
assert!(!V2.load(Ordering::Relaxed), "the second trigger was ignored");
26+
27+
reg1.deregister()?;
28+
signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
29+
assert_eq!(V1.load(Ordering::Relaxed), 2, "the first trigger is still invoked");
30+
31+
assert!(gix::interrupt::is_triggered(), "this happens automatically");
32+
// now the registration is actually removed.
33+
reg2.with_reset(true).deregister()?;
34+
assert!(
35+
!gix::interrupt::is_triggered(),
36+
"the deregistration succeeded and this is an optional side-effect"
37+
);
38+
39+
let reg1 = gix::interrupt::init_handler(3, || {
40+
V1.fetch_add(1, Ordering::SeqCst);
41+
})
42+
.expect("succeeds");
43+
assert_eq!(V1.load(Ordering::Relaxed), 2, "nothing changed yet");
44+
let reg2 =
45+
gix::interrupt::init_handler(2, || V2.store(true, Ordering::SeqCst)).expect("multi-initialization is OK");
46+
assert!(!V2.load(Ordering::Relaxed));
47+
48+
signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
49+
assert_eq!(V1.load(Ordering::Relaxed), 3, "the first trigger is invoked");
50+
assert!(!V2.load(Ordering::Relaxed), "the second trigger was ignored");
51+
52+
reg2.auto_deregister();
53+
reg1.with_reset(true).auto_deregister();
54+
55+
assert!(
56+
!gix::interrupt::is_triggered(),
57+
"the deregistration succeeded and this is an optional side-effect"
58+
);
59+
60+
Ok(())
61+
}
5962
}

justfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ check:
127127
cargo check -p gix --no-default-features --features worktree-mutation --tests
128128
cargo check -p gix --no-default-features --features credentials --tests
129129
cargo check -p gix --no-default-features --features index --tests
130+
cargo check -p gix --no-default-features --features interrupt --tests
130131
cargo check -p gix --no-default-features
131132
cargo check -p gix-odb --features serde
132133
cargo check --no-default-features --features max-control

0 commit comments

Comments
 (0)