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
12 changes: 12 additions & 0 deletions include/boost/corosio/cancel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ cancel_after(capy::IoAwaitable auto&& op, timer& t, timer::duration timeout)
@note Creates a timer per call. Use the explicit-timer overload
to amortize allocation across multiple timeouts.

@note The awaiting coroutine's executor must be backed by an
io_context (the deadline timer is built from it). Awaiting this
on a non-io_context executor is a precondition violation and
aborts; use the explicit-timer overload to construct the timer
yourself if you need a catchable error.

@par Example
@code
auto [ec, n] = co_await cancel_at(
Expand Down Expand Up @@ -175,6 +181,12 @@ cancel_at(capy::IoAwaitable auto&& op, timer::time_point deadline)
@note Creates a timer per call. Use the explicit-timer overload
to amortize allocation across multiple timeouts.

@note The awaiting coroutine's executor must be backed by an
io_context (the deadline timer is built from it). Awaiting this
on a non-io_context executor is a precondition violation and
aborts; use the explicit-timer overload to construct the timer
yourself if you need a catchable error.

@par Example
@code
auto [ec, n] = co_await cancel_after(
Expand Down
25 changes: 24 additions & 1 deletion include/boost/corosio/detail/cancel_at_awaitable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
#define BOOST_COROSIO_DETAIL_CANCEL_AT_AWAITABLE_HPP

#include <boost/corosio/detail/timeout_coro.hpp>
#include <boost/corosio/detail/except.hpp>
#include <boost/capy/ex/io_env.hpp>

#include <chrono>
#include <coroutine>
#include <new>
#include <optional>
#include <stdexcept>
#include <stop_token>
#include <type_traits>
#include <utility>
Expand Down Expand Up @@ -126,7 +128,28 @@ struct cancel_at_awaitable
auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
{
if constexpr (Owning)
timer_.emplace(env->executor.context());
{
// The deadline timer is built here from the awaiting
// coroutine's executor context, the first point at which it
// is known. await_suspend is driven through a noexcept
// wrapper, so a failure cannot be surfaced as a catchable
// exception. An executor whose context is not an io_context
// cannot supply a timer service; silently running the
// operation with no deadline would be a worse failure than
// aborting, so translate the service-lookup error into a
// clear precondition diagnostic. This terminates by design
// (a usage error) rather than dropping the requested timeout.
try
{
timer_.emplace(env->executor.context());
}
catch (std::logic_error const&)
{
throw_logic_error(
"cancel_after/cancel_at requires an "
"io_context-backed executor");
}
}

timer_->expires_at(deadline_);

Expand Down
26 changes: 2 additions & 24 deletions include/boost/corosio/detail/timer_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#define BOOST_COROSIO_DETAIL_TIMER_SERVICE_HPP

#include <boost/corosio/timer.hpp>
#include <boost/corosio/io_context.hpp>
#include <boost/corosio/detail/scheduler.hpp>
#include <boost/corosio/detail/scheduler_op.hpp>
#include <boost/corosio/detail/intrusive.hpp>
#include <boost/corosio/detail/thread_local_ptr.hpp>
Expand Down Expand Up @@ -891,26 +891,6 @@ timer_service::implementation::wait(

// Free functions

struct timer_service_access
{
static timer_service& get_timer(io_context& ctx) noexcept
{
return *ctx.timer_svc_;
}

static void set_timer(io_context& ctx, timer_service& svc) noexcept
{
ctx.timer_svc_ = &svc;
}
};

// Bypass find_service() mutex by reading io_context's cached pointer
inline io_object::io_service&
timer_service_direct(capy::execution_context& ctx) noexcept
{
return timer_service_access::get_timer(static_cast<io_context&>(ctx));
}

inline std::size_t
timer_service_update_expiry(timer::implementation& base)
{
Expand All @@ -935,9 +915,7 @@ timer_service_cancel_one(timer::implementation& base) noexcept
inline timer_service&
get_timer_service(capy::execution_context& ctx, scheduler& sched)
{
auto& svc = ctx.make_service<timer_service>(sched);
timer_service_access::set_timer(static_cast<io_context&>(ctx), svc);
return svc;
return ctx.make_service<timer_service>(sched);
}

} // namespace boost::corosio::detail
Expand Down
4 changes: 0 additions & 4 deletions include/boost/corosio/io_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ struct io_context_options

namespace detail {
class timer_service;
struct timer_service_access;
} // namespace detail

/** An I/O context for running asynchronous operations.
Expand Down Expand Up @@ -201,8 +200,6 @@ struct timer_service_access;
*/
class BOOST_COROSIO_DECL io_context : public capy::execution_context
{
friend struct detail::timer_service_access;

/// Pre-create services that depend on options (before construct).
void apply_options_pre_(io_context_options const& opts);

Expand All @@ -215,7 +212,6 @@ class BOOST_COROSIO_DECL io_context : public capy::execution_context
void configure_single_threaded_();

protected:
detail::timer_service* timer_svc_ = nullptr;
detail::scheduler* sched_;

public:
Expand Down
12 changes: 12 additions & 0 deletions include/boost/corosio/native/native_cancel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ cancel_after(
@note Creates a timer per call. Use the explicit-timer overload
to amortize allocation across multiple timeouts.

@note The awaiting coroutine's executor must be backed by an
io_context (the deadline timer is built from it). Awaiting this
on a non-io_context executor is a precondition violation and
aborts; use the explicit-timer overload to construct the timer
yourself if you need a catchable error.

@par Example
@code
auto [ec, n] = co_await cancel_at<epoll>(
Expand Down Expand Up @@ -195,6 +201,12 @@ cancel_at(capy::IoAwaitable auto&& op, timer::time_point deadline)
@note Creates a timer per call. Use the explicit-timer overload
to amortize allocation across multiple timeouts.

@note The awaiting coroutine's executor must be backed by an
io_context (the deadline timer is built from it). Awaiting this
on a non-io_context executor is a precondition violation and
aborts; use the explicit-timer overload to construct the timer
yourself if you need a catchable error.

@par Example
@code
auto [ec, n] = co_await cancel_after<epoll>(
Expand Down
46 changes: 46 additions & 0 deletions include/boost/corosio/native/native_timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,52 @@ class native_timer : public timer
{
}

/** Construct a native timer from an executor.

The timer is associated with the executor's context, which must
be a corosio io_context.

@param ex The executor whose context will own this timer.

@throws std::logic_error if the executor's context is not an
io_context.
*/
template<class Ex>
requires(!std::same_as<std::remove_cvref_t<Ex>, native_timer>) &&
capy::Executor<Ex>
explicit native_timer(Ex const& ex) : native_timer(ex.context())
{
}

/** Construct a native timer from an executor with an absolute expiry.

@param ex The executor whose context will own this timer.
@param t The initial expiry time point.

@throws std::logic_error if the executor's context is not an
io_context.
*/
template<class Ex>
requires capy::Executor<Ex>
native_timer(Ex const& ex, time_point t) : native_timer(ex.context(), t)
{
}

/** Construct a native timer from an executor with a relative expiry.

@param ex The executor whose context will own this timer.
@param d The initial expiry duration relative to now.

@throws std::logic_error if the executor's context is not an
io_context.
*/
template<class Ex, class Rep, class Period>
requires capy::Executor<Ex>
native_timer(Ex const& ex, std::chrono::duration<Rep, Period> d)
: native_timer(ex.context(), d)
{
}

/** Move construct.

@param other The timer to move from.
Expand Down
66 changes: 63 additions & 3 deletions include/boost/corosio/timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
#include <boost/capy/concept/executor.hpp>

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

namespace boost::corosio {

Expand Down Expand Up @@ -58,21 +60,33 @@ class BOOST_COROSIO_DECL timer : public io_timer

/** Construct a timer from an execution context.

@param ctx The execution context that will own this timer.
@param ctx The execution context that will own this timer. It
must be a corosio io_context; otherwise the constructor
throws (a timer service is required).

@throws std::logic_error if @p ctx is not an io_context.
*/
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 ctx The execution context that will own this timer. It
must be a corosio io_context; otherwise the constructor
throws (a timer service is required).
@param t The initial expiry time point.

@throws std::logic_error if @p ctx is not an io_context.
*/
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 ctx The execution context that will own this timer. It
must be a corosio io_context; otherwise the constructor
throws (a timer service is required).
@param d The initial expiry duration relative to now.

@throws std::logic_error if @p ctx is not an io_context.
*/
template<class Rep, class Period>
timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
Expand All @@ -81,6 +95,52 @@ class BOOST_COROSIO_DECL timer : public io_timer
expires_after(d);
}

/** Construct a timer from an executor.

The timer is associated with the executor's context, which must
be a corosio io_context.

@param ex The executor whose context will own this timer.

@throws std::logic_error if the executor's context is not an
io_context.
*/
template<class Ex>
requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) &&
capy::Executor<Ex>
explicit timer(Ex const& ex) : timer(ex.context())
{
}

/** Construct a timer from an executor with an absolute expiry time.

@param ex The executor whose context will own this timer.
@param t The initial expiry time point.

@throws std::logic_error if the executor's context is not an
io_context.
*/
template<class Ex>
requires capy::Executor<Ex>
timer(Ex const& ex, time_point t) : timer(ex.context(), t)
{
}

/** Construct a timer from an executor with a relative expiry time.

@param ex The executor whose context will own this timer.
@param d The initial expiry duration relative to now.

@throws std::logic_error if the executor's context is not an
io_context.
*/
template<class Ex, class Rep, class Period>
requires capy::Executor<Ex>
timer(Ex const& ex, std::chrono::duration<Rep, Period> d)
: timer(ex.context(), d)
{
}

/** Move constructor.

Transfers ownership of the timer resources.
Expand Down
2 changes: 1 addition & 1 deletion src/corosio/src/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace boost::corosio {
timer::~timer() = default;

timer::timer(capy::execution_context& ctx)
: io_timer(handle(ctx, detail::timer_service_direct(ctx)))
: io_timer(create_handle<detail::timer_service>(ctx))
{
}

Expand Down
13 changes: 13 additions & 0 deletions test/unit/native/native_timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ struct native_timer_test
BOOST_TEST(t.expiry() > timer::time_point{});
}

// Issue #231: native_timer mirrors timer's executor constructors.
void testTimerConstructFromExecutor()
{
io_context ctx(Backend);
native_timer<Backend> t(ctx.get_executor());
BOOST_TEST_PASS();

native_timer<Backend> t2(
ctx.get_executor(), std::chrono::milliseconds(100));
BOOST_TEST(t2.expiry() > timer::time_point{});
}

void testTimerWait()
{
io_context ctx(Backend);
Expand Down Expand Up @@ -106,6 +118,7 @@ struct native_timer_test
{
testTimerConstruct();
testTimerConstructDuration();
testTimerConstructFromExecutor();
testTimerWait();
testTimerWaitExpired();
testTimerPolymorphicSlice();
Expand Down
Loading
Loading