src/corosio/src/timer.cpp

94.5% Lines (52/55) 100.0% List of functions (10/10) 80.0% Branches (16/20)
timer.cpp
f(x) Functions (10)
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 3861x timer::~timer() = default;
17
18 3863x timer::timer(capy::execution_context& ctx)
19
1/1
✓ Branch 2 → 3 taken 3861 times.
3863x : io_object(create_handle<detail::timer_service>(ctx))
20 {
21 3861x }
22
23 // Not inline: wait_awaitable::await_suspend (defined in timer.hpp) calls
24 // this from translation units that may never include timer_service.hpp,
25 // so this must be the one strong definition the linker can always find
26 // wherever a detail::timer is used (every such user also needs timer's
27 // constructors, defined in this same translation unit).
28 std::coroutine_handle<>
29 3458x timer::implementation::wait(waiter_node& w)
30 {
31 // Already-expired fast path — no publication, no mutex.
32 // Post instead of dispatch so the coroutine yields to the
33 // scheduler, allowing other queued work to run.
34
2/2
✓ Branch 3 → 4 taken 51 times.
✓ Branch 3 → 9 taken 3407 times.
3458x if (already_expired())
35 {
36 51x w.ec_ = {};
37 51x w.d_.post(w.cont_);
38 51x return std::noop_coroutine();
39 }
40 3407x return publish(w);
41 }
42
43 std::coroutine_handle<>
44 3412x timer::implementation::publish(waiter_node& w)
45 {
46 // Publication-last invariant: fully initialize the waiter, count
47 // its work, and arm cancellation BEFORE insert_waiter() publishes
48 // it into the heap/waiter slot where a concurrent run() thread can fire
49 // it. impl_ stays null until insert_waiter() sets it under the
50 // mutex, so a stop callback that fires early (cancel_waiter) sees a
51 // null impl_ and is a safe no-op. To avoid losing such an early
52 // cancel, insert_waiter() re-checks stop_requested() under the lock
53 // and completes as canceled if it fires in this window.
54 3412x w.impl_ = nullptr;
55 3412x w.svc_ = svc_;
56
57 3412x might_have_pending_waits_.store(true, std::memory_order_relaxed);
58 3412x svc_->get_scheduler().work_started();
59
60
2/2
✓ Branch 6 → 7 taken 2076 times.
✓ Branch 6 → 8 taken 1336 times.
3412x if (w.token_->stop_possible())
61 2076x w.arm_stop_cb();
62
63 3412x svc_->insert_waiter(*this, &w);
64
65 3412x return std::noop_coroutine();
66 }
67
68 std::coroutine_handle<>
69 5x timer::publish_wait(waiter_node& w)
70 {
71 5x return get().publish(w);
72 }
73
74 bool
75 6x timer::rearm_wait(waiter_node& w, duration d) noexcept
76 {
77 // The single waiter was popped before its op ran, so the impl is
78 // out of the heap with no published waiters: expires_after only
79 // stores the saturated expiry, and writing it is race-free.
80 6x expires_after(d);
81 6x auto& impl = get();
82 // The drain that popped the waiter cleared the flag.
83 6x impl.might_have_pending_waits_.store(true, std::memory_order_relaxed);
84 try
85 {
86
1/1
✓ Branch 5 → 6 taken 6 times.
6x impl.svc_->insert_waiter(impl, &w);
87 }
88 catch(std::bad_alloc const&)
89 {
90 // insert_waiter grows the heap before publishing anything,
91 // so the waiter is untouched and the caller can complete
92 // the wait through the normal resume path.
93 return false;
94 }
95 6x return true;
96 }
97
98 // completion_op and canceller definitions live here, non-inline, for
99 // the same reason wait() does: the inline waiter_node constructor in
100 // timer.hpp references do_complete and the vtable from translation
101 // units that never include timer_service.hpp.
102
103 void
104 2057x waiter_node::canceller::operator()() const
105 {
106 2057x waiter_->svc_->cancel_waiter(waiter_);
107 2057x }
108
109 void
110 3403x waiter_node::completion_op::do_complete(
111 [[maybe_unused]] void* owner,
112 scheduler_op* base,
113 std::uint32_t,
114 std::uint32_t)
115 {
116 // owner is always non-null here. The destroy path (owner == nullptr)
117 // is unreachable because completion_op overrides destroy() directly,
118 // bypassing scheduler_op::destroy() which would call func_(nullptr, ...).
119
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3403 times.
3403x BOOST_COROSIO_ASSERT(owner);
120 3403x static_cast<completion_op*>(base)->operator()();
121 3403x }
122
123 void
124 3403x waiter_node::completion_op::operator()()
125 {
126 // The node lives in the resuming coroutine's frame: posting the
127 // continuation is the last access, since the frame (and node)
128 // may complete and die on another thread immediately after.
129 3403x auto* w = waiter_;
130 // A true return means the waiter re-published itself: the frame
131 // stays suspended, the wait's work count stays live, and the
132 // node may already be firing on another thread — no access past
133 // this point.
134
6/6
✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 6 taken 3393 times.
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 6 taken 4 times.
✓ Branch 7 → 8 taken 6 times.
✓ Branch 7 → 9 taken 3397 times.
3403x if (w->on_fire_ && w->on_fire_(w->on_fire_ctx_))
135 6x return;
136 3397x w->reset_stop_cb();
137 3397x auto d = w->d_;
138 3397x auto& sched = w->svc_->get_scheduler();
139
1/1
✓ Branch 11 → 12 taken 3397 times.
3397x d.post(w->cont_);
140 3397x sched.work_finished();
141 }
142
143 void
144 1x waiter_node::completion_op::destroy()
145 {
146 // Called during scheduler shutdown drain when this completion_op is
147 // in the scheduler's ready queue (posted by cancel_timer() or
148 // process_expired()). Balances the work_started() from
149 // implementation::wait(), keeping the run-loop counter sane; no
150 // shutdown path waits on that counter.
151 //
152 // This override also prevents scheduler_op::destroy() from calling
153 // do_complete(nullptr, ...). See also: timer_service::shutdown()
154 // which drains waiters still in the timer heap (the other path).
155 // Destroying the frame also ends the node's storage, so it is
156 // the last access.
157 1x auto* w = waiter_;
158 1x w->reset_stop_cb();
159 1x auto h = std::exchange(w->h_, {});
160 1x auto& sched = w->svc_->get_scheduler();
161 1x sched.work_finished();
162
1/2
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
1x if (h)
163
1/1
✓ Branch 9 → 10 taken 1 time.
1x h.destroy();
164 1x }
165
166 } // namespace boost::corosio::detail
167