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";
Supported Signals
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.
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.
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
}
}
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...
}
}
Next Steps
-
Timers — Timed operations
-
I/O Context — The event loop
-
Echo Server Tutorial — Server example