src/corosio/src/timer.cpp

74.5% Lines (41/55) 53.8% List of functions (7/13) 64.3% Branches (9/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 3182x timer::~timer() = default;
17
18 3184x timer::timer(capy::execution_context& ctx)
19
1/1
✓ Branch 2 → 3 taken 3182 times.
3184x : io_object(create_handle<detail::timer_service>(ctx))
20 {
21 3182x }
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 2441x 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 3 → 4 taken 572 times.
✓ Branch 3 → 9 taken 1869 times.
2441x if (already_expired())
68 {
69 572x w.ec_ = {};
70 572x w.d_.post(w.cont_);
71 572x 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 1869x w.impl_ = nullptr;
83 1869x w.svc_ = svc_;
84
85 1869x might_have_pending_waits_.store(true, std::memory_order_relaxed);
86 1869x svc_->get_scheduler().work_started();
87
88
2/2
✓ Branch 13 → 14 taken 1432 times.
✓ Branch 13 → 15 taken 437 times.
1869x if (w.token_->stop_possible())
89 1432x w.arm_stop_cb();
90
91 1869x svc_->insert_waiter(*this, &w);
92
93 1869x return std::noop_coroutine();
94 }
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 1416x waiter_node::canceller::operator()() const
103 {
104 1416x waiter_->svc_->cancel_waiter(waiter_);
105 1416x }
106
107 void
108 1855x 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
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1855 times.
1855x BOOST_COROSIO_ASSERT(owner);
118 1855x static_cast<completion_op*>(base)->operator()();
119 1855x }
120
121 void
122 1855x 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 1855x auto* w = waiter_;
128 1855x w->reset_stop_cb();
129 1855x auto d = w->d_;
130 1855x auto& sched = w->svc_->get_scheduler();
131
1/1
✓ Branch 4 → 5 taken 1855 times.
1855x d.post(w->cont_);
132 1855x sched.work_finished();
133 1855x }
134
135 void
136 1x 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 1x auto* w = waiter_;
152 1x w->reset_stop_cb();
153 1x auto h = std::exchange(w->h_, {});
154 1x auto& sched = w->svc_->get_scheduler();
155 1x sched.work_finished();
156
1/2
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
1x if (h)
157
1/1
✓ Branch 9 → 10 taken 1 time.
1x h.destroy();
158 1x }
159
160 } // namespace boost::corosio::detail
161