Signal Handling

The signal_set class provides asynchronous signal handling. It allows coroutines to wait for operating system signals like SIGINT (Ctrl+C) or SIGTERM.

Code snippets assume:
#include <boost/corosio/signal_set.hpp>
#include <csignal>

namespace corosio = boost::corosio;

Overview

corosio::signal_set signals(ioc, SIGINT, SIGTERM);

auto [ec, signum] = co_await signals.async_wait();
if (!ec)
    std::cout << "Received signal " << signum << "\n";

Construction

Empty Signal Set

corosio::signal_set signals(ioc);
signals.add(SIGINT);
signals.add(SIGTERM);

With Initial Signals

// One signal
corosio::signal_set s1(ioc, SIGINT);

// Two signals
corosio::signal_set s2(ioc, SIGINT, SIGTERM);

// Three signals
corosio::signal_set s3(ioc, SIGINT, SIGTERM, SIGHUP);

Supported Signals

Windows

On Windows, the following signals are supported:

Signal Description

SIGINT

Interrupt (Ctrl+C)

SIGTERM

Termination request

SIGABRT

Abnormal termination

SIGFPE

Floating-point exception

SIGILL

Illegal instruction

SIGSEGV

Segmentation violation

POSIX

On POSIX systems, all standard signals are supported.

Managing Signals

add()

Add a signal to the set:

signals.add(SIGUSR1);

// With error code
boost::system::error_code ec;
signals.add(SIGUSR1, ec);
if (ec)
    std::cerr << "Failed to add signal: " << ec.message() << "\n";

Adding a signal that’s already in the set has no effect.

remove()

Remove a signal from the set:

signals.remove(SIGINT);

// With error code
boost::system::error_code ec;
signals.remove(SIGINT, ec);

Removing a signal that’s not in the set has no effect.

clear()

Remove all signals from the set:

signals.clear();

// With error code
boost::system::error_code ec;
signals.clear(ec);

Waiting for Signals

The async_wait() operation waits for any signal in the set:

auto [ec, signum] = co_await signals.async_wait();

if (!ec)
{
    switch (signum)
    {
    case SIGINT:
        std::cout << "Interrupt received\n";
        break;
    case SIGTERM:
        std::cout << "Termination requested\n";
        break;
    }
}

Cancellation

cancel()

Cancel pending wait operations:

signals.cancel();

The wait completes with capy::error::canceled:

auto [ec, signum] = co_await signals.async_wait();
if (ec == capy::error::canceled)
    std::cout << "Wait was cancelled\n";

Cancellation does NOT remove signals from the set. The signal set remains configured and can be waited on again.

Stop Token Cancellation

Signal waits support stop token cancellation through the affine protocol.

Use Cases

Graceful Shutdown

capy::task<void> shutdown_handler(
    corosio::io_context& ioc,
    std::atomic<bool>& running)
{
    corosio::signal_set signals(ioc, SIGINT, SIGTERM);

    auto [ec, signum] = co_await signals.async_wait();
    if (!ec)
    {
        std::cout << "Shutdown signal received\n";
        running = false;
        ioc.stop();
    }
}

Multiple Signal Waits

You can wait for signals multiple times:

capy::task<void> signal_loop(corosio::io_context& ioc)
{
    corosio::signal_set signals(ioc, SIGUSR1);

    for (;;)
    {
        auto [ec, signum] = co_await signals.async_wait();
        if (ec)
            break;

        std::cout << "Received USR1, doing work...\n";
        // Handle signal
    }
}

Reload Configuration

capy::task<void> config_reloader(
    corosio::io_context& ioc,
    Config& config)
{
    corosio::signal_set signals(ioc, SIGHUP);

    for (;;)
    {
        auto [ec, signum] = co_await signals.async_wait();
        if (ec)
            break;

        std::cout << "Reloading configuration...\n";
        config.reload();
    }
}

Move Semantics

Signal sets are move-only:

corosio::signal_set s1(ioc, SIGINT);
corosio::signal_set s2 = std::move(s1);  // OK

corosio::signal_set s3 = s2;  // Error: deleted copy constructor
Source and destination must share the same execution context.

Thread Safety

Operation Thread Safety

Distinct signal_sets

Safe from different threads

Same signal_set

NOT safe for concurrent operations

Don’t call async_wait(), add(), remove(), clear(), or cancel() concurrently on the same signal_set.

Example: Server with Graceful Shutdown

capy::task<void> run_server(corosio::io_context& ioc)
{
    std::atomic<bool> running{true};

    // Start signal handler
    capy::run_async(ioc.get_executor())(
        [](corosio::io_context& ioc, std::atomic<bool>& running)
            -> capy::task<void>
        {
            corosio::signal_set signals(ioc, SIGINT, SIGTERM);
            co_await signals.async_wait();
            running = false;
            ioc.stop();
        }(ioc, running));

    // Accept loop
    corosio::acceptor acc(ioc);
    acc.listen(corosio::endpoint(8080));

    while (running)
    {
        corosio::socket peer(ioc);
        auto [ec] = co_await acc.accept(peer);
        if (ec)
            break;

        // Handle connection...
    }
}

Platform Notes

Windows

Windows has limited signal support. The library uses signal() from the C runtime for compatibility.

POSIX

On POSIX systems, signals are handled using platform-native mechanisms for reliable delivery.

Next Steps