-
Notifications
You must be signed in to change notification settings - Fork 0
Signal Handling
This page explains how to handle Unix signals safely in Elio coroutines using signalfd.
Traditional signal handlers have significant limitations in coroutine-based applications:
-
Async-signal-safety: Signal handlers can interrupt at any point, even in the middle of coroutine scheduling operations. Only a limited set of functions are safe to call in signal handlers.
-
Race conditions: Modifying scheduler state, coroutine handles, or shared data structures from a signal handler is inherently unsafe.
-
Limited functionality: You can't use
co_await, I/O operations, or most standard library functions in signal handlers.
Linux's signalfd(2) converts signals into file descriptor events. By blocking signals with sigprocmask and reading them via signalfd, signals become normal I/O events that can be handled in a regular coroutine context.
#include <elio/signal/signalfd.hpp>
using namespace elio::signal;coro::task<void> handle_shutdown() {
// Create signal set with signals to handle
signal_set sigs{SIGINT, SIGTERM};
// Create signalfd - automatically blocks the signals
signal_fd sigfd(sigs);
// Wait for signal in coroutine context
while (running) {
auto info = co_await sigfd.wait();
if (info) {
if (info->signo == SIGINT || info->signo == SIGTERM) {
ELIO_LOG_INFO("Shutdown requested via {}", info->full_name());
running = false;
}
}
}
co_return;
}// Simple one-shot signal wait
auto info = co_await wait_signal(SIGTERM);
ELIO_LOG_INFO("Received {}", info.full_name());Manages a set of signals using sigset_t.
// Create empty set
signal_set sigs;
// Add signals (chainable)
sigs.add(SIGINT).add(SIGTERM).add(SIGUSR1);
// Create with initializer list
signal_set sigs{SIGINT, SIGTERM, SIGUSR1};
// Remove a signal
sigs.remove(SIGUSR1);
// Check membership
if (sigs.contains(SIGINT)) { /* ... */ }
// Clear or fill
sigs.clear(); // Empty set
sigs.fill(); // All signals
// Block/unblock for current thread
sigset_t old_mask;
sigs.block(&old_mask); // Block these signals
sigs.unblock(); // Unblock these signals
sigs.set_mask(&old_mask); // Replace signal maskAsync-friendly signalfd wrapper.
// Create with automatic blocking
signal_fd sigfd(sigs);
// Don't auto-block (caller manages signal mask)
signal_fd sigfd(sigs, false);
// Check validity
if (sigfd.valid()) { /* ... */ }
if (sigfd) { /* ... */ } // bool conversion
// Get file descriptor
int fd = sigfd.fd();
// Async wait
auto info = co_await sigfd.wait();
// Sync try-read (non-blocking)
auto info = sigfd.try_read();
// Update signal set
signal_set new_sigs{SIGUSR2};
sigfd.update(new_sigs);
// Restore original signal mask
sigfd.restore_mask();
// Close explicitly
sigfd.close();Information about a received signal.
auto info = co_await sigfd.wait();
if (info) {
int signo = info->signo; // Signal number
const char* name = info->name(); // "INT", "TERM", etc.
std::string full = info->full_name(); // "SIGINT", "SIGTERM"
uint32_t pid = info->pid; // Sender PID
uint32_t uid = info->uid; // Sender UID
int32_t code = info->code; // Signal code (SI_USER, SI_KERNEL, etc.)
}RAII guard for temporary signal blocking.
{
signal_block_guard guard(sigs);
// Signals are blocked here
} // Signals restored automatically// Get signal name from number
const char* name = signal_name(SIGINT); // "INT"
// Get signal number from name
int signo = signal_number("SIGINT"); // 2
int signo = signal_number("INT"); // 2 (prefix optional)Block signals before creating any threads to ensure all threads inherit the blocked mask:
int main() {
// Block signals FIRST, before anything else
signal_set sigs{SIGINT, SIGTERM, SIGUSR1};
sigs.block_all_threads();
// Now create scheduler and start threads
scheduler sched(4);
sched.start();
// ...
}coro::task<void> signal_router() {
signal_set sigs{SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGHUP};
signal_fd sigfd(sigs);
while (running) {
auto info = co_await sigfd.wait();
if (!info) continue;
switch (info->signo) {
case SIGINT:
case SIGTERM:
initiate_shutdown();
break;
case SIGHUP:
reload_configuration();
break;
case SIGUSR1:
print_status();
break;
case SIGUSR2:
rotate_logs();
break;
}
}
co_return;
}For simple server applications, use elio::serve() which handles all shutdown logic automatically:
coro::task<int> async_main(int argc, char* argv[]) {
http::router r;
r.get("/", handler);
http::server srv(r);
// serve() waits for SIGINT/SIGTERM and calls srv.stop() automatically
co_await elio::serve(srv, srv.listen(addr));
co_return 0;
}
ELIO_ASYNC_MAIN(async_main)For more complex scenarios with custom shutdown logic:
std::atomic<bool> g_running{true};
coro::task<void> shutdown_handler() {
signal_set sigs{SIGINT, SIGTERM};
signal_fd sigfd(sigs);
auto info = co_await sigfd.wait();
ELIO_LOG_INFO("Shutdown signal received: {}", info->full_name());
g_running = false;
co_return;
}
coro::task<void> worker() {
while (g_running) {
// Do work
co_await process_request();
}
// Cleanup before exit
co_return;
}void log_signal(const signal_info& info) {
ELIO_LOG_INFO("Signal: {} ({})", info.full_name(), info.signo);
ELIO_LOG_INFO(" Sender: PID={}, UID={}", info.pid, info.uid);
ELIO_LOG_INFO(" Code: {}", info.code);
}See examples/signal_handling.cpp for a complete example showing:
- Signal blocking before thread creation
- Signal handler coroutine
- Graceful shutdown coordination
- Status requests via SIGUSR1
Build and run:
cd build
make signal_handling
./signal_handling
# In another terminal:
kill -USR1 <pid> # Print status
kill -INT <pid> # or Ctrl+C for graceful shutdown// DON'T do this in coroutine applications!
void signal_handler(int signo) {
// Can't use co_await here
// Can't safely modify scheduler state
// Limited to async-signal-safe functions
g_shutdown_flag = true; // Only atomic operations are safe
}coro::task<void> signal_handler() {
signal_fd sigfd(signal_set{SIGINT, SIGTERM});
auto info = co_await sigfd.wait(); // Full coroutine support!
// Can use any function here
co_await cleanup_connections();
co_await flush_caches();
ELIO_LOG_INFO("Clean shutdown complete");
co_return;
}- Core-Concepts - Coroutines and scheduler basics
- Examples - More code examples
-
man signalfd- Linux signalfd documentation -
man sigprocmask- Signal mask manipulation