77// / \brief Timer scheduler that provides Qt-like timer functionality.
88// /
99// / TimerScheduler manages timers that can be processed either by a dedicated
10- // / worker thread or manually via process/update calls.
10+ // / worker thread or manually via process/update calls. Timers are rescheduled
11+ // / using fixed-rate semantics, meaning the next activation time is based on the
12+ // / previously scheduled fire time. Cancelled timers are removed lazily from the
13+ // / internal queue, which can temporarily increase the queue size under frequent
14+ // / start/stop cycles.
1115
1216#include " config.hpp"
1317
1418#include < atomic>
19+ #include < cassert>
1520#include < chrono>
1621#include < condition_variable>
1722#include < cstddef>
@@ -37,14 +42,15 @@ namespace time_shield {
3742
3843 // / \brief Internal state shared between Timer and TimerScheduler.
3944 struct TimerState {
40- TimerScheduler* m_scheduler = nullptr ;
41- TimerCallback m_callback;
42- std::chrono::milliseconds m_interval{0 };
43- std::atomic<bool > m_is_single_shot{false };
44- std::atomic<bool > m_is_active{false };
45- std::atomic<bool > m_is_running{false };
46- std::size_t m_id{0 };
47- std::atomic<std::uint64_t > m_generation{0 };
45+ TimerScheduler* m_scheduler = nullptr ;
46+ std::mutex m_callback_mutex;
47+ TimerCallback m_callback;
48+ std::atomic<std::int64_t > m_interval_ms{0 };
49+ std::atomic<bool > m_is_single_shot{false };
50+ std::atomic<bool > m_is_active{false };
51+ std::atomic<bool > m_is_running{false };
52+ std::size_t m_id{0 };
53+ std::atomic<std::uint64_t > m_generation{0 };
4854 };
4955
5056 // / \brief Data stored in the priority queue of scheduled timers.
@@ -100,7 +106,9 @@ namespace time_shield {
100106 // / \brief Starts a dedicated worker thread that processes timers.
101107 // /
102108 // / This method is non-blocking. It spawns a background thread that
103- // / waits for timers to fire and executes their callbacks.
109+ // / waits for timers to fire and executes their callbacks. While the
110+ // / worker thread is active, manual processing via process() or update()
111+ // / must not be used.
104112 void run ();
105113
106114 // / \brief Requests the worker thread to stop and waits for it to exit.
@@ -109,6 +117,8 @@ namespace time_shield {
109117 // / \brief Processes all timers that are ready to fire at the moment of the call.
110118 // /
111119 // / The method is non-blocking: it does not wait for future timers.
120+ // / It must not be called while the worker thread started by run() is
121+ // / active.
112122 void process ();
113123
114124 // / \brief Alias for process() for compatibility with update-based loops.
@@ -151,6 +161,9 @@ namespace time_shield {
151161 Timer& operator =(Timer&&) = delete ;
152162
153163 // / \brief Sets the interval used by the timer.
164+ // /
165+ // / Negative durations are clamped to zero. An interval of zero means
166+ // / the timer is rescheduled immediately after firing.
154167 template <class Rep , class Period >
155168 void set_interval (std::chrono::duration<Rep, Period> interval) noexcept ;
156169
@@ -165,8 +178,17 @@ namespace time_shield {
165178 void start (std::chrono::duration<Rep, Period> interval);
166179
167180 // / \brief Stops the timer.
181+ // /
182+ // / The operation is non-blocking: the method does not wait for a
183+ // / running callback to finish. Use stop_and_wait() to synchronously
184+ // / wait for completion.
168185 void stop ();
169186
187+ // / \brief Stops the timer and waits until an active callback finishes.
188+ // /
189+ // / Must not be called from inside the timer callback itself.
190+ void stop_and_wait ();
191+
170192 // / \brief Sets whether the timer should fire only once.
171193 void set_single_shot (bool is_single_shot) noexcept ;
172194
@@ -183,6 +205,8 @@ namespace time_shield {
183205 void set_callback (Callback callback);
184206
185207 // / \brief Creates a single-shot timer that invokes the callback once.
208+ // /
209+ // / The helper keeps the timer alive until the callback finishes.
186210 template <class Rep , class Period >
187211 static void single_shot (TimerScheduler& scheduler,
188212 std::chrono::duration<Rep, Period> interval,
@@ -235,14 +259,13 @@ namespace time_shield {
235259 lock.lock ();
236260 m_is_worker_running = false ;
237261 m_stop_requested = false ;
238- lock.unlock ();
239- m_thread = std::thread ();
240262 }
241263
242264 inline void TimerScheduler::process () {
243265 std::vector<detail::DueTimer> due;
244266 {
245267 std::lock_guard<std::mutex> lock (m_mutex);
268+ assert (!m_is_worker_running && " process() must not be called while the worker thread is active" );
246269 const auto now = clock::now ();
247270 collect_due_timers_locked (due, now);
248271 }
@@ -358,15 +381,20 @@ namespace time_shield {
358381 continue ;
359382 }
360383
361- state->m_is_running .store (true , std::memory_order_relaxed );
384+ state->m_is_running .store (true , std::memory_order_release );
362385 due.push_back (detail::DueTimer{item.m_fire_time , item.m_generation , std::move (state)});
363386 }
364387 }
365388
366389 inline void TimerScheduler::execute_due_timers (std::vector<detail::DueTimer>& due) {
367390 for (auto & timer : due) {
368- if (timer.m_state && timer.m_state ->m_callback ) {
369- timer.m_state ->m_callback ();
391+ detail::TimerCallback callback;
392+ if (timer.m_state ) {
393+ std::lock_guard<std::mutex> callback_lock (timer.m_state ->m_callback_mutex );
394+ callback = timer.m_state ->m_callback ;
395+ }
396+ if (callback) {
397+ callback ();
370398 }
371399 finalize_timer (timer);
372400 }
@@ -379,7 +407,7 @@ namespace time_shield {
379407 }
380408
381409 std::unique_lock<std::mutex> lock (m_mutex);
382- state->m_is_running .store (false , std::memory_order_relaxed );
410+ state->m_is_running .store (false , std::memory_order_release );
383411 if (!state->m_is_active .load (std::memory_order_relaxed)) {
384412 return ;
385413 }
@@ -394,7 +422,8 @@ namespace time_shield {
394422 return ;
395423 }
396424
397- const auto next_fire_time = due_timer.m_fire_time + state->m_interval ;
425+ const auto interval_ms = state->m_interval_ms .load (std::memory_order_relaxed);
426+ const auto next_fire_time = due_timer.m_fire_time + std::chrono::milliseconds (interval_ms);
398427 const auto next_generation = state->m_generation .fetch_add (1 , std::memory_order_relaxed) + 1 ;
399428 m_queue.push (detail::ScheduledTimer{next_fire_time, state->m_id , next_generation});
400429 m_cv.notify_all ();
@@ -413,20 +442,21 @@ namespace time_shield {
413442
414443 template <class Rep , class Period >
415444 void Timer::set_interval (std::chrono::duration<Rep, Period> interval) noexcept {
416- const auto converted = std::chrono::duration_cast<std::chrono::milliseconds>(interval);
417- if (converted.count () < 0 ) {
418- m_state->m_interval = std::chrono::milliseconds (0 );
419- } else {
420- m_state->m_interval = converted;
445+ auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(interval).count ();
446+ if (milliseconds < 0 ) {
447+ milliseconds = 0 ;
421448 }
449+ m_state->m_interval_ms .store (milliseconds, std::memory_order_relaxed);
422450 }
423451
424452 inline std::chrono::milliseconds Timer::interval () const noexcept {
425- return m_state->m_interval ;
453+ const auto milliseconds = m_state->m_interval_ms .load (std::memory_order_relaxed);
454+ return std::chrono::milliseconds (milliseconds);
426455 }
427456
428457 inline void Timer::start () {
429- const auto delay = TimerScheduler::clock::now () + m_state->m_interval ;
458+ const auto milliseconds = m_state->m_interval_ms .load (std::memory_order_relaxed);
459+ const auto delay = TimerScheduler::clock::now () + std::chrono::milliseconds (milliseconds);
430460 m_scheduler.start_timer (m_state, delay);
431461 }
432462
@@ -440,6 +470,13 @@ namespace time_shield {
440470 m_scheduler.stop_timer (m_state);
441471 }
442472
473+ inline void Timer::stop_and_wait () {
474+ m_scheduler.stop_timer (m_state);
475+ while (m_state->m_is_running .load (std::memory_order_acquire)) {
476+ std::this_thread::yield ();
477+ }
478+ }
479+
443480 inline void Timer::set_single_shot (bool is_single_shot) noexcept {
444481 m_state->m_is_single_shot .store (is_single_shot, std::memory_order_relaxed);
445482 }
@@ -457,6 +494,7 @@ namespace time_shield {
457494 }
458495
459496 inline void Timer::set_callback (Callback callback) {
497+ std::lock_guard<std::mutex> lock (m_state->m_callback_mutex );
460498 m_state->m_callback = std::move (callback);
461499 }
462500
@@ -466,13 +504,11 @@ namespace time_shield {
466504 Callback callback) {
467505 auto timer = std::shared_ptr<Timer>(new Timer (scheduler));
468506 timer->set_single_shot (true );
469- std::shared_ptr <Callback> callback_holder ( new Callback ( std::move (callback) ));
507+ auto callback_holder = std::make_shared <Callback>( std::move (callback));
470508 timer->set_callback ([timer, callback_holder]() mutable {
471509 if (*callback_holder) {
472510 (*callback_holder)();
473511 }
474- timer->stop ();
475- timer.reset ();
476512 });
477513 timer->start (interval);
478514 }
0 commit comments