@@ -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