11= Signal Handling in Swift
22François Lamboley <
francois [email protected] >
33
4- This package provides a Swift syntax for the low-level C `sigaction` function
5- and a way to delay or cancel sigaction handlers.
4+ This package provides a Swift syntax for the low-level C `sigaction` function and a way to delay or cancel sigaction handlers.
65
7- It is compatible with macOS and Linux. Read the documentation carefully before
8- using the sigaction handler delaying capabilities.
6+ It is compatible with macOS and Linux. Read the documentation carefully before using the sigaction handler delaying capabilities.
97
108== Example of Use
119
@@ -34,8 +32,7 @@ let delayedSigaction = try SigactionDelayer_Unsig.registerDelayedSigaction(Signa
3432})
3533----
3634
37- With both examples in the same code, when the program receives the terminated
38- signal (15 on macOS), the following will be logged:
35+ With both examples in the same code, when the program receives the terminated signal (15 on macOS), the following will be logged:
3936
4037[source,text]
4138----
@@ -46,94 +43,75 @@ Handling signal SIGTERM from sigaction
4643
4744== How Does Delaying the Sigaction Work?
4845
49- Delaying a signal is an unusual process and you should be aware of the inner
50- workings of this method before using it.
46+ Delaying a signal is an unusual process and you should be aware of the inner workings of this method before using it.
5147
52- We have two delaying strategies. One sets the signal as ignored until the signal
53- is allowed to go through (`SigactionDelayer_Unsig`); the other blocks the
54- incoming signal until the signal is allowed to go through
55- (`SigactionDelayer_Block`).
48+ We have two delaying strategies.
49+ One sets the signal as ignored until the signal is allowed to go through (`SigactionDelayer_Unsig`);
50+ the other blocks the incoming signal until the signal is allowed to go through (`SigactionDelayer_Block`).
5651
5752Both strategies have pros and cons.
5853
59- While the signal is blocked or ignored, it is also monitored using `libdispatch`
60- (aka. GCD), which itself monitors the signals using `kqueue` on BSD and
61- `signalfd` on Linux. This allows unblocking the signal or setting the sigaction
62- handler when needed.
54+ While the signal is blocked or ignored, it is also monitored using `libdispatch` (aka. GCD),
55+ which itself monitors the signals using `kqueue` on BSD and `signalfd` on Linux.
56+ This allows unblocking the signal or setting the sigaction handler when needed.
6357
64- **Important caveat of both methods**: `libdispatch` can only detect signals sent
65- to the whole process, not threads. A delayed signal sent to a thread is thus
66- blocked or ignored forever.
58+ **Important caveat of both methods**:
59+ `libdispatch` can only detect signals sent to the whole process, not threads.
60+ A delayed signal sent to a thread is thus blocked or ignored forever.
6761
6862=== Details of the Unsigaction Strategy (`SigactionDelayer_Unsig`)
6963
7064This is the recommended method.
7165
72- This method does not require any particular bootstrap. You can delay a signal
73- whenever you want.
66+ This method does not require any particular bootstrap.
67+ You can delay a signal whenever you want.
7468
75- Once a signal has been registered for delay though, the sigaction should not be
76- manually changed. (Exception being made of the `install` method provided by the
77- `Sigaction` struct in this project, which is aware of the possible unsigactions
78- on the signal and can update them accordingly.)
69+ Once a signal has been registered for delay though, the sigaction should not be manually changed.
70+ (Exception being made of the `install` method provided by the `Sigaction` struct in this project,
71+ which is aware of the possible unsigactions on the signal and can update them accordingly.)
7972
8073When a signal is first registered for delay a few things happen:
8174
8275* The sigaction handler is saved in an internal structure;
8376* The signal is set to be ignored;
84- * A dedicated thread is spawned (if not already spawned), which unblocks all
85- signals;
77+ * A dedicated thread is spawned (if not already spawned), which unblocks all signals;
8678* A dispatch source is created to monitor the incoming signal using GCD.
8779
8880Then, when a signal is received, this happens:
8981
9082* `libdispatch` notifies the sigaction delayer, which will in turn
91- * Reinstall temporarily the saved sigaction for the signal (after the clients
92- say it’s ok), and
93- * Send the signal to the dedicated thread. This triggers the sigaction but does
94- not notify `libdispatch`.
83+ * Reinstall temporarily the saved sigaction for the signal (after the clients say it’s ok), and
84+ * Send the signal to the dedicated thread. This triggers the sigaction but does not notify `libdispatch`.
9585* Finally, the delayer sets the signal back to being ignored.
9686
9787**Caveats of this method**:
9888
99- * There is a non-avoidable race-condition (AFAICT) between the time the signal
100- is sent and set back to ignored;
89+ * There is a non-avoidable race-condition (AFAICT) between the time the signal is sent and set back to ignored;
10190* The signal that is sent back has lost the siginfo of the original signal;
102- * On Linux signal delaying is fragile. See Linux caveat of the blocking strategy
103- for more information.
91+ * On Linux signal delaying is fragile. See Linux caveat of the blocking strategy for more information.
10492
10593=== Details of the Blocking Strategy (`SigactionDelayer_Block`)
10694
107- You must bootstrap this method before using it, giving the bootstrap method all
108- the signals you’ll want to delay, _before any threads are created_.
109- The bootstrap will first block all the given signals on the current (main)
110- thread, then spawn a thread on which all these signals will be unblocked.
95+ You must bootstrap this method before using it, giving the bootstrap method all the signals you’ll want to delay, _before any threads are created_.
96+ The bootstrap will first block all the given signals on the current (main) thread,
97+ then spawn a thread on which all these signals will be unblocked.
11198
112- This allows our dedicated thread to be the only one allowed to receive the
113- signals that are to be monitored.
99+ This allows our dedicated thread to be the only one allowed to receive the signals that are to be monitored.
114100
115- When a signal is registered for delay, the delayer will block the signal on the
116- dedicated thread too!
101+ When a signal is registered for delay, the delayer will block the signal on the dedicated thread too!
117102
118- When the signal is received, `libdispatch` will notify the delayer, which will
119- unblock the signal, thus allowing it to be delivered.
103+ When the signal is received, `libdispatch` will notify the delayer, which will unblock the signal, thus allowing it to be delivered.
120104
121105**Caveats of this method**:
122106
123- * On macOS, when a signal blocked on all threads is received, it seems to be
124- assigned to an arbitrary thread. Unblocking the signal on another thread will
125- not unblock it at all. To workaround this problem we check if the signal is
126- pending on the dedicated thread before unblocking it. If it is not, we send the
127- signal to our thread, thus losing the sigaction again, exactly like when using
128- the unsigaction strategy. Plus the original signal will stay pending on the
129- affected thread forever.
130- * On Linux, there is an issue where contrary to what the man page says,
131- `libdispatch` https://github.com/apple/swift-corelibs-libdispatch/pull/560[does
132- modify the sigaction of a signal when a dispatch source for this signal is first
133- installed].
134- So in theory this strategy should not work (and to be honest, the other one
135- should not either). However, it has been noticed that changing the sigaction
136- _after_ the signal source has been installed is enough to avoid this problem. So
137- we save the sigaction before installing the signal source, then restore it after
138- the source is installed, and we’re good. This solution seems fragile though, and
139- might break in the future, or not even work reliably right now.
107+ * On macOS, when a signal blocked on all threads is received, it seems to be assigned to an arbitrary thread.
108+ Unblocking the signal on another thread will not unblock it at all.
109+ To workaround this problem we check if the signal is pending on the dedicated thread before unblocking it.
110+ If it is not, we send the signal to our thread, thus losing the sigaction again, exactly like when using the unsigaction strategy.
111+ Plus the original signal will stay pending on the affected thread forever.
112+ * On Linux, there is an issue where contrary to what the man page says, `libdispatch`
113+ https://github.com/apple/swift-corelibs-libdispatch/pull/560[does modify the sigaction of a signal when a dispatch source for this signal is first installed].
114+ So in theory this strategy should not work (and to be honest, the other one should not either).
115+ However, it has been noticed that changing the sigaction _after_ the signal source has been installed is enough to avoid this problem.
116+ So we save the sigaction before installing the signal source, then restore it after the source is installed, and we’re good.
117+ This solution seems fragile though, and might break in the future, or not even work reliably right now.
0 commit comments