Skip to content

Commit 2f7bd25

Browse files
committed
Add Unix/POSIX signal handling support
1 parent 2cd646a commit 2f7bd25

2 files changed

Lines changed: 198 additions & 2 deletions

File tree

src/Core/EntropyApplication.cpp

Lines changed: 182 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
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

2227
namespace 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

src/Core/EntropyApplication.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ class EntropyApplication {
6565
void handleConsoleSignal(unsigned long ctrlType);
6666
// Signal-safe notification from console handler (sets flag and signals event)
6767
void notifyConsoleSignalFromHandler(unsigned long ctrlType) noexcept;
68+
#else
69+
// Exposed for Unix signal handler forwarder
70+
void handlePosixSignal(int signum);
71+
// Signal-safe notification from signal handler
72+
void notifyPosixSignalFromHandler(int signum) noexcept;
6873
#endif
6974

7075
private:
@@ -75,6 +80,10 @@ class EntropyApplication {
7580
// Windows console control handling
7681
void installSignalHandlers();
7782
void uninstallSignalHandlers();
83+
#else
84+
// Unix/POSIX signal handling
85+
void installSignalHandlers();
86+
void uninstallSignalHandlers();
7887
#endif
7988

8089
// Fields
@@ -95,6 +104,13 @@ class EntropyApplication {
95104
void* _ctrlEvent{nullptr}; // HANDLE, kept as void* to avoid windows.h in header (auto-reset)
96105
void* _terminateEvent{nullptr}; // HANDLE, kept as void* (manual-reset)
97106
std::atomic<unsigned long> _lastCtrlType{0};
107+
#else
108+
std::atomic<bool> _handlersInstalled{false};
109+
std::atomic<bool> _signalSeen{false};
110+
std::atomic<bool> _escalationStarted{false};
111+
// Unix signal handling internals
112+
int _signalPipe[2]{-1, -1}; // Pipe for signal-safe notification
113+
std::atomic<int> _lastSignal{0};
98114
#endif
99115

100116
// Inline wait primitives (replacing EntropyRunLoop)

0 commit comments

Comments
 (0)