History

Merge branch 'handlers-mt'

Sebastian Thiel committed 3 years ago

Commit f584d762 parents d365eb3 a201f0d9 files · +133 −36Browse files

Unified · Split

9 files
  1. Cargo.lock +1 −0
  2. gix/Cargo.toml +3 −0
  3. gix/examples/clone.rs +1 −1
  4. gix/examples/interrupt-handler-allows-graceful-shutdown.rs +1 −1
  5. gix/examples/reversible-interrupt-handlers.rs +1 −1
  6. gix/src/interrupt.rs +66 −31
  7. gix/tests/interrupt.rs +58 −0
  8. src/plumbing/main.rs +1 −1
  9. src/porcelain/main.rs +1 −1
Cargo.lock+1−0
@@ -1205,6 +1205,7 @@ dependencies = [ · 1204 unmodified lines
12051205 "is_ci",
12061206 "log",
12071207 "once_cell",
1208+ "parking_lot",
12081209 "prodash 25.0.1",
12091210 "regex",
12101211 "reqwest",
gix/Cargo.toml+3−0
@@ -191,6 +191,9 @@ regex = { version = "1.6.0", optional = true, default-features = false, features · 190 unmodified lines
191191 # For internal use to allow pure-Rust builds without openssl.
192192 reqwest-for-configuration-only = { package = "reqwest", version = "0.11.13", default-features = false, optional = true }
193193
194+# for `interrupt` module
195+parking_lot = "0.12.1"
196+
194197 document-features = { version = "0.2.0", optional = true }
195198
196199 [target.'cfg(target_vendor = "apple")'.dependencies]
gix/examples/clone.rs+1−1
@@ -11,7 +11,7 @@ fn main() -> anyhow::Result<()> { · 10 unmodified lines
1111 .nth(2)
1212 .context("The second argument is the directory to clone the repository into")?;
1313
14− gix::interrupt::init_handler(|| {})?;
14+ gix::interrupt::init_handler(1, || {})?;
1515 std::fs::create_dir_all(&dst)?;
1616 let url = gix::url::parse(repo_url.to_str().unwrap().into())?;
1717
gix/examples/interrupt-handler-allows-graceful-shutdown.rs+1−1
@@ -3,7 +3,7 @@ use std::path::Path; · 2 unmodified lines
33 use gix_tempfile::{AutoRemove, ContainingDirectory};
44
55 fn main() -> anyhow::Result<()> {
6− gix::interrupt::init_handler(|| {})?;
6+ gix::interrupt::init_handler(1, || {})?;
77 eprintln!("About to emit the first term signal");
88 let tempfile_path = Path::new("example-file.tmp");
99 let _keep_tempfile = gix_tempfile::mark_at(tempfile_path, ContainingDirectory::Exists, AutoRemove::Tempfile)?;
gix/examples/reversible-interrupt-handlers.rs+1−1
@@ -1,6 +1,6 @@
11 fn main() -> anyhow::Result<()> {
22 {
3− let _deregister_on_drop = gix::interrupt::init_handler(|| {})?.auto_deregister();
3+ let _deregister_on_drop = gix::interrupt::init_handler(1, || {})?.auto_deregister();
44 }
55 eprintln!("About to emit the first term signal, which acts just like a normal one");
66 signal_hook::low_level::raise(signal_hook::consts::SIGTERM)?;
gix/src/interrupt.rs+66−31
@@ -8,46 +8,62 @@ · 7 unmodified lines
88 mod init {
99 use std::{
1010 io,
11− sync::atomic::{AtomicBool, AtomicUsize, Ordering},
11+ sync::atomic::{AtomicUsize, Ordering},
1212 };
1313
14− static IS_INITIALIZED: AtomicBool = AtomicBool::new(false);
14+ static DEREGISTER_COUNT: AtomicUsize = AtomicUsize::new(0);
15+ static REGISTERED_HOOKS: once_cell::sync::Lazy<parking_lot::Mutex<Vec<(i32, signal_hook::SigId)>>> =
16+ once_cell::sync::Lazy::new(Default::default);
17+ static DEFAULT_BEHAVIOUR_HOOKS: once_cell::sync::Lazy<parking_lot::Mutex<Vec<signal_hook::SigId>>> =
18+ once_cell::sync::Lazy::new(Default::default);
1519
20+ /// A type to help deregistering hooks registered with [`init_handler`](super::init_handler());
1621 #[derive(Default)]
17− pub struct Deregister(Vec<(i32, signal_hook::SigId)>);
22+ pub struct Deregister {
23+ do_reset: bool,
24+ }
1825 pub struct AutoDeregister(Deregister);
1926
2027 impl Deregister {
21− /// Remove all previously registered handlers, and assure the default behaviour is reinstated.
28+ /// Remove all previously registered handlers, and assure the default behaviour is reinstated, if this is the last available instance.
2229 ///
2330 /// Note that only the instantiation of the default behaviour can fail.
2431 pub fn deregister(self) -> std::io::Result<()> {
25− if self.0.is_empty() {
32+ let mut hooks = REGISTERED_HOOKS.lock();
33+ let count = DEREGISTER_COUNT.fetch_sub(1, Ordering::SeqCst);
34+ if count > 1 || hooks.is_empty() {
2635 return Ok(());
2736 }
28− static REINSTATE_DEFAULT_BEHAVIOUR: AtomicBool = AtomicBool::new(true);
29− for (_, hook_id) in &self.0 {
37+ if self.do_reset {
38+ super::reset();
39+ }
40+ for (_, hook_id) in hooks.iter() {
3041 signal_hook::low_level::unregister(*hook_id);
3142 }
32− IS_INITIALIZED.store(false, Ordering::SeqCst);
33− if REINSTATE_DEFAULT_BEHAVIOUR
34− .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| Some(false))
35− .expect("always returns value")
36− {
37− for (sig, _) in self.0 {
38− // # SAFETY
39− // * we only call a handler that is specifically designed to run in this environment.
40− #[allow(unsafe_code)]
41− unsafe {
42− signal_hook::low_level::register(sig, move || {
43− signal_hook::low_level::emulate_default_handler(sig).ok();
44− })?;
45− }
43+
44+ let hooks = hooks.drain(..);
45+ let mut default_hooks = DEFAULT_BEHAVIOUR_HOOKS.lock();
46+ // Even if dropped, `drain(..)` clears the vec which is a must.
47+ for (sig, _) in hooks {
48+ // # SAFETY
49+ // * we only register a handler that is specifically designed to run in this environment.
50+ #[allow(unsafe_code)]
51+ unsafe {
52+ default_hooks.push(signal_hook::low_level::register(sig, move || {
53+ signal_hook::low_level::emulate_default_handler(sig).ok();
54+ })?);
4655 }
4756 }
4857 Ok(())
4958 }
5059
60+ /// If called with `toggle` being `true`, when actually deregistering, we will also reset the trigger by
61+ /// calling [`reset()`](super::reset()).
62+ pub fn with_reset(mut self, toggle: bool) -> Self {
63+ self.do_reset = toggle;
64+ self
65+ }
66+
5167 /// Return a type that deregisters all installed signal handlers on drop.
5268 pub fn auto_deregister(self) -> AutoDeregister {
5369 AutoDeregister(self)
@@ -60,20 +76,33 @@ mod init { · 6 unmodified lines
6076 }
6177 }
6278
63− /// Initialize a signal handler to listen to SIGINT and SIGTERM and trigger our [`trigger()`][super::trigger()] that way.
64− /// Also trigger `interrupt()` which promises to never use a Mutex, allocate or deallocate.
79+ /// Initialize a signal handler to listen to SIGINT and SIGTERM and trigger our [`trigger()`](super::trigger()) that way.
80+ /// Also trigger `interrupt()` which promises to never use a Mutex, allocate or deallocate, or do anything else that's blocking.
81+ /// Use `grace_count` to determine how often the termination signal can be received before it's terminal, e.g. 1 would only terminate
82+ /// the application the second time the signal is received.
83+ /// Note that only the `grace_count` and `interrupt` of the first call are effective, all others will be ignored.
84+ ///
85+ /// Use the returned `Deregister` type to explicitly deregister hooks, or to do so automatically.
6586 ///
6687 /// # Note
6788 ///
6889 /// It will abort the process on second press and won't inform the user about this behaviour either as we are unable to do so without
6990 /// deadlocking even when trying to write to stderr directly.
70− pub fn init_handler(interrupt: impl Fn() + Send + Sync + Clone + 'static) -> io::Result<Deregister> {
71− if IS_INITIALIZED
72− .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| Some(true))
73− .expect("always returns value")
74− {
75− return Err(io::Error::new(io::ErrorKind::Other, "Already initialized"));
91+ pub fn init_handler(
92+ grace_count: usize,
93+ interrupt: impl Fn() + Send + Sync + Clone + 'static,
94+ ) -> io::Result<Deregister> {
95+ let prev_count = DEREGISTER_COUNT.fetch_add(1, Ordering::SeqCst);
96+ if prev_count != 0 {
97+ // Try to obtain the lock before we return just to wait for the signals to actually be registered.
98+ let _guard = REGISTERED_HOOKS.lock();
99+ return Ok(Deregister::default());
100+ }
101+ let mut guard = REGISTERED_HOOKS.lock();
102+ if !guard.is_empty() {
103+ return Ok(Deregister::default());
76104 }
105+
77106 let mut hooks = Vec::with_capacity(signal_hook::consts::TERM_SIGNALS.len());
78107 for sig in signal_hook::consts::TERM_SIGNALS {
79108 // # SAFETY
@@ -88,7 +117,7 @@ mod init { · 8 unmodified lines
88117 INTERRUPT_COUNT.store(0, Ordering::SeqCst);
89118 }
90119 let msg_idx = INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst);
91− if msg_idx == 1 {
120+ if msg_idx == grace_count {
92121 gix_tempfile::registry::cleanup_tempfiles_signal_safe();
93122 signal_hook::low_level::emulate_default_handler(*sig).ok();
94123 }
@@ -98,13 +127,19 @@ mod init { · 3 unmodified lines
98127 hooks.push((*sig, hook_id));
99128 }
100129 }
130+ for hook_id in DEFAULT_BEHAVIOUR_HOOKS.lock().drain(..) {
131+ signal_hook::low_level::unregister(hook_id);
132+ }
101133
102134 // This means that they won't setup a handler allowing us to call them right before we actually abort.
103135 gix_tempfile::signal::setup(gix_tempfile::signal::handler::Mode::None);
104136
105− Ok(Deregister(hooks))
137+ *guard = hooks;
138+ Ok(Deregister::default())
106139 }
107140 }
141+pub use init::Deregister;
142+
108143 use std::{
109144 io,
110145 sync::atomic::{AtomicBool, Ordering},
gix/tests/interrupt.rs+58−0
@@ -0,0 +1,58 @@
1+use signal_hook::consts::SIGTERM;
2+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
3+
4+#[test]
5+fn multi_registration() -> gix_testtools::Result {
6+ static V1: AtomicUsize = AtomicUsize::new(0);
7+ static V2: AtomicBool = AtomicBool::new(false);
8+
9+ let reg1 = gix::interrupt::init_handler(3, || {
10+ V1.fetch_add(1, Ordering::SeqCst);
11+ })
12+ .expect("succeeds");
13+ assert!(!gix::interrupt::is_triggered());
14+ assert_eq!(V1.load(Ordering::Relaxed), 0);
15+ let reg2 =
16+ gix::interrupt::init_handler(2, || V2.store(true, Ordering::SeqCst)).expect("multi-initialization is OK");
17+ assert!(!V2.load(Ordering::Relaxed));
18+
19+ signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
20+ assert!(gix::interrupt::is_triggered(), "this happens automatically");
21+ assert_eq!(V1.load(Ordering::Relaxed), 1, "the first trigger is invoked");
22+ assert!(!V2.load(Ordering::Relaxed), "the second trigger was ignored");
23+
24+ reg1.deregister()?;
25+ signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
26+ assert_eq!(V1.load(Ordering::Relaxed), 2, "the first trigger is still invoked");
27+
28+ assert!(gix::interrupt::is_triggered(), "this happens automatically");
29+ // now the registration is actually removed.
30+ reg2.with_reset(true).deregister()?;
31+ assert!(
32+ !gix::interrupt::is_triggered(),
33+ "the deregistration succeeded and this is an optional side-effect"
34+ );
35+
36+ let reg1 = gix::interrupt::init_handler(3, || {
37+ V1.fetch_add(1, Ordering::SeqCst);
38+ })
39+ .expect("succeeds");
40+ assert_eq!(V1.load(Ordering::Relaxed), 2, "nothing changed yet");
41+ let reg2 =
42+ gix::interrupt::init_handler(2, || V2.store(true, Ordering::SeqCst)).expect("multi-initialization is OK");
43+ assert!(!V2.load(Ordering::Relaxed));
44+
45+ signal_hook::low_level::raise(SIGTERM).expect("signal can be raised");
46+ assert_eq!(V1.load(Ordering::Relaxed), 3, "the first trigger is invoked");
47+ assert!(!V2.load(Ordering::Relaxed), "the second trigger was ignored");
48+
49+ reg2.auto_deregister();
50+ reg1.with_reset(true).auto_deregister();
51+
52+ assert!(
53+ !gix::interrupt::is_triggered(),
54+ "the deregistration succeeded and this is an optional side-effect"
55+ );
56+
57+ Ok(())
58+}
src/plumbing/main.rs+1−1
@@ -129,7 +129,7 @@ pub fn main() -> Result<()> { · 128 unmodified lines
129129 let auto_verbose = !progress && !args.no_verbose;
130130
131131 let should_interrupt = Arc::new(AtomicBool::new(false));
132− gix::interrupt::init_handler({
132+ gix::interrupt::init_handler(1, {
133133 let should_interrupt = Arc::clone(&should_interrupt);
134134 move || should_interrupt.store(true, Ordering::SeqCst)
135135 })?;
src/porcelain/main.rs+1−1
@@ -20,7 +20,7 @@ pub fn main() -> Result<()> { · 19 unmodified lines
2020 time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound);
2121 }
2222 let should_interrupt = Arc::new(AtomicBool::new(false));
23− gix::interrupt::init_handler({
23+ gix::interrupt::init_handler(1, {
2424 let should_interrupt = Arc::clone(&should_interrupt);
2525 move || should_interrupt.store(true, Ordering::SeqCst)
2626 })?;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t