# How to Use Asio signal_set for Process Signals: A Complete Guide

> Master Asio signal_set for process signals. Learn how to asynchronously handle POSIX signals like SIGINT with the io_context event loop for robust application control. Read the complete guide.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: how-to-guide
- Published: 2026-07-17

---

**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](https://github.com/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`](https://github.com/chriskohlhoff/asio/blob/main/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`](https://github.com/chriskohlhoff/asio/blob/main/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`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_signal_set.hpp).

Here is a complete example waiting for `SIGINT` (Ctrl-C) or `SIGTERM`:

```cpp
#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`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_signal_set.hpp):

```cpp
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`](https://github.com/chriskohlhoff/asio/blob/main/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:

```cpp
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`](https://github.com/chriskohlhoff/asio/blob/main/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_set`** is 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 uses `remove()` or `clear()`.
- **Queueing** ensures signals are not lost if they arrive before `async_wait` is 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`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/detail/signal_set_service.hpp).