1717#if defined(_WIN32)
1818#define WIN32_LEAN_AND_MEAN
1919#include < windows.h>
20+ #else
21+ #include < signal.h>
22+ #include < unistd.h>
23+ #include < fcntl.h>
24+ #include < poll.h>
2025#endif
2126
2227namespace EntropyEngine { namespace Core {
@@ -66,6 +71,18 @@ int EntropyApplication::run() {
6671 if (_cfg.installSignalHandlers ) {
6772 installSignalHandlers ();
6873 }
74+ #else
75+ // Create signal notification pipe for Unix
76+ if (_signalPipe[0 ] == -1 ) {
77+ if (pipe (_signalPipe) == 0 ) {
78+ // Set both ends non-blocking
79+ fcntl (_signalPipe[0 ], F_SETFL , O_NONBLOCK );
80+ fcntl (_signalPipe[1 ], F_SETFL , O_NONBLOCK );
81+ }
82+ }
83+ if (_cfg.installSignalHandlers ) {
84+ installSignalHandlers ();
85+ }
6986#endif
7087
7188 // Drive service lifecycle
@@ -106,8 +123,34 @@ int EntropyApplication::run() {
106123 }
107124#else
108125 {
109- std::unique_lock<std::mutex> lk (_loopMutex);
110- _loopCv.wait (lk, [&]{ return _terminateRequested.load (std::memory_order_acquire); });
126+ // Unix wait loop with signal handling
127+ if (_cfg.installSignalHandlers && _signalPipe[0 ] != -1 ) {
128+ // Use poll() to wait on both condition variable and signal pipe
129+ for (;;) {
130+ if (_terminateRequested.load (std::memory_order_acquire)) {
131+ break ;
132+ }
133+
134+ // Check signal pipe with short timeout
135+ struct pollfd pfd;
136+ pfd.fd = _signalPipe[0 ];
137+ pfd.events = POLLIN ;
138+ int ret = poll (&pfd, 1 , 100 ); // 100ms timeout
139+
140+ if (ret > 0 && (pfd.revents & POLLIN )) {
141+ // Signal received - drain pipe and handle
142+ char buf[1 ];
143+ while (read (_signalPipe[0 ], buf, 1 ) > 0 );
144+
145+ int signum = _lastSignal.load (std::memory_order_relaxed);
146+ handlePosixSignal (signum);
147+ }
148+ }
149+ } else {
150+ // Fallback to condition_variable if signal handlers not installed
151+ std::unique_lock<std::mutex> lk (_loopMutex);
152+ _loopCv.wait (lk, [&]{ return _terminateRequested.load (std::memory_order_acquire); });
153+ }
111154 }
112155#endif
113156
@@ -123,6 +166,18 @@ int EntropyApplication::run() {
123166 CloseHandle (static_cast <HANDLE >(_terminateEvent));
124167 _terminateEvent = nullptr ;
125168 }
169+ #else
170+ if (_cfg.installSignalHandlers ) {
171+ uninstallSignalHandlers ();
172+ }
173+ if (_signalPipe[0 ] != -1 ) {
174+ close (_signalPipe[0 ]);
175+ _signalPipe[0 ] = -1 ;
176+ }
177+ if (_signalPipe[1 ] != -1 ) {
178+ close (_signalPipe[1 ]);
179+ _signalPipe[1 ] = -1 ;
180+ }
126181#endif
127182
128183 _running.store (false );
@@ -232,6 +287,131 @@ void EntropyApplication::handleConsoleSignal(unsigned long ctrlType) {
232287 }
233288 }
234289}
290+ #else
291+ // Unix/POSIX signal handling
292+ namespace {
293+ // Signal handler - must be async-signal-safe
294+ static void EntropySigHandler (int signum) {
295+ EntropyEngine::Core::EntropyApplication::shared ().notifyPosixSignalFromHandler (signum);
296+ }
297+ }
298+
299+ void EntropyApplication::installSignalHandlers () {
300+ if (_handlersInstalled.exchange (true )) return ;
301+
302+ // Set up sigaction for graceful termination signals
303+ struct sigaction sa;
304+ sa.sa_handler = EntropySigHandler;
305+ sigemptyset (&sa.sa_mask );
306+ sa.sa_flags = 0 ;
307+
308+ // Install handlers for common signals
309+ sigaction (SIGINT , &sa, nullptr ); // Ctrl+C
310+ sigaction (SIGTERM , &sa, nullptr ); // termination request
311+ sigaction (SIGHUP , &sa, nullptr ); // hangup
312+ sigaction (SIGQUIT , &sa, nullptr ); // quit signal
313+
314+ // For fatal signals like SIGSEGV, SIGABRT - also install but allow default behavior after logging
315+ struct sigaction fatal_sa;
316+ fatal_sa.sa_handler = EntropySigHandler;
317+ sigemptyset (&fatal_sa.sa_mask );
318+ fatal_sa.sa_flags = SA_RESETHAND ; // Reset to default after first signal
319+
320+ sigaction (SIGABRT , &fatal_sa, nullptr ); // abort
321+ sigaction (SIGSEGV , &fatal_sa, nullptr ); // segmentation fault
322+ sigaction (SIGBUS , &fatal_sa, nullptr ); // bus error
323+ sigaction (SIGFPE , &fatal_sa, nullptr ); // floating point exception
324+ sigaction (SIGILL , &fatal_sa, nullptr ); // illegal instruction
325+ }
326+
327+ void EntropyApplication::uninstallSignalHandlers () {
328+ if (!_handlersInstalled.exchange (false )) return ;
329+
330+ // Restore default signal handlers
331+ signal (SIGINT , SIG_DFL );
332+ signal (SIGTERM , SIG_DFL );
333+ signal (SIGHUP , SIG_DFL );
334+ signal (SIGQUIT , SIG_DFL );
335+ signal (SIGABRT , SIG_DFL );
336+ signal (SIGSEGV , SIG_DFL );
337+ signal (SIGBUS , SIG_DFL );
338+ signal (SIGFPE , SIG_DFL );
339+ signal (SIGILL , SIG_DFL );
340+ }
341+
342+ void EntropyApplication::notifyPosixSignalFromHandler (int signum) noexcept {
343+ _lastSignal.store (signum, std::memory_order_relaxed);
344+ // Write to pipe to wake up main thread (signal-safe operation)
345+ if (_signalPipe[1 ] != -1 ) {
346+ char byte = 1 ;
347+ (void )write (_signalPipe[1 ], &byte, 1 );
348+ }
349+ }
350+
351+ void EntropyApplication::handlePosixSignal (int signum) {
352+ // Map signals we care about
353+ bool isFatal = false ;
354+ switch (signum) {
355+ case SIGINT :
356+ case SIGTERM :
357+ case SIGHUP :
358+ case SIGQUIT :
359+ break ; // Graceful termination signals
360+ case SIGABRT :
361+ case SIGSEGV :
362+ case SIGBUS :
363+ case SIGFPE :
364+ case SIGILL :
365+ isFatal = true ;
366+ break ;
367+ default :
368+ return ; // ignore others
369+ }
370+
371+ bool first = !_signalSeen.exchange (true );
372+
373+ if (first) {
374+ // Optionally consult delegate; if vetoed, just return on first request
375+ bool allow = true ;
376+ if (_delegate && !isFatal) {
377+ try { allow = _delegate->applicationShouldTerminate (); }
378+ catch (...) { /* swallow in signal path */ }
379+ }
380+
381+ if (allow || isFatal) {
382+ terminate (isFatal ? 1 : 0 );
383+ }
384+
385+ // Start escalation timer after first signal regardless, to avoid hanging forever
386+ if (!_escalationStarted.exchange (true ) && !isFatal) {
387+ auto deadline = _cfg.shutdownDeadline ;
388+ std::weak_ptr<EntropyApplication> weak = EntropyApplication::sharedPtr ();
389+ std::thread ([weak, deadline]{
390+ auto endAt = std::chrono::steady_clock::now () + deadline;
391+ std::this_thread::sleep_until (endAt);
392+ if (auto sp = weak.lock ()) {
393+ if (sp->isRunning ()) {
394+ // Escalate: attempt a harder exit
395+ sp->terminate (1 );
396+ std::this_thread::sleep_for (std::chrono::milliseconds (200 ));
397+ if (sp->isRunning ()) {
398+ std::quick_exit (1 );
399+ }
400+ }
401+ }
402+ }).detach ();
403+ }
404+ } else {
405+ // Subsequent signal: escalate immediately
406+ if (_running.load ()) {
407+ terminate (1 );
408+ std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
409+ if (_running.load ()) {
410+ std::quick_exit (1 );
411+ }
412+ }
413+ }
414+ }
235415#endif
236416
237417
0 commit comments