Delays and Timeouts
Corosio schedules delays and deadlines through two free functions,
delay() and timeout(). Neither one names an I/O object: there is
no timer to construct, store as a member, move around, or close.
Each call returns an awaitable that arms whatever clock resource it
needs for the duration of a single co_await, then releases it. A
loop that waits repeatedly, like a heartbeat or a retry backoff, just
calls delay() again on the next iteration instead of resetting a
long-lived object’s expiry.
|
Code snippets assume:
|
Delaying for a Duration
delay(duration) suspends the calling coroutine until the given
duration elapses:
auto [ec] = co_await corosio::delay(500ms);
if (!ec)
std::cout << "500ms elapsed\n";
A zero or negative duration completes synchronously without suspending.
Delaying Until a Time Point
delay(time_point) suspends until an absolute steady_clock time is
reached. This is the right tool for periodic work, since it avoids
the drift that accumulates when each iteration re-measures a relative
duration from "now":
auto next = std::chrono::steady_clock::now();
for (int i = 0; i < 10; ++i)
{
next += 100ms;
auto [ec] = co_await corosio::delay(next);
if (ec)
break;
std::cout << "Tick " << i << "\n";
}
A time point already in the past also completes synchronously.
Cancellation
delay() honors the stop token of its co_await environment. If the
token is already stopped when the coroutine suspends, or becomes
stopped while it is waiting, the wait ends early and resumes with
capy::cond::canceled:
auto [ec] = co_await corosio::delay(10s);
if (ec == capy::cond::canceled)
std::cout << "Delay was cancelled\n";
There is no cancel() to call directly; cancellation flows entirely
through the coroutine’s stop token, the same mechanism every other
Corosio operation uses.
Racing an Operation Against a Deadline
timeout(op, duration) starts an awaitable and a deadline together.
Whichever finishes first decides the outcome:
auto [ec, n] = co_await corosio::timeout(
sock.read_some(buffer), 200ms);
if (ec == capy::cond::timeout)
std::cout << "No data within 200ms\n";
else if (!ec)
std::cout << "Read " << n << " bytes\n";
If the inner operation wins, its io_result is returned unchanged,
error or success, payload and all. If the deadline wins, the inner
operation is cancelled and timeout() produces its own result: ec
is capy::cond::timeout and any payload (such as a byte count) is
default-initialized, not whatever the cancelled operation happened
to leave behind.
timeout() also accepts an absolute deadline:
auto deadline = std::chrono::steady_clock::now() + 5s;
auto [ec] = co_await corosio::timeout(sock.connect(ep), deadline);
Distinguishing Timeout from Cancellation
A timeout() call sits inside whatever stop token its own coroutine
is awaited under. If that parent token is stopped, the race ends the
same way an unguarded operation would: the inner awaitable is
cancelled and timeout() reports capy::cond::canceled, not
capy::cond::timeout. The two conditions are never ambiguous:
| Result | Meaning |
|---|---|
|
The deadline elapsed before the operation completed. |
|
The coroutine’s own stop token fired, independent of the deadline. |
auto [ec] = co_await corosio::timeout(sock.connect(ep), 3s);
if (ec == capy::cond::timeout)
std::cout << "Connect attempt timed out\n";
else if (ec == capy::cond::canceled)
std::cout << "Connect attempt cancelled by caller\n";
Requires an io_context
Both delay() and timeout() need a clock service to arm, which
they obtain from the awaiting coroutine’s executor. That executor
must belong to an io_context; awaiting either one from any other
kind of execution context terminates the program, since silently
skipping the requested delay would be worse than a hard failure.
Composing with Other Operations
Because timeout() takes any awaitable that yields an io_result,
it composes with anything Corosio returns, not just socket reads. A
connect-with-retry loop pairs timeout() for the per-attempt deadline
with delay() for the pause between attempts:
capy::task<>
connect_with_deadline(
corosio::tcp_socket& sock,
corosio::endpoint ep,
int max_attempts)
{
for (int attempt = 0; attempt < max_attempts; ++attempt)
{
auto [ec] = co_await corosio::timeout(sock.connect(ep), 3s);
if (!ec)
co_return;
if (ec == capy::cond::canceled)
co_return;
sock.close();
co_await corosio::delay(500ms);
}
}
Next Steps
-
Signal Handling: respond to OS signals
-
I/O Context: the event loop
-
Error Handling: cancellation patterns