Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions doc/modules/ROOT/pages/4.guide/4h.timers.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -83,12 +84,17 @@ if (!ec)

=== Cancellation

`cancel()` cancels all pending waits. `cancel_one()` cancels only the
oldest pending wait ( FIFO order ). Both return the number of operations
cancelled:

[source,cpp]
----
t.cancel(); // Pending wait completes with capy::error::canceled
std::size_t n = t.cancel(); // Cancel all pending waits
std::size_t m = t.cancel_one(); // Cancel oldest pending wait (0 or 1)
----

The wait completes immediately with an error:
The cancelled wait completes with an error:

[source,cpp]
----
Expand All @@ -111,14 +117,47 @@ clock adjustments.

== Resetting Timers

Setting a new expiry cancels any pending wait:
Setting a new expiry cancels any pending waits and returns the number
cancelled:

[source,cpp]
----
t.expires_after(10s);
// Later, before 10s elapses:
t.expires_after(5s); // Resets to 5s, cancels previous wait
std::size_t n = t.expires_after(5s); // Resets to 5s, cancels previous waits
----

== Multiple Waiters

Multiple coroutines can wait on the same timer concurrently. When the
timer expires, all waiters complete with success. When cancelled, all
waiters complete with `capy::error::canceled`:

[source,cpp]
----
capy::task<void> waiter(corosio::timer& t, int id)
{
auto [ec] = co_await t.wait();
if (!ec)
std::cout << "Waiter " << id << " expired\n";
}

capy::task<void> multi_wait(corosio::io_context& ioc)
{
corosio::timer t(ioc);
t.expires_after(1s);

// All three coroutines wait on the same timer
co_await capy::when_all(
waiter(t, 1),
waiter(t, 2),
waiter(t, 3));
}
----

Each waiter has independent stop token cancellation. Cancelling one
waiter's stop token does not affect the others. `cancel_one()` cancels
the oldest waiter only.

== Use Cases

Expand Down
15 changes: 9 additions & 6 deletions include/boost/corosio/basic_io_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@

#include <boost/corosio/detail/config.hpp>
#include <boost/corosio/detail/scheduler.hpp>
#include <coroutine>
#include <boost/capy/ex/execution_context.hpp>

#include <chrono>
#include <coroutine>
#include <cstddef>
#include <limits>

namespace boost::corosio {

namespace detail {
struct timer_service_access;
} // namespace detail

/** Base class for I/O context implementations.

This class provides the common API for all I/O context types.
Expand All @@ -33,6 +37,8 @@ namespace boost::corosio {
*/
class BOOST_COROSIO_DECL basic_io_context : public capy::execution_context
{
friend struct detail::timer_service_access;

public:
/** The executor type for this context. */
class executor_type;
Expand Down Expand Up @@ -254,15 +260,14 @@ class BOOST_COROSIO_DECL basic_io_context : public capy::execution_context
Derived classes must set sched_ in their constructor body.
*/
basic_io_context()
: sched_(nullptr)
: capy::execution_context(this)
, sched_(nullptr)
{
}

detail::scheduler* sched_;
};

//------------------------------------------------------------------------------

/** An executor for dispatching work to an I/O context.

The executor provides the interface for posting work items and
Expand Down Expand Up @@ -392,8 +397,6 @@ class basic_io_context::executor_type
}
};

//------------------------------------------------------------------------------

inline
basic_io_context::executor_type
basic_io_context::
Expand Down
1 change: 1 addition & 0 deletions include/boost/corosio/detail/scheduler.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down
76 changes: 62 additions & 14 deletions include/boost/corosio/timer.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand All @@ -22,10 +23,9 @@
#include <system_error>

#include <chrono>
#include <concepts>
#include <coroutine>
#include <cstddef>
#include <stop_token>
#include <type_traits>

namespace boost::corosio {

Expand All @@ -35,13 +35,17 @@ namespace boost::corosio {
awaitable types. The timer can be used to schedule operations
to occur after a specified duration or at a specific time point.

Multiple coroutines may wait concurrently on the same timer.
When the timer expires, all waiters complete with success. When
the timer is cancelled, all waiters complete with an error that
compares equal to `capy::cond::canceled`.

Each timer operation participates in the affine awaitable protocol,
ensuring coroutines resume on the correct executor.

@par Thread Safety
Distinct objects: Safe.@n
Shared objects: Unsafe. A timer must not have concurrent wait
operations.
Shared objects: Unsafe.

@par Semantics
Wraps platform timer facilities via the io_context reactor.
Expand Down Expand Up @@ -111,6 +115,27 @@ class BOOST_COROSIO_DECL timer : public io_object
*/
explicit timer(capy::execution_context& ctx);

/** Construct a timer with an initial absolute expiry time.

@param ctx The execution context that will own this timer.
@param t The initial expiry time point.
*/
timer(capy::execution_context& ctx, time_point t);

/** Construct a timer with an initial relative expiry time.

@param ctx The execution context that will own this timer.
@param d The initial expiry duration relative to now.
*/
template<class Rep, class Period>
timer(
capy::execution_context& ctx,
std::chrono::duration<Rep, Period> d)
: timer(ctx)
{
expires_after(d);
}

/** Move constructor.

Transfers ownership of the timer resources.
Expand All @@ -135,14 +160,26 @@ class BOOST_COROSIO_DECL timer : public io_object
timer(timer const&) = delete;
timer& operator=(timer const&) = delete;

/** Cancel any pending asynchronous operations.
/** Cancel all pending asynchronous wait operations.

All outstanding operations complete with an error code that
compares equal to `capy::cond::canceled`.

@return The number of operations that were cancelled.
*/
std::size_t cancel();

/** Cancel one pending asynchronous wait operation.

The oldest pending wait is cancelled (FIFO order). It
completes with an error code that compares equal to
`capy::cond::canceled`.

@return The number of operations that were cancelled (0 or 1).
*/
void cancel();
std::size_t cancel_one();

/** Get the timer's expiry time as an absolute time.
/** Return the timer's expiry time as an absolute time.

@return The expiry time point. If no expiry has been set,
returns a default-constructed time_point.
Expand All @@ -154,36 +191,47 @@ class BOOST_COROSIO_DECL timer : public io_object
Any pending asynchronous wait operations will be cancelled.

@param t The expiry time to be used for the timer.

@return The number of pending operations that were cancelled.
*/
void expires_at(time_point t);
std::size_t expires_at(time_point t);

/** Set the timer's expiry time relative to now.

Any pending asynchronous wait operations will be cancelled.

@param d The expiry time relative to now.

@return The number of pending operations that were cancelled.
*/
void expires_after(duration d);
std::size_t expires_after(duration d);

/** Set the timer's expiry time relative to now.

This is a convenience overload that accepts any duration type
and converts it to the timer's native duration type.
and converts it to the timer's native duration type. Any
pending asynchronous wait operations will be cancelled.

@param d The expiry time relative to now.

@return The number of pending operations that were cancelled.
*/
template<class Rep, class Period>
void expires_after(std::chrono::duration<Rep, Period> d)
std::size_t expires_after(std::chrono::duration<Rep, Period> d)
{
expires_after(std::chrono::duration_cast<duration>(d));
return expires_after(std::chrono::duration_cast<duration>(d));
}

/** Wait for the timer to expire.

Multiple coroutines may wait on the same timer concurrently.
When the timer expires, all waiters complete with success.

The operation supports cancellation via `std::stop_token` through
the affine awaitable protocol. If the associated stop token is
triggered, the operation completes immediately with an error
that compares equal to `capy::cond::canceled`.
triggered, only that waiter completes with an error that
compares equal to `capy::cond::canceled`; other waiters are
unaffected.

@par Example
@code
Expand Down
3 changes: 2 additions & 1 deletion src/corosio/src/detail/dispatch_coro.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2026 Steve Gerbino
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Expand Down Expand Up @@ -37,7 +38,7 @@ dispatch_coro(
capy::executor_ref ex,
std::coroutine_handle<> h)
{
if (&ex.type_id() == &capy::detail::type_id<basic_io_context::executor_type>())
if ( ex.target< basic_io_context::executor_type >() )
return h;
return ex.dispatch(h);
}
Expand Down
1 change: 1 addition & 0 deletions src/corosio/src/detail/epoll/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "src/detail/epoll/scheduler.hpp"
#include "src/detail/epoll/op.hpp"
#include "src/detail/timer_service.hpp"
#include "src/detail/make_err.hpp"
#include "src/detail/posix/resolver_service.hpp"
#include "src/detail/posix/signals.hpp"
Expand Down
6 changes: 2 additions & 4 deletions src/corosio/src/detail/epoll/scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
#if BOOST_COROSIO_HAS_EPOLL

#include <boost/corosio/detail/config.hpp>
#include <boost/corosio/detail/scheduler.hpp>
#include <boost/capy/ex/execution_context.hpp>

#include "src/detail/scheduler_impl.hpp"
#include "src/detail/scheduler_op.hpp"
#include "src/detail/timer_service.hpp"

#include <atomic>
#include <condition_variable>
Expand Down Expand Up @@ -53,7 +52,7 @@ struct scheduler_context;
All public member functions are thread-safe.
*/
class epoll_scheduler
: public scheduler
: public scheduler_impl
, public capy::execution_context::service
{
public:
Expand Down Expand Up @@ -255,7 +254,6 @@ class epoll_scheduler
mutable std::atomic<long> outstanding_work_;
bool stopped_;
bool shutdown_;
timer_service* timer_svc_ = nullptr;

// True while a thread is blocked in epoll_wait. Used by
// wake_one_thread_and_unlock and work_finished to know when
Expand Down
7 changes: 3 additions & 4 deletions src/corosio/src/detail/iocp/scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
#if BOOST_COROSIO_HAS_IOCP

#include <boost/corosio/detail/config.hpp>
#include <boost/corosio/detail/scheduler.hpp>
#include <boost/capy/ex/execution_context.hpp>

#include "src/detail/scheduler_impl.hpp"
#include <system_error>

#include "src/detail/scheduler_op.hpp"
Expand All @@ -34,10 +35,9 @@ namespace boost::corosio::detail {
// Forward declarations
struct overlapped_op;
class win_timers;
class timer_service;

class win_scheduler
: public scheduler
: public scheduler_impl
, public capy::execution_context::service
{
public:
Expand Down Expand Up @@ -90,7 +90,6 @@ class win_scheduler
mutable win_mutex dispatch_mutex_;
mutable op_queue completed_ops_;
std::unique_ptr<win_timers> timers_;
timer_service* timer_svc_ = nullptr;
};

} // namespace boost::corosio::detail
Expand Down
1 change: 1 addition & 0 deletions src/corosio/src/detail/kqueue/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "src/detail/kqueue/scheduler.hpp"
#include "src/detail/kqueue/op.hpp"
#include "src/detail/timer_service.hpp"
#include "src/detail/make_err.hpp"
#include "src/detail/posix/resolver_service.hpp"
#include "src/detail/posix/signals.hpp"
Expand Down
Loading
Loading