Timers

The timer class provides asynchronous delays and timeouts. It integrates with the I/O context to schedule operations at specific times or after durations.

Code snippets assume:
#include <boost/corosio/timer.hpp>
namespace corosio = boost::corosio;
using namespace std::chrono_literals;

Overview

Timers let you pause execution for a duration:

corosio::timer t(ioc);
t.expires_after(5s);
co_await t.wait();  // Suspends for 5 seconds

Construction

corosio::io_context ioc;
corosio::timer t(ioc);  // From execution context

Setting Expiry Time

Relative Time (Duration)

t.expires_after(100ms);      // 100 milliseconds from now
t.expires_after(5s);         // 5 seconds from now
t.expires_after(2min);       // 2 minutes from now

Any std::chrono::duration type works.

Absolute Time (Time Point)

auto deadline = std::chrono::steady_clock::now() + 10s;
t.expires_at(deadline);

Querying Expiry

corosio::timer::time_point when = t.expiry();

Waiting

The wait() operation suspends until the timer expires:

t.expires_after(1s);
auto [ec] = co_await t.wait();

if (!ec)
    std::cout << "Timer expired normally\n";

Cancellation

t.cancel();  // Pending wait completes with capy::error::canceled

The wait completes immediately with an error:

auto [ec] = co_await t.wait();
if (ec == capy::error::canceled)
    std::cout << "Timer was cancelled\n";

Type Aliases

using clock_type = std::chrono::steady_clock;
using time_point = clock_type::time_point;
using duration = clock_type::duration;

The timer uses steady_clock for monotonic timing unaffected by system clock adjustments.

Resetting Timers

Setting a new expiry cancels any pending wait:

t.expires_after(10s);
// Later, before 10s elapses:
t.expires_after(5s);  // Resets to 5s, cancels previous wait

Use Cases

Simple Delay

capy::task<void> delayed_action(corosio::io_context& ioc)
{
    corosio::timer t(ioc);
    t.expires_after(2s);
    co_await t.wait();

    std::cout << "2 seconds have passed\n";
}

Periodic Timer

capy::task<void> periodic_task(corosio::io_context& ioc)
{
    corosio::timer t(ioc);

    for (int i = 0; i < 10; ++i)
    {
        t.expires_after(1s);
        co_await t.wait();
        std::cout << "Tick " << i << "\n";
    }
}

Operation Timeout

capy::task<void> with_timeout(
    corosio::io_context& ioc,
    corosio::socket& sock)
{
    corosio::timer timeout(ioc);
    timeout.expires_after(30s);

    // Start both operations
    auto read_task = sock.read_some(buffer);
    auto timeout_task = timeout.wait();

    // In practice, use parallel composition utilities
    // This is simplified for illustration
}
For proper timeout handling, use Capy’s parallel composition utilities like when_any or cancellation tokens.

Rate Limiting

capy::task<void> rate_limited_work(corosio::io_context& ioc)
{
    corosio::timer t(ioc);
    auto next_time = std::chrono::steady_clock::now();

    for (int i = 0; i < 100; ++i)
    {
        // Do work
        process_item(i);

        // Wait until next interval
        next_time += 100ms;
        t.expires_at(next_time);
        auto [ec] = co_await t.wait();
        if (ec)
            break;
    }
}

Using absolute time points prevents drift in periodic operations.

Stop Token Cancellation

Timer waits support stop token cancellation through the affine protocol:

// Inside a cancellable task:
auto [ec] = co_await t.wait();
// Completes with capy::error::canceled if stop requested

Move Semantics

Timers are move-only:

corosio::timer t1(ioc);
corosio::timer t2 = std::move(t1);  // OK

corosio::timer t3 = t2;  // Error: deleted copy constructor

Move assignment cancels any pending wait on the destination timer.

Source and destination must share the same execution context.

Thread Safety

Operation Thread Safety

Distinct timers

Safe from different threads

Same timer

NOT safe for concurrent operations

Don’t call wait(), expires_after(), or cancel() concurrently on the same timer.

Example: Heartbeat

capy::task<void> heartbeat(
    corosio::io_context& ioc,
    corosio::socket& sock,
    std::atomic<bool>& running)
{
    corosio::timer t(ioc);

    while (running)
    {
        t.expires_after(30s);
        auto [ec] = co_await t.wait();

        if (ec)
            break;

        // Send heartbeat
        std::string ping = "PING\r\n";
        auto [wec, n] = co_await corosio::write(
            sock, capy::const_buffer(ping.data(), ping.size()));

        if (wec)
            break;
    }
}

Next Steps