Skip to content

Commit f3038db

Browse files
committed
Add explanation of the signal delaying
1 parent b70aa87 commit f3038db

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

Readme.adoc

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,81 @@ Handling signal SIGTERM from sigaction
4242
----
4343

4444
== How Does Delaying the Sigaction Work?
45+
46+
Delaying a signal is an unusual process and you should be aware of the inner
47+
workings of this method before using it.
48+
49+
We have two delaying strategies. One sets the signal as ignored until the signal
50+
is allowed to go through (`SigactionDelayer_Unsig`); the other blocks the
51+
incoming signal until the signal is allowed to go through
52+
(`SigactionDelayer_Block`).
53+
54+
Both strategies have pros and cons.
55+
56+
While the signal is blocked or ignored, it is also monitored using `libdispatch`
57+
(aka. GCD), which itself monitors the signals using `kqueue` on BSD and
58+
`signalfd` on Linux. This allows unblocking the signal or setting the sigaction
59+
handler when needed.
60+
61+
**Important caveat of both methods**: `libdispatch` can only detect signals sent
62+
to the whole process, not threads. A delayed signal sent to a thread is thus
63+
blocked or ignored forever.
64+
65+
=== Details of the Unsigaction Strategy (`SigactionDelayer_Unsig`)
66+
67+
This is the recommended method.
68+
69+
This method does not require any particular bootstrap. You can delay a signal
70+
whenever you want.
71+
72+
Once a signal has been registered for delay though, the sigaction should not be
73+
manually changed. (Exception being made of the `install` method provided by the
74+
`Sigaction` struct in this project, which is aware of the possible unsigactions
75+
on the signal and can update them accordingly.)
76+
77+
When a signal is first registered for delay a few things happen:
78+
79+
* The sigaction handler is saved in an internal structure;
80+
* The signal is set to be ignored;
81+
* A dedicated thread is spawned (if not already spawned), which unblocks all
82+
signals;
83+
* A dispatch source is created to monitor the incoming signal using GCD.
84+
85+
Then, when a signal is received, this happens:
86+
87+
* `libdispatch` notifies the sigaction delayer, which will in turn
88+
* Reinstall temporarily the saved sigaction for the signal (after the clients
89+
say it’s ok), and
90+
* Send the signal to the dedicated thread. This triggers the sigaction but does
91+
not notify `libdispatch`.
92+
* Finally, the delayer sets the signal back to being ignored.
93+
94+
**Caveats of this method**:
95+
96+
* There is a non-avoidable race-condition (AFAICT) between the time the signal
97+
is sent and set back to ignored;
98+
* The signal that is sent back has lost the siginfo of the original signal.
99+
100+
=== Details of the Blocking Strategy (`SigactionDelayer_Block`)
101+
102+
You must bootstrap this method before using it, giving the bootstrap method all
103+
the signals you’ll want to delay, _before any threads are created_.
104+
The bootstrap will first block all the given signals on the current (main)
105+
thread, then spawn a thread on which all these signals will be unblocked.
106+
107+
This allows our dedicated thread to be the only one allowed to receive the
108+
signals that are to be monitored.
109+
110+
When a signal is registered for delay, the delayer will block the signal on the
111+
dedicated thread too!
112+
113+
When the signal is received, `libdispatch` will notify the delayer, which will
114+
unblock the signal, thus allowing it to be delivered.
115+
116+
**Caveat of this method**: On macOS, when a signal blocked on all threads is
117+
received, it seems to be affected to an arbitrary thread. Unblocking the signal
118+
on another thread will not unblock it at all. To workaround this problem we
119+
check if the signal is pending on the dedicated thread before unblocking it. If
120+
it is not, we send the signal to our thread, thus losing the sigaction again,
121+
exactly like when using the unsigaction strategy. Plus the original signal will
122+
stay pending on the affected thread forever.

0 commit comments

Comments
 (0)