How to Use Asio signal_set for Process Signals: A Complete Guide
Asio's signal_set class provides a portable, asynchronous mechanism for handling POSIX-style process signals like SIGINT and SIGTERM, allowing applications to register multiple signals and receive notifications through the io_context event loop.
The asio::signal_set utility in the chriskohlhoff/asio repository enables robust signal handling without blocking threads. Built on top of the generic I/O object infrastructure, this facility integrates seamlessly with Asio's executor model to deliver signal notifications as asynchronous completion handlers.
Understanding the Architecture
asio::signal_set is actually a typedef for asio::basic_signal_set<>, which inherits from signal_set_base and delegates OS-level operations to asio::detail::signal_set_service according to the implementation in include/asio/basic_signal_set.hpp.
Class Hierarchy and Service Implementation
The template class uses the signal-set service (detail::signal_set_service) to manage the underlying operating system signal registrations. All public member functions—including add(), remove(), clear(), cancel(), and async_wait()—are thin wrappers that forward to this service implementation. When using overloads without an explicit asio::error_code parameter, the class automatically converts service errors into thrown system_error exceptions.
Executor Binding
The template parameter Executor (defaulting to any_io_executor) determines which executor dispatches completion handlers. As defined in include/asio/basic_signal_set.hpp (lines 13-16), this binding ensures that signal handlers execute within your application's chosen concurrency context, whether that's a io_context, thread_pool, or custom executor.
Basic Usage and Registration
Signals are registered using the constructor or the add() method. The constructor accepting multiple signals appears at lines 102-108 in include/asio/basic_signal_set.hpp.
Here is a complete example waiting for SIGINT (Ctrl-C) or SIGTERM:
#include <asio.hpp>
#include <iostream>
void signal_handler(const asio::error_code& ec, int signal_number)
{
if (!ec)
std::cout << "Caught signal " << signal_number << "\n";
else
std::cout << "Signal wait error: " << ec.message() << "\n";
}
int main()
{
asio::io_context ctx;
// Register for SIGINT and SIGTERM via constructor
asio::signal_set signals(ctx, SIGINT, SIGTERM);
// Asynchronously wait for either signal
signals.async_wait(signal_handler);
// Block until signal arrives
ctx.run();
}
Adding and Removing Signals Dynamically
You can modify the signal set after construction using add() and remove(), implemented at lines 61-66 and 48-53 respectively in include/asio/basic_signal_set.hpp:
asio::signal_set sigs(ctx); // Empty set
sigs.add(SIGUSR1); // Register SIGUSR1 (lines 61-66)
sigs.async_wait([](auto ec, int sig){ /* handle */ });
sigs.remove(SIGUSR1); // Unregister SIGUSR1 (lines 48-53)
Asynchronous Operations and Cancellation
The async_wait() method (lines 60-66) is an initiating function that returns immediately. The supplied handler receives an asio::error_code and the integer signal number that triggered the wake-up.
Queueing Semantics
If a signal arrives while no handler is waiting, the notification is queued internally. Subsequent async_wait calls dequeue notifications in ascending signal number order, as documented in lines 77-84 of include/asio/basic_signal_set.hpp.
Cancelling Pending Operations
The cancel() method (lines 105-112) forces pending async_wait operations to complete with asio::error::operation_aborted, but does not remove the registered signals from the set:
sigs.async_wait(handler);
// ...
sigs.cancel(); // Handler invoked with operation_aborted
Platform Considerations
Signal Masking on POSIX Systems
As noted in lines 99-104 of include/asio/basic_signal_set.hpp, Asio does not modify the process signal mask. Your application must ensure that at least one thread has the registered signals unblocked (e.g., via sigprocmask or pthread_sigmask). If all threads block the signal, the io_context will never receive the notification.
Summary
asio::signal_setis a portable wrapper around POSIX signal handling that integrates with Asio's asynchronous model.- Registration occurs via constructor parameters or the
add()method, while removal usesremove()orclear(). - Queueing ensures signals are not lost if they arrive before
async_waitis called, with delivery in ascending signal number order. - Cancellation via
cancel()aborts pending waits but leaves the signal registration intact. - POSIX Requirement: The application must ensure signals are unblocked in at least one thread; Asio does not manage the process signal mask.
Frequently Asked Questions
What is the difference between signal_set and standard signal handling?
Standard signal handling using signal() or sigaction requires synchronous signal handlers that execute in a restricted context, limiting what operations can be performed safely. In contrast, asio::signal_set delivers notifications asynchronously through the io_context, allowing handlers to use the full Asio API and execute on your application's executors without the reentrancy constraints of traditional signal handlers.
How do I handle multiple signals with one signal_set?
You can register multiple signals—such as SIGINT, SIGTERM, and SIGUSR1—in a single signal_set instance using the variadic constructor or successive calls to add(). When any registered signal arrives, the async_wait handler fires with the specific signal number as the second parameter, allowing you to distinguish which signal triggered the event using a single completion handler.
Can I use signal_set on Windows?
Windows support is limited because signal_set relies on POSIX-style process signals. While Windows does not implement POSIX signals in the same manner, Asio provides emulation for some signals like SIGINT and SIGTERM on Windows platforms. However, for full portability across Unix-like systems and Windows, consider using alternative mechanisms such as asio::steady_timer or OS-specific APIs for Windows service control.
How do I properly shutdown a signal_set?
To cleanly shut down, first call cancel() to abort any pending async_wait operations with asio::error::operation_aborted. Then, either let the signal_set object go out of scope or explicitly call clear() to remove all registered signals. Note that destruction of the signal_set automatically cancels pending operations and unregisters all signals from the underlying service in include/asio/detail/signal_set_service.hpp.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →