Error Handling
Learn how errors are reported in Asio coroutines.
Default: Exceptions
By default, errors in co_await expressions throw boost::system::system_error:
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::awaitable;
using asio::use_awaitable;
awaitable<void> connect_with_exceptions()
{
auto executor = co_await asio::this_coro::executor;
asio::ip::tcp::socket socket(executor);
try
{
asio::ip::tcp::endpoint ep(asio::ip::make_address("127.0.0.1"), 12345);
co_await socket.async_connect(ep, use_awaitable);
std::cout << "Connected!\n";
}
catch (const boost::system::system_error& e)
{
std::cout << "Error: " << e.what() << "\n";
std::cout << "Code: " << e.code() << "\n";
// e.code() == asio::error::connection_refused, etc.
}
}
Using Error Codes
If you prefer error codes over exceptions, use redirect_error:
awaitable<void> connect_with_error_codes()
{
auto executor = co_await asio::this_coro::executor;
asio::ip::tcp::socket socket(executor);
asio::ip::tcp::endpoint ep(asio::ip::make_address("127.0.0.1"), 12345);
boost::system::error_code ec;
co_await socket.async_connect(ep, asio::redirect_error(use_awaitable, ec));
if (ec)
{
std::cout << "Error: " << ec.message() << "\n";
co_return;
}
std::cout << "Connected!\n";
}
Using as_tuple
as_tuple returns all results including the error code as a tuple:
#include <boost/asio/experimental/as_tuple.hpp>
awaitable<void> read_with_as_tuple()
{
auto executor = co_await asio::this_coro::executor;
asio::ip::tcp::socket socket(executor);
// ... connect ...
char buf[1024];
auto [ec, n] = co_await socket.async_read_some(
asio::buffer(buf),
asio::experimental::as_tuple(use_awaitable));
if (ec)
{
if (ec == asio::error::eof)
std::cout << "Connection closed\n";
else
std::cout << "Error: " << ec.message() << "\n";
co_return;
}
std::cout << "Read " << n << " bytes\n";
}
This is particularly useful when the error isn’t exceptional (like EOF).
Common Error Codes
Connection Errors
| Error | Meaning |
|---|---|
|
No server listening on that port |
|
Peer forcibly closed the connection |
|
No route to host |
|
No route to network |
|
Connection attempt timed out |
Checking Error Codes
if (ec == asio::error::eof)
{
// Handle clean close
}
else if (ec == asio::error::connection_refused)
{
// Handle refused connection
}
else if (ec)
{
// Handle other errors
}
You can also check error categories:
if (ec.category() == asio::error::get_system_category())
{
// System-level error (OS error code)
}
else if (ec.category() == asio::error::get_misc_category())
{
// Asio-specific error (eof, etc.)
}
Cancellation
When an operation is cancelled (e.g., timer cancelled, socket closed), it
completes with asio::error::operation_aborted:
awaitable<void> handle_cancellation()
{
auto executor = co_await asio::this_coro::executor;
asio::steady_timer timer(executor, std::chrono::seconds(10));
try
{
co_await timer.async_wait(use_awaitable);
std::cout << "Timer expired\n";
}
catch (const boost::system::system_error& e)
{
if (e.code() == asio::error::operation_aborted)
std::cout << "Timer was cancelled\n";
else
throw;
}
}
Partial Operations
Some operations may succeed partially. For example, async_read might read
fewer bytes than requested if the connection closes:
awaitable<void> handle_partial_read()
{
// ... socket setup ...
char buf[1000];
boost::system::error_code ec;
std::size_t n = co_await asio::async_read(
socket,
asio::buffer(buf),
asio::redirect_error(use_awaitable, ec));
// n bytes were read, even if ec indicates an error
std::cout << "Read " << n << " bytes\n";
if (ec == asio::error::eof)
std::cout << "Connection closed after partial read\n";
}
Exception Safety in Coroutines
Exceptions propagate through coroutine chains:
awaitable<void> inner()
{
throw std::runtime_error("Something went wrong");
}
awaitable<void> outer()
{
try
{
co_await inner();
}
catch (const std::exception& e)
{
std::cout << "Caught: " << e.what() << "\n";
}
}
If an exception escapes a coroutine spawned with detached, it terminates
the program. Use a completion handler to catch exceptions:
asio::co_spawn(ctx, my_coroutine(),
[](std::exception_ptr ep) {
if (ep)
{
try { std::rethrow_exception(ep); }
catch (const std::exception& e) {
std::cerr << "Unhandled: " << e.what() << "\n";
}
}
});
Best Practices
-
Use exceptions for unexpected errors — Connection failures, permission denied, etc.
-
Use
as_tuplefor expected conditions — EOF is normal when reading to end of stream; don’t force a try/catch. -
Always handle
operation_aborted— It’s not really an error, just indicates the operation was cancelled. -
Log error details — Include
ec.message()andec.value()in logs for debugging.
Next Steps
-
Reading and Writing — Handle read/write errors
-
SSL/TLS — SSL-specific errors