STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
HoldTimer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4#include <chrono>
5#include <condition_variable>
6#include <cstdint>
7#include <functional>
8#include <mutex>
9#include <string>
10#include <thread>
11
12namespace sequencer {
13
14/**
15 * What one hold is: how long, and who to tell.
16 *
17 * The durations arrive here rather than living on the timer as members. That is deliberate --
18 * FireManager carried them in `fire_duration_ms_` behind a `configure()` setter documented "call
19 * before start()", and reloadConfig() was exactly the caller that forgot, so a burn silently ran
20 * the previous config's window. A number that is passed in cannot go stale.
21 */
22struct HoldSpec {
23 /** How long to hold, in milliseconds. */
24 uint32_t duration_ms = 0;
25 /** What extend() restarts the countdown at, measured from the extend call. 0 refuses extend()
26 * entirely -- a characterization pulse has a duration the operator asked for, and stretching
27 * it silently would make the mass they weighed describe a different window. */
28 uint32_t extended_ms = 0;
29 /** Called when the hold ends of its own accord. Not called when stop() cancels it. */
30 std::function<void()> on_expire;
31 /** true when the hold begins, false when it ends by any path. nullptr means nobody is told --
32 * which is how a hold that the controller must not hear about simply has no way to notify it,
33 * rather than relying on a suppression flag someone can forget to check. */
34 std::function<void(bool)> on_active;
35 /** Names this hold in the log ("FIRE", "Flow Test"). */
36 std::string label = "HOLD";
37};
38
39/**
40 * A cancellable hold of an exact length, and nothing else.
41 *
42 * Owns no I/O and knows nothing about states: it counts down and raises callbacks, and its owner
43 * decides what they mean. That is why the same object serves both the burn window and a
44 * characterization pulse -- the only difference between them is the HoldSpec handed to start().
45 *
46 * One instance per owner, not one per kind of hold. The rig occupies exactly one state at a time,
47 * so exactly one hold can be running; a second timer object would make "both armed at once"
48 * representable, and the failure mode there is a valve held open by a timer nobody is watching.
49 *
50 * The countdown is a steady_clock deadline waited on with condition_variable::wait_until. The
51 * predecessor slept in 50 ms steps and added a nominal 50 to a counter, so it measured sleep
52 * *calls* rather than time: every iteration overshot, the error only ever accumulated in one
53 * direction, and a requested duration quantised to the step. A deadline cannot drift, and the wait
54 * wakes immediately on stop() instead of at the next step boundary.
55 *
56 * Threading: start() and stop() are not safe to call concurrently with each other -- the caller
57 * serialises them (SequencerService does, through transitionTo). extend() and isActive() are safe
58 * from any thread.
59 */
60class HoldTimer {
61public:
62 using Clock = std::chrono::steady_clock;
63
64 HoldTimer() = default;
65 ~HoldTimer();
66
67 HoldTimer(const HoldTimer&) = delete;
68 HoldTimer& operator=(const HoldTimer&) = delete;
69
70 /**
71 * Begin a hold. Supersedes any hold already running (which is stopped first, so its on_expire
72 * never fires). Returns as soon as the countdown is armed; expiry arrives on a timer thread.
73 */
74 void start(HoldSpec spec);
75
76 /**
77 * Cancel the hold and reap the timer thread. Raises on_active(false) if a hold was actually
78 * running, so the "it ended" notification happens exactly once whichever way it ended.
79 * Safe to call when nothing is running, and safe to call from on_expire (see the .cpp).
80 */
81 void stop();
82
83 /**
84 * Restart the countdown at spec.extended_ms, measured from now.
85 * @return false if no hold is running, or if this hold set extended_ms = 0.
86 */
87 bool extend();
88
89 bool isActive() const {
90 return active_.load();
91 }
92
93 /** The window currently being counted -- extended_ms after a successful extend(). */
94 uint32_t currentDurationMs() const;
95
96private:
97 void runTimer();
98
99 mutable std::mutex mtx_;
100 std::condition_variable cv_;
101 std::thread timer_thread_;
102
103 /** Read without the lock by isActive() and by the wait loop, so atomic. */
104 std::atomic<bool> active_{false};
105 /** Set by extend() to make the timer thread recompute its deadline. */
106 bool restart_ = false;
107 Clock::time_point started_at_{};
109
111};
112
113} // namespace sequencer
HoldTimer()=default
bool isActive() const
Definition HoldTimer.hpp:89
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_
std::chrono::steady_clock Clock
Definition HoldTimer.hpp:62
bool extend()
Restart the countdown at spec.extended_ms, measured from now.
Definition HoldTimer.cpp:88
uint32_t current_duration_ms_
HoldTimer & operator=(const HoldTimer &)=delete
HoldTimer(const HoldTimer &)=delete
What one hold is: how long, and who to tell.
Definition HoldTimer.hpp:22
std::string label
Names this hold in the log ("FIRE", "Flow Test").
Definition HoldTimer.hpp:36
std::function< void()> on_expire
Called when the hold ends of its own accord.
Definition HoldTimer.hpp:30
uint32_t duration_ms
How long to hold, in milliseconds.
Definition HoldTimer.hpp:24
std::function< void(bool)> on_active
true when the hold begins, false when it ends by any path.
Definition HoldTimer.hpp:34
uint32_t extended_ms
What extend() restarts the countdown at, measured from the extend call.
Definition HoldTimer.hpp:28