src/corosio/src/timer.cpp

68.4% Lines (39/57) 46.2% List of functions (6/13) 35.7% Branches (5/14)
timer.cpp
f(x) Functions (13)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Steve Gerbino
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #include <boost/corosio/detail/timer.hpp>
12 #include <boost/corosio/detail/timer_service.hpp>
13
14 namespace boost::corosio::detail {
15
16 17974x timer::~timer() = default;
17
18 17982x timer::timer(capy::execution_context& ctx)
19 8991x : io_object(create_handle<detail::timer_service>(ctx))
20 17982x {
21 17982x }
22
23 timer::timer(capy::execution_context& ctx, time_point t) : timer(ctx)
24 {
25 expires_at(t);
26 }
27
28 timer::timer(timer&& other) noexcept : io_object(std::move(other)) {}
29
30 timer&
31 timer::operator=(timer&& other) noexcept
32 {
33 if (this != &other)
34 h_ = std::move(other.h_);
35 return *this;
36 }
37
38 std::size_t
39 timer::do_cancel()
40 {
41 return detail::timer_service_cancel(get());
42 }
43
44 std::size_t
45 timer::do_cancel_one()
46 {
47 return detail::timer_service_cancel_one(get());
48 }
49
50 std::size_t
51 timer::do_update_expiry()
52 {
53 return detail::timer_service_update_expiry(get());
54 }
55
56 // Not inline: wait_awaitable::await_suspend (defined in timer.hpp) calls
57 // this from translation units that may never include timer_service.hpp,
58 // so this must be the one strong definition the linker can always find
59 // wherever a detail::timer is used (every such user also needs timer's
60 // constructors, defined in this same translation unit).
61 std::coroutine_handle<>
62 8306x timer::implementation::wait(waiter_node& w)
63 {
64 // Already-expired fast path — no publication, no mutex.
65 // Post instead of dispatch so the coroutine yields to the
66 // scheduler, allowing other queued work to run.
67
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8305 times.
8306x if (already_expired())
68 {
69 1x w.ec_ = {};
70 1x w.d_.post(w.cont_);
71 1x return std::noop_coroutine();
72 }
73
74 // Publication-last invariant: fully initialize the waiter, count
75 // its work, and arm cancellation BEFORE insert_waiter() publishes
76 // it into the heap/list where a concurrent run() thread can fire
77 // it. impl_ stays null until insert_waiter() sets it under the
78 // mutex, so a stop callback that fires early (cancel_waiter) sees a
79 // null impl_ and is a safe no-op. To avoid losing such an early
80 // cancel, insert_waiter() re-checks stop_requested() under the lock
81 // and completes as canceled if it fires in this window.
82 8305x w.impl_ = nullptr;
83 8305x w.svc_ = svc_;
84
85 8305x might_have_pending_waits_.store(true, std::memory_order_relaxed);
86 8305x svc_->get_scheduler().work_started();
87
88
2/2
✓ Branch 0 taken 6298 times.
✓ Branch 1 taken 2007 times.
8305x if (w.token_->stop_possible())
89 2007x w.arm_stop_cb();
90
91 8305x svc_->insert_waiter(*this, &w);
92
93 8305x return std::noop_coroutine();
94 8306x }
95
96 // completion_op and canceller definitions live here, non-inline, for
97 // the same reason wait() does: the inline waiter_node constructor in
98 // timer.hpp references do_complete and the vtable from translation
99 // units that never include timer_service.hpp.
100
101 void
102 1974x waiter_node::canceller::operator()() const
103 {
104 1974x waiter_->svc_->cancel_waiter(waiter_);
105 1974x }
106
107 void
108 waiter_node::completion_op::do_complete(
109 [[maybe_unused]] void* owner,
110 scheduler_op* base,
111 std::uint32_t,
112 std::uint32_t)
113 {
114 // owner is always non-null here. The destroy path (owner == nullptr)
115 // is unreachable because completion_op overrides destroy() directly,
116 // bypassing scheduler_op::destroy() which would call func_(nullptr, ...).
117 BOOST_COROSIO_ASSERT(owner);
118 static_cast<completion_op*>(base)->operator()();
119 }
120
121 void
122 8277x waiter_node::completion_op::operator()()
123 {
124 // The node lives in the resuming coroutine's frame: posting the
125 // continuation is the last access, since the frame (and node)
126 // may complete and die on another thread immediately after.
127 8277x auto* w = waiter_;
128 8277x w->reset_stop_cb();
129 8277x auto d = w->d_;
130 8277x auto& sched = w->svc_->get_scheduler();
131 8277x d.post(w->cont_);
132 8277x sched.work_finished();
133 8277x }
134
135 void
136 2x waiter_node::completion_op::destroy()
137 {
138 // Called during scheduler shutdown drain when this completion_op is
139 // in the scheduler's ready queue (posted by cancel_timer() or
140 // process_expired()). Balances the work_started() from
141 // implementation::wait(). The scheduler drain loop separately
142 // balances the work_started() from post(). On IOCP both decrements
143 // are required for outstanding_work_ to reach zero; on other
144 // backends this is harmless.
145 //
146 // This override also prevents scheduler_op::destroy() from calling
147 // do_complete(nullptr, ...). See also: timer_service::shutdown()
148 // which drains waiters still in the timer heap (the other path).
149 // Destroying the frame also ends the node's storage, so it is
150 // the last access.
151 2x auto* w = waiter_;
152 2x w->reset_stop_cb();
153 2x auto h = std::exchange(w->h_, {});
154 2x auto& sched = w->svc_->get_scheduler();
155 2x sched.work_finished();
156
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2x if (h)
157 2x h.destroy();
158 2x }
159
160 } // namespace boost::corosio::detail
161