File timer_service.hpp

File List > astutedds > dcps > timer_service.hpp

Go to the documentation of this file

//
// Copyright (c) 2026, Astute Systems PTY LTD
//
// This file is part of the Astute DDS developed by Astute Systems.
//
// See the commercial LICENSE file in the project root for full license details.
//
// @file timer_service.hpp
// @brief Min-heap based timer scheduler for DCPS-level watchdogs.
//
// The TimerService owns one background thread that waits on a condition
// variable until the next scheduled expiry, invokes the timer's callback,
// and (for periodic timers) rearms it.  The heap gives O(log N) insert
// and O(1) peek; cancellation is O(1) via a per-timer flag and lazy
// popping of stale heap entries.  This is the substrate for the §1.1b-B
// liveliness, deadline, and (future) sample-lost watchdogs.
//
// Threading contract:
//   * All public methods except stop() and the destructor are safe to
//     call from timer callbacks.  Callbacks run on the timer thread with
//     the internal mutex NOT held, so callbacks may re-enter schedule/
//     cancel/reset without deadlock.
//   * stop() and ~TimerService() must NOT be called from within a
//     callback — they wait for the timer thread to join.
//   * Callbacks that throw are silently caught so a bad user callback
//     cannot kill the timer thread.
//

#ifndef ASTUTEDDS_DCPS_TIMER_SERVICE_HPP
#define ASTUTEDDS_DCPS_TIMER_SERVICE_HPP

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <mutex>
#include <queue>
#include <thread>
#include <unordered_map>
#include <vector>

namespace astutedds::dcps
{

using TimerId = std::uint64_t;

constexpr TimerId INVALID_TIMER_ID = 0;

class TimerService
{
public:
    TimerService();
    ~TimerService();

    TimerService(const TimerService&)            = delete;
    TimerService& operator=(const TimerService&) = delete;

    void start();

    void stop();

    TimerId schedule(std::chrono::nanoseconds delay,
                     std::function<void()>    callback);

    TimerId schedule_periodic(std::chrono::nanoseconds period,
                              std::function<void()>    callback);

    bool cancel(TimerId id);

    bool reset(TimerId id, std::chrono::nanoseconds new_delay);

    std::size_t timer_count() const;

    bool is_running() const;

private:
    struct Timer
    {
        TimerId                                    id{0};
        std::chrono::steady_clock::time_point      next_fire;
        std::chrono::nanoseconds                   period{0};  
        std::function<void()>                      callback;
        bool                                       cancelled{false};
    };

    struct HeapNode
    {
        std::chrono::steady_clock::time_point when;
        TimerId                               id;
        bool operator>(const HeapNode& rhs) const noexcept { return when > rhs.when; }
    };

    void run_();
    TimerId schedule_locked_(std::chrono::nanoseconds first_delay,
                             std::chrono::nanoseconds period,
                             std::function<void()>    callback);

    mutable std::mutex                        mtx_;
    std::condition_variable                   cv_;
    std::thread                               thread_;
    std::atomic<bool>                         running_{false};
    std::atomic<bool>                         started_{false};
    std::atomic<std::uint64_t>                next_id_{1};
    std::unordered_map<TimerId, Timer>        timers_;
    std::priority_queue<HeapNode,
                        std::vector<HeapNode>,
                        std::greater<HeapNode>> heap_;
};

}  // namespace astutedds::dcps

#endif  // ASTUTEDDS_DCPS_TIMER_SERVICE_HPP