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
1205 "is_ci",1205 "is_ci",
1206 "log",1206 "log",
1207 "once_cell",1207 "once_cell",
1208 "parking_lot",
1208 "prodash 25.0.1",1209 "prodash 25.0.1",
1209 "regex",1210 "regex",
1210 "reqwest",1211 "reqwest",
gix/Cargo.toml+3−0
@@ -191,6 +191,9 @@ regex = { version = "1.6.0", optional = true, default-features = false, features · 190 unmodified lines
191# For internal use to allow pure-Rust builds without openssl.191# For internal use to allow pure-Rust builds without openssl.
192reqwest-for-configuration-only = { package = "reqwest", version = "0.11.13", default-features = false, optional = true }192reqwest-for-configuration-only = { package = "reqwest", version = "0.11.13", default-features = false, optional = true }
193 193
194# for `interrupt` module
195parking_lot = "0.12.1"
196
194document-features = { version = "0.2.0", optional = true }197document-features = { version = "0.2.0", optional = true }
195 198
196[target.'cfg(target_vendor = "apple")'.dependencies]199[target.'cfg(target_vendor = "apple")'.dependencies]
gix/examples/clone.rs+1−1
@@ -11,7 +11,7 @@ fn main() -> anyhow::Result<()> { · 10 unmodified lines
11 .nth(2)11 .nth(2)
12 .context("The second argument is the directory to clone the repository into")?;12 .context("The second argument is the directory to clone the repository into")?;
13 13
14 gix::interrupt::init_handler(|| {})?;14 gix::interrupt::init_handler(1, || {})?;
15 std::fs::create_dir_all(&dst)?;15 std::fs::create_dir_all(&dst)?;
16 let url = gix::url::parse(repo_url.to_str().unwrap().into())?;16 let url = gix::url::parse(repo_url.to_str().unwrap().into())?;
17 17
gix/examples/interrupt-handler-allows-graceful-shutdown.rs+1−1
@@ -3,7 +3,7 @@ use std::path::Path; · 2 unmodified lines
3use gix_tempfile::{AutoRemove, ContainingDirectory};3use gix_tempfile::{AutoRemove, ContainingDirectory};
4 4
5fn main() -> anyhow::Result<()> {5fn main() -> anyhow::Result<()> {
6 gix::interrupt::init_handler(|| {})?;6 gix::interrupt::init_handler(1, || {})?;
7 eprintln!("About to emit the first term signal");7 eprintln!("About to emit the first term signal");
8 let tempfile_path = Path::new("example-file.tmp");8 let tempfile_path = Path::new("example-file.tmp");
9 let _keep_tempfile = gix_tempfile::mark_at(tempfile_path, ContainingDirectory::Exists, AutoRemove::Tempfile)?;9 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 @@
1fn main() -> anyhow::Result<()> {1fn main() -> anyhow::Result<()> {
2 {2 {
3 let _deregister_on_drop = gix::interrupt::init_handler(|| {})?.auto_deregister();3 let _deregister_on_drop = gix::interrupt::init_handler(1, || {})?.auto_deregister();
4 }4 }
5 eprintln!("About to emit the first term signal, which acts just like a normal one");5 eprintln!("About to emit the first term signal, which acts just like a normal one");
6 signal_hook::low_level::raise(signal_hook::consts::SIGTERM)?;6 signal_hook::low_level::raise(signal_hook::consts::SIGTERM)?;
gix/src/interrupt.rs+66−31
@@ -8,46 +8,62 @@ · 7 unmodified lines
8mod init {8mod init {
9 use std::{9 use std::{
10 io,10 io,
11 sync::atomic::{AtomicBool, AtomicUsize, Ordering},11 sync::atomic::{AtomicUsize, Ordering},
12 };12 };
13 13
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);
15 19
20 /// A type to help deregistering hooks registered with [`init_handler`](super::init_handler());
16 #[derive(Default)]21 #[derive(Default)]
17 pub struct Deregister(Vec<(i32, signal_hook::SigId)>);22 pub struct Deregister {
23 do_reset: bool,
24 }
18 pub struct AutoDeregister(Deregister);25 pub struct AutoDeregister(Deregister);
19 26
20 impl Deregister {27 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.
22 ///29 ///
23 /// Note that only the instantiation of the default behaviour can fail.30 /// Note that only the instantiation of the default behaviour can fail.
24 pub fn deregister(self) -> std::io::Result<()> {31 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() {
26 return Ok(());35 return Ok(());
27 }36 }
28 static REINSTATE_DEFAULT_BEHAVIOUR: AtomicBool = AtomicBool::new(true);37 if self.do_reset {
29 for (_, hook_id) in &self.0 {38 super::reset();
39 }
40 for (_, hook_id) in hooks.iter() {
30 signal_hook::low_level::unregister(*hook_id);41 signal_hook::low_level::unregister(*hook_id);
31 }42 }
32 IS_INITIALIZED.store(false, Ordering::SeqCst);43
33 if REINSTATE_DEFAULT_BEHAVIOUR44 let hooks = hooks.drain(..);
34 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| Some(false))45 let mut default_hooks = DEFAULT_BEHAVIOUR_HOOKS.lock();
35 .expect("always returns value")46 // Even if dropped, `drain(..)` clears the vec which is a must.
36 {47 for (sig, _) in hooks {
37 for (sig, _) in self.0 {48 // # SAFETY
38 // # SAFETY49 // * we only register a handler that is specifically designed to run in this environment.
39 // * we only call a handler that is specifically designed to run in this environment.50 #[allow(unsafe_code)]
40 #[allow(unsafe_code)]51 unsafe {
41 unsafe {52 default_hooks.push(signal_hook::low_level::register(sig, move || {
42 signal_hook::low_level::register(sig, move || {53 signal_hook::low_level::emulate_default_handler(sig).ok();
43 signal_hook::low_level::emulate_default_handler(sig).ok();54 })?);
44 })?;
45 }
46 }55 }
47 }56 }
48 Ok(())57 Ok(())
49 }58 }
50 59
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
51 /// Return a type that deregisters all installed signal handlers on drop.67 /// Return a type that deregisters all installed signal handlers on drop.
52 pub fn auto_deregister(self) -> AutoDeregister {68 pub fn auto_deregister(self) -> AutoDeregister {
53 AutoDeregister(self)69 AutoDeregister(self)
@@ -60,20 +76,33 @@ mod init { · 6 unmodified lines
60 }76 }
61 }77 }
62 78
63 /// Initialize a signal handler to listen to SIGINT and SIGTERM and trigger our [`trigger()`][super::trigger()] that way.79 /// 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.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.
65 ///86 ///
66 /// # Note87 /// # Note
67 ///88 ///
68 /// 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 without89 /// 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
69 /// deadlocking even when trying to write to stderr directly.90 /// deadlocking even when trying to write to stderr directly.
70 pub fn init_handler(interrupt: impl Fn() + Send + Sync + Clone + 'static) -> io::Result<Deregister> {91 pub fn init_handler(
71 if IS_INITIALIZED92 grace_count: usize,
72 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| Some(true))93 interrupt: impl Fn() + Send + Sync + Clone + 'static,
73 .expect("always returns value")94 ) -> io::Result<Deregister> {
74 {95 let prev_count = DEREGISTER_COUNT.fetch_add(1, Ordering::SeqCst);
75 return Err(io::Error::new(io::ErrorKind::Other, "Already initialized"));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());
76 }104 }
105
77 let mut hooks = Vec::with_capacity(signal_hook::consts::TERM_SIGNALS.len());106 let mut hooks = Vec::with_capacity(signal_hook::consts::TERM_SIGNALS.len());
78 for sig in signal_hook::consts::TERM_SIGNALS {107 for sig in signal_hook::consts::TERM_SIGNALS {
79 // # SAFETY108 // # SAFETY
@@ -88,7 +117,7 @@ mod init { · 8 unmodified lines
88 INTERRUPT_COUNT.store(0, Ordering::SeqCst);117 INTERRUPT_COUNT.store(0, Ordering::SeqCst);
89 }118 }
90 let msg_idx = INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst);119 let msg_idx = INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst);
91 if msg_idx == 1 {120 if msg_idx == grace_count {
92 gix_tempfile::registry::cleanup_tempfiles_signal_safe();121 gix_tempfile::registry::cleanup_tempfiles_signal_safe();
93 signal_hook::low_level::emulate_default_handler(*sig).ok();122 signal_hook::low_level::emulate_default_handler(*sig).ok();
94 }123 }
@@ -98,13 +127,19 @@ mod init { · 3 unmodified lines
98 hooks.push((*sig, hook_id));127 hooks.push((*sig, hook_id));
99 }128 }
100 }129 }
130 for hook_id in DEFAULT_BEHAVIOUR_HOOKS.lock().drain(..) {
131 signal_hook::low_level::unregister(hook_id);
132 }
101 133
102 // This means that they won't setup a handler allowing us to call them right before we actually abort.134 // This means that they won't setup a handler allowing us to call them right before we actually abort.
103 gix_tempfile::signal::setup(gix_tempfile::signal::handler::Mode::None);135 gix_tempfile::signal::setup(gix_tempfile::signal::handler::Mode::None);
104 136
105 Ok(Deregister(hooks))137 *guard = hooks;
138 Ok(Deregister::default())
106 }139 }
107}140}
141pub use init::Deregister;
142
108use std::{143use std::{
109 io,144 io,
110 sync::atomic::{AtomicBool, Ordering},145 sync::atomic::{AtomicBool, Ordering},
gix/tests/interrupt.rs+58−0
@@ -0,0 +1,58 @@
1use signal_hook::consts::SIGTERM;
2use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
3
4#[test]
5fn 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
129 let auto_verbose = !progress && !args.no_verbose;129 let auto_verbose = !progress && !args.no_verbose;
130 130
131 let should_interrupt = Arc::new(AtomicBool::new(false));131 let should_interrupt = Arc::new(AtomicBool::new(false));
132 gix::interrupt::init_handler({132 gix::interrupt::init_handler(1, {
133 let should_interrupt = Arc::clone(&should_interrupt);133 let should_interrupt = Arc::clone(&should_interrupt);
134 move || should_interrupt.store(true, Ordering::SeqCst)134 move || should_interrupt.store(true, Ordering::SeqCst)
135 })?;135 })?;
src/porcelain/main.rs+1−1
@@ -20,7 +20,7 @@ pub fn main() -> Result<()> { · 19 unmodified lines
20 time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound);20 time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound);
21 }21 }
22 let should_interrupt = Arc::new(AtomicBool::new(false));22 let should_interrupt = Arc::new(AtomicBool::new(false));
23 gix::interrupt::init_handler({23 gix::interrupt::init_handler(1, {
24 let should_interrupt = Arc::clone(&should_interrupt);24 let should_interrupt = Arc::clone(&should_interrupt);
25 move || should_interrupt.store(true, Ordering::SeqCst)25 move || should_interrupt.store(true, Ordering::SeqCst)
26 })?;26 })?;

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