STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
HoldTimer.cpp
Go to the documentation of this file.
2
3#include <iostream>
4
5namespace sequencer {
6
10
11// ─────────────────────────────────────────────────────────────────────────────
13 stop(); // reap any previous timer, including one that expired naturally (see stop())
14
15 std::function<void(bool)> on_active;
16 uint32_t duration = 0;
17 std::string label;
18 {
19 std::lock_guard<std::mutex> lk(mtx_);
20 spec_ = std::move(spec);
21 current_duration_ms_ = spec_.duration_ms;
22 restart_ = false;
23 active_ = true;
24 on_active = spec_.on_active;
25 duration = current_duration_ms_;
26 label = spec_.label;
27 }
28
29 // Notify BEFORE the clock starts. on_active can be a TCP connect to another service with a
30 // timeout of its own; if the deadline were anchored ahead of it, a slow or unreachable peer
31 // would eat that time out of the hold and shorten the window the operator asked for.
32 if (on_active)
33 on_active(true);
34
35 {
36 std::lock_guard<std::mutex> lk(mtx_);
37 started_at_ = Clock::now();
38 }
39 std::cout << "[HoldTimer] " << label << " started (" << duration << " ms)" << std::endl;
40
41 timer_thread_ = std::thread([this]() {
42 runTimer();
43 });
44}
45
46// ─────────────────────────────────────────────────────────────────────────────
48 // Claim the "it ended" notification with an exchange, so exactly one of stop() and runTimer()
49 // raises on_active(false). A hold that expired naturally has already set active_ false and
50 // notified, and must not be announced as stopped a second time.
51 const bool was_active = active_.exchange(false);
52
53 std::function<void(bool)> on_active;
54 std::string label;
55 {
56 std::lock_guard<std::mutex> lk(mtx_);
57 restart_ = false;
58 on_active = spec_.on_active;
59 label = spec_.label;
60 cv_.notify_all();
61 }
62
63 // Reap the thread whenever it is joinable, not only when a hold was active: runTimer() clears
64 // active_ before invoking on_expire_, so a naturally expired hold leaves a joinable thread
65 // behind with active_ already false. Skipping it there would leave start() move-assigning onto
66 // a joinable std::thread, which is std::terminate ("terminate called without an active
67 // exception").
68 if (timer_thread_.joinable()) {
69 if (timer_thread_.get_id() == std::this_thread::get_id()) {
70 // We are ON the timer thread: on_expire_ ran a state transition, and leaving the held
71 // state called back into stop(). A thread cannot join itself, and it is about to
72 // return anyway, so detach. Note this is reached only after the exchange above, so it
73 // cannot deadlock against the outer caller.
74 timer_thread_.detach();
75 } else {
76 timer_thread_.join();
77 }
78 }
79
80 if (was_active) {
81 if (on_active)
82 on_active(false);
83 std::cout << "[HoldTimer] " << label << " stopped" << std::endl;
84 }
85}
86
87// ─────────────────────────────────────────────────────────────────────────────
89 std::lock_guard<std::mutex> lk(mtx_);
90 if (!active_.load())
91 return false;
92 if (spec_.extended_ms == 0)
93 return false; // this hold is a fixed window by construction
94
95 current_duration_ms_ = spec_.extended_ms;
96 started_at_ = Clock::now(); // extended_ms runs from the extend call, not from the start
97 restart_ = true;
98 cv_.notify_all();
99 std::cout << "[HoldTimer] " << spec_.label << " extended to " << spec_.extended_ms << " ms"
100 << std::endl;
101 return true;
102}
103
104// ─────────────────────────────────────────────────────────────────────────────
106 std::lock_guard<std::mutex> lk(mtx_);
108}
109
110// ─────────────────────────────────────────────────────────────────────────────
112 std::unique_lock<std::mutex> lk(mtx_);
113 for (;;) {
114 const auto deadline = started_at_ + std::chrono::milliseconds(current_duration_ms_);
115 // Re-check the clock rather than passing a predicate: wait_until may return early for a
116 // spurious wake, and the deadline is the authority on whether the hold is over.
117 while (active_.load() && !restart_ && Clock::now() < deadline)
118 cv_.wait_until(lk, deadline);
119
120 if (!active_.load())
121 return; // stop() cancelled us; it owns the notification and the reaping
122 if (restart_) {
123 restart_ = false;
124 continue; // extend() moved the deadline
125 }
126 break; // deadline reached
127 }
128
129 active_ = false;
130 // Copy the callbacks and release the lock before calling out. on_expire_ transitions state,
131 // which calls back into stop() on this very thread; running that under mtx_ would deadlock on
132 // a non-recursive mutex.
133 auto on_expire = spec_.on_expire;
134 auto on_active = spec_.on_active;
135 const std::string label = spec_.label;
136 lk.unlock();
137
138 std::cout << "[HoldTimer] " << label << " expired" << std::endl;
139 if (on_active)
140 on_active(false);
141 if (on_expire)
142 on_expire();
143}
144
145} // namespace sequencer
void start(HoldSpec spec)
Begin a hold.
Definition HoldTimer.cpp:12
bool restart_
Set by extend() to make the timer thread recompute its deadline.
void stop()
Cancel the hold and reap the timer thread.
Definition HoldTimer.cpp:47
std::atomic< bool > active_
Read without the lock by isActive() and by the wait loop, so atomic.
std::condition_variable cv_
std::thread timer_thread_
uint32_t currentDurationMs() const
The window currently being counted – extended_ms after a successful extend().
Clock::time_point started_at_
bool extend()
Restart the countdown at spec.extended_ms, measured from now.
Definition HoldTimer.cpp:88
uint32_t current_duration_ms_
What one hold is: how long, and who to tell.
Definition HoldTimer.hpp:22