8
8
mod init {
9
9
use std:: {
10
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
21
#[ derive( Default ) ]
17
- pub struct Deregister ( Vec < ( i32 , signal_hook:: SigId ) > ) ;
22
+ pub struct Deregister {
23
+ do_reset : bool ,
24
+ }
18
25
pub struct AutoDeregister ( Deregister ) ;
19
26
20
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
30
/// Note that only the instantiation of the default behaviour can fail.
24
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
35
return Ok ( ( ) ) ;
27
36
}
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 ( ) {
30
41
signal_hook:: low_level:: unregister ( * hook_id) ;
31
42
}
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
+ } ) ?) ;
46
55
}
47
56
}
48
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
67
/// Return a type that deregisters all installed signal handlers on drop.
52
68
pub fn auto_deregister ( self ) -> AutoDeregister {
53
69
AutoDeregister ( self )
@@ -60,20 +76,33 @@ mod init {
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.
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.
65
86
///
66
87
/// # Note
67
88
///
68
89
/// 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
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 > {
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 ( ) ) ;
76
104
}
105
+
77
106
let mut hooks = Vec :: with_capacity ( signal_hook:: consts:: TERM_SIGNALS . len ( ) ) ;
78
107
for sig in signal_hook:: consts:: TERM_SIGNALS {
79
108
// # SAFETY
@@ -88,7 +117,7 @@ mod init {
88
117
INTERRUPT_COUNT . store ( 0 , Ordering :: SeqCst ) ;
89
118
}
90
119
let msg_idx = INTERRUPT_COUNT . fetch_add ( 1 , Ordering :: SeqCst ) ;
91
- if msg_idx == 1 {
120
+ if msg_idx == grace_count {
92
121
gix_tempfile:: registry:: cleanup_tempfiles_signal_safe ( ) ;
93
122
signal_hook:: low_level:: emulate_default_handler ( * sig) . ok ( ) ;
94
123
}
@@ -98,13 +127,19 @@ mod init {
98
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
134
// This means that they won't setup a handler allowing us to call them right before we actually abort.
103
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
}
141
+ pub use init:: Deregister ;
142
+
108
143
use std:: {
109
144
io,
110
145
sync:: atomic:: { AtomicBool , Ordering } ,
0 commit comments