Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
SignalHandlers.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Execution_SignalHandlers_h_
5#define _Stroika_Foundation_Execution_SignalHandlers_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <compare>
10#include <csignal>
11
12#include "Stroika/Foundation/Common/Common.h"
13#include "Stroika/Foundation/Containers/Mapping.h"
14#include "Stroika/Foundation/Containers/Set.h"
16#include "Stroika/Foundation/Execution/Signals.h"
18
19/**
20 * Description:
21 *
22 * This module defines support for POSIX (and std c++ defined) Signals (not to be confused
23 * with the 'Signals and slots' design pattern which is largely unrelated). (POSIX) Signals have all sorts of tricky
24 * rules for their manipulation/control. This doesn't eliminate those, but it makes most common cases much easier
25 * to handle safely. In particular, many cases of signals can be 'handled' not as signals (safe signal handlers).
26 *
27 *
28 * TODO:
29 * @todo http://stroika-bugs.sophists.com/browse/STK-467 Lose shared_ptr<> with SignalHandlerRegistry::SafeSignalsManager::Rep_
30 * and change semantics - assert or autodelete - safe handlers on destroy of safe signal mgr.
31 *
32 * @todo Small issue - AddSignalHandler versus SetSignalHandler (). This can be confusing. I had a bug
33 * which was we setup DEFAULT signal handlers, and then in the BasicUNIX Serviced code - did
34 * AddSignalHandler (SIGINT). Issue is that we process BOTH handlers - one to set an event
35 * object to cleanly shutdown and the other to HARD ABORT!
36 *
37 * This API encourages that mistake. I changed the Service code to use SetSignalHandler - but
38 * That has the default of being hard to debug/non-modular. Maybe have a "SetDefaultHandler"
39 * or "SetFallbackHandler" - and that is invoked ONLY if no others? Or maybe a property of all
40 * handlers?
41 *
42 * @todo Consider use of threadpool for SafeSignalsManager
43 * Good idea since we may need to handle signals quickly so as to not overflow
44 * fixed size incoming buffer.
45 *
46 * @todo Do overload (or some such) for (sa_sigaction)(int, siginfo_t *, void *); Allow
47 * these to be (more or less) interchangable with regular SignalHandler.
48 *
49 * @todo Consider adding "ThreadSignalHandler" facility - where we register a set of handlers
50 * that ONLY apply when the signal is sent to the given (argument with register) thread.
51 * If we do this - we will want to write cooperating code with the thread start/end
52 * stuff so these get cleared out appropriately.
53 * Consider how this might be useful for stuff like SIGPIPE handling?
54 */
55
57
58 /**
59 * A key feature of SignalHandler versus function<void(SignalID)> is that you can compare them (@see Function)
60 *
61 * Note that to do so, you must save the original SignalHandler you create to later remove it by value:
62 * creating another SignalHandler (even with the same arguments) may not compare as equal.
63 *
64 * Also, signal handlers come with a flag indicating that they are intended to be run in a 'safe' manner
65 * or a direct signal handling manner.
66 *
67 * \note BEWARE - these may be copied during invocation, which for 'direct' signal handers is a
68 * dangerous, finicky place. Copy must not do operations (like allocate memory) which would be
69 * unsafe during signal (direct) handling.
70 */
72 public:
73 /**
74 * A direct (eDirect) signal handler is invoked in the stack context in which the
75 * signal is delivered. Direct signal handlers are VERY UNSAFE and use carefully,
76 * since they can EASILY produce deadlocks.
77 *
78 * A 'safe' (eSafe) signal handler is run in a separate thread context.
79 *
80 * @see SignalHandlerRegistry::SafeSignalsManager.
81 *
82 * \note Common::DefaultNames<> supported
83 */
84 enum class Type {
85 eDirect,
86 eSafe,
87
88 eDEFAULT = eSafe,
89
90 Stroika_Define_Enum_Bounds (eDirect, eSafe)
91 };
92
93 public:
94 /**
95 */
96 static constexpr Type eDirect = Type::eDirect;
97
98 public:
99 /**
100 */
101 static constexpr Type eSafe = Type::eSafe;
102
103 public:
104 /**
105 * Any overload can be used for safe exception handlers, but only the noexcept overload may be used
106 * for 'direct' exception calls (as a documentation hint - no real need).
107 *
108 * \note https://stackoverflow.com/questions/38760784/how-will-c17-exception-specifier-type-system-work
109 * and the existing compiler vs2k17 15.9.3 - dont support this noexcept on function objects, so
110 * try again later.
111 */
112 SignalHandler () = delete;
113 SignalHandler (const SignalHandler&) = default;
114 SignalHandler (void (*signalHandler) (SignalID) noexcept, Type type = Type::eDEFAULT);
115 SignalHandler (void (*signalHandler) (SignalID), Type type = Type::eDEFAULT);
116 SignalHandler (const Function<void (SignalID)>& signalHandler, Type type = Type::eDEFAULT);
117
118 public:
119 nonvirtual Type GetType () const;
120
121 public:
122 /**
123 * Invoke the actual signal handler.
124 */
125 nonvirtual void operator() (SignalID i) const;
126
127 public:
128 /**
129 */
130 nonvirtual Characters::String ToString () const;
131
132 public:
133 /**
134 */
135 nonvirtual strong_ordering operator<=> (const SignalHandler& rhs) const = default;
136
137 private:
138 Type fType_;
139 Function<void (SignalID)> fCall_;
140 };
141
142 /**
143 * SignalHandlerRegistry is a singleton object. If used - it itself registers signal handlers
144 * for each supported signal.
145 *
146 * The user can then add in their own 'handlers' for those signals, and they are ALL called -
147 * one after the other (TDB how threads work with this).
148 *
149 * When an platform signal-handler is installed (via 'sigaction' for example) -
150 * and then later UNINSTALLED (due to changes in GetHandledSignals) - this code resets the
151 * signal handler to SIG_DFL (not the previous value).
152 *
153 * \note The SignalHandlerRegistry must only be accessed after the start of main, and must not
154 * be accessed after main has completed.
155 *
156 * To use 'safe' signal handlers, be sure to read about and use
157 * @see SignalHandlerRegistry::SafeSignalsManager
158 *
159 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
160 *
161 */
163 public:
164 /**
165 * If this handler is set to the the ONLY handler for a given signal, then that signal handler is
166 * effectively ignored.
167 *
168 * To get the signal to be handled the DEFAULT way - remove all signal handlers.
169 */
171
172 public:
173 /**
174 * Access singleton implementation. None exists until this is called.
175 */
176 static SignalHandlerRegistry& Get ();
177
178 private:
180
181 public:
184
185 public:
186 nonvirtual SignalHandlerRegistry& operator= (const SignalHandlerRegistry&) = delete;
187
188 public:
189 class SafeSignalsManager;
190
191 public:
192 /**
193 * Returns the set of signals trapped by the SignalHandlerRegistry registry. Note - if not 'Installed ()' - these
194 * are tracked internally by Stroika but not actually installed in the OS.
195 */
197
198 public:
199 /**
200 * Returns the set of signals trapped by the SignalHandlerRegistry registry. This doesn't imply there is a handler.
201 * NB: A signal handler must be registered for a given signal number AND the signal number must be in GetHandledSignals () AND
202 * the SignalHandlerRegistry must be Installed () - to get the signal called.
203 *
204 * It is NOT an error to have a signal handler registered for a signal not in the set of GetHandledSignals () - or vice versa.
205 * Signals in the list of GetHandledSignals() with no handlers are effectively ignored.
206 */
207 nonvirtual Containers::Set<SignalHandler> GetSignalHandlers (SignalID signal) const;
208
209 public:
210 /**
211 * @see GetSignalHandlers().
212 *
213 * SetSignalHandlers () with NO arguments uninstalls all Stroika signal handlers for this signal.
214 * SetSignalHandlers () with ONE argument makes Stroika take-over the signal handling - and sets the set of hanlders to be
215 * exactly the one given (effectively removing any others previously added).
216 * SetSignalHandlers () with ONE a set of handlers registers all the given handlers.
217 *
218 * Note - if through ANY combination of set/add/remove - you have NO signal handler - this reverts to SIG_DFL, and if you have
219 * exactly ONE signal handler - and its kIGNORED- the signal will be ignored.
220 *
221 * \note Setting any 'Safe' signal handlers requires that SafeSignalsManager has been created.
222 */
223 nonvirtual void SetSignalHandlers (SignalID signal);
224 nonvirtual void SetSignalHandlers (SignalID signal, const SignalHandler& handler);
225 nonvirtual void SetSignalHandlers (SignalID signal, const Containers::Set<SignalHandler>& handlers);
226
227 public:
228 /**
229 * @see GetSignalHandlers().
230 *
231 * \note - subtlety - if you wish to later call RemoveSignalHandler, save the signalhandler in a static const of type
232 * SignalHandler, and re-use that value.
233 *
234 * \note Adding any 'Safe' signal handlers requires that SafeSignalsManager has been created.
235 */
236 nonvirtual void AddSignalHandler (SignalID signal, const SignalHandler& handler);
237
238 public:
239 /**
240 * @see GetSignalHandlers()
241 */
242 nonvirtual void RemoveSignalHandler (SignalID signal, const SignalHandler& handler);
243
244 public:
245 /**
246 * This signal handler simply prints error to the trace log, and calls 'abort' - which on most operating systems will allow the
247 * debugger to examine the errant code.
248 */
249 static void DefaultCrashSignalHandler (SignalID signal);
250
251 public:
252 /**
253 * These signals are generally associated with a programming error or bug, and these signals
254 * should generally be treated as a crash and terminate the program with a core-dump file.
255 * o SIGABRT
256 * o SIGILL
257 * o SIGFPE
258 * o SIGSEGV
259 * o SIGSYS (POSIX ONLY)
260 * o SIGBUS (POSIX ONLY)
261 * o SIGQUIT (POSIX ONLY)
262 */
264
265 public:
266 /**
267 * The set of signals given (by default GetStandardCrashSignals) will be set to the given handler
268 * (by default DefaultCrashSignalHandler).
269 *
270 * The only exception is SIGABRT will be intentionally ignored from this call because it prevents abort()
271 * from functioning properly. We COULD disable SIGABRT upon receipt of that signal (SIG_DFL) but that
272 * would be different than other signals handled, raise re-entrancy issues etc. Didn't seem worth while.
273 *
274 * \note By default these are created as 'unsafe' signal handlers, meaning that they are run
275 * on the thread that triggered the error. This is mostly because we (currently) have no other
276 * way to capture what thread we were running on at that point.
277 *
278 * This is bad, because we could deadlock in handling the crash. But realistically, we were crashing
279 * anyhow, so the better stack trace maybe worth it.
280 *
281 * @todo we may want to come up with some way to capture / pass along extra info to handlers, like
282 * the thread which recieved the signal. But for now...
283 */
284 nonvirtual void SetStandardCrashHandlerSignals (SignalHandler handler = SignalHandler{DefaultCrashSignalHandler, SignalHandler::Type::eDirect},
286
287 private:
289
290 private:
291 mutable atomic<unsigned int> fDirectSignalHandlersCache_Lock_{0};
292 vector<function<void (SignalID)>> fDirectSignalHandlersCache_[NSIG];
293
294 private:
295 static void FirstPassSignalHandler_ (SignalID signal);
296 };
297
298 /**
299 * A direct (eDirect) signal handler is invoked in the stack context in which the
300 * signal is delivered.
301 *
302 * A 'safe' (eSafe) signal handler is run in a separate thread context.
303 *
304 * Direct is more performant, and has lower latency. However, since many signals are
305 * delivered on an arbitrary thread, this can easily lead to deadlocks!
306 * See http://stackoverflow.com/questions/3366307/why-is-malloc-not-async-signal-safe
307 *
308 * 'safe' signal handlers avoid this by automatically moving the signal delivery to a special
309 * thread that handles the signals one at a time, and careful to avoid any memory allocation
310 * in the direct signal handling thread.
311 *
312 * To use this feature, you must construct a SignalHandlerRegistry::SafeSignalsManager.
313 *
314 * \note This must be constructed BEFORE adding any safe signal handler, and
315 * must be destroyed before exiting main (so it can shutdown its own threads).
316 *
317 * And only one SafeSignalsManager instance can exist at a time.
318 *
319 * The easiest (and recommened) way to do this is to add the line:
320 * SignalHandlerRegistry::SafeSignalsManager safeSignalsMgr;
321 * to the beginning of main ([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[])...
322 */
324 public:
325 /**
326 */
328 SafeSignalsManager (const SafeSignalsManager&) = delete;
329
330 public:
332
333 public:
334 nonvirtual SafeSignalsManager& operator= (const SafeSignalsManager&) = delete;
335
336 private:
337 class Rep_;
338#if __cpp_lib_atomic_shared_ptr >= 201711
339 static inline atomic<shared_ptr<Rep_>> sTheRep_{nullptr};
340#else
341 static inline shared_ptr<Rep_> sTheRep_{nullptr};
342#endif
343
344 private:
345 friend class SignalHandlerRegistry;
346 };
347
348}
349
350/*
351 ********************************************************************************
352 ***************************** Implementation Details ***************************
353 ********************************************************************************
354 */
355#include "SignalHandlers.inl"
356
357#endif /*_Stroika_Foundation_Execution_SignalHandlers_h_*/
#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Definition Set.h:105
nonvirtual void operator()(SignalID i) const
static Containers::Set< SignalID > GetStandardCrashSignals()
nonvirtual void AddSignalHandler(SignalID signal, const SignalHandler &handler)
nonvirtual void RemoveSignalHandler(SignalID signal, const SignalHandler &handler)
nonvirtual void SetStandardCrashHandlerSignals(SignalHandler handler=SignalHandler{DefaultCrashSignalHandler, SignalHandler::Type::eDirect}, const Containers::Set< SignalID > &forSignals=GetStandardCrashSignals())
nonvirtual Containers::Set< SignalID > GetHandledSignals() const
nonvirtual Containers::Set< SignalHandler > GetSignalHandlers(SignalID signal) const
Wrap any object with Synchronized<> and it can be used similarly to the base type,...