100.00% Lines (5/5) 100.00% Functions (2/2)
TLA Baseline Branch
Line Hits Code Line Hits Code
  1 + //
  2 + // Copyright (c) 2026 Michael Vandeberg
  3 + //
  4 + // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5 + // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6 + //
  7 + // Official repository: https://github.com/cppalliance/capy
  8 + //
  9 +
  10 + #ifndef BOOST_CAPY_EX_TIMER_SERVICE_HPP
  11 + #define BOOST_CAPY_EX_TIMER_SERVICE_HPP
  12 +
  13 + #include <boost/capy/detail/config.hpp>
  14 + #include <boost/capy/ex/execution_context.hpp>
  15 +
  16 + #include <chrono>
  17 + #include <cstdint>
  18 + #include <functional>
  19 + #include <mutex>
  20 + #include <condition_variable>
  21 + #include <queue>
  22 + #include <thread>
  23 + #include <unordered_set>
  24 + #include <vector>
  25 +
  26 + namespace boost {
  27 + namespace capy {
  28 + namespace detail {
  29 +
  30 + /* Shared timer thread for an execution_context.
  31 +
  32 + One background std::thread per execution_context. All timeouts
  33 + scheduled through this context share the same thread, which sleeps
  34 + on a condition variable until the next deadline.
  35 +
  36 + The timer thread never touches coroutine frames or executors
  37 + directly — callbacks are responsible for posting work through
  38 + the appropriate executor.
  39 + */
  40 +
  41 + class BOOST_CAPY_DECL
  42 + timer_service
  43 + : public execution_context::service
  44 + {
  45 + public:
  46 + using timer_id = std::uint64_t;
  47 +
  48 + explicit timer_service(execution_context& ctx);
  49 +
  50 + // Calls shutdown() to join the background thread.
  51 + // Handles the discard path in use_service_impl where
  52 + // a duplicate service is deleted without shutdown().
  53 + ~timer_service();
  54 +
  55 + /** Schedule a callback to fire after a duration.
  56 +
  57 + The callback is invoked on the timer service's background
  58 + thread. It must not block for extended periods.
  59 +
  60 + @return An id that can be passed to cancel().
  61 + */
  62 + template<typename Rep, typename Period>
HITGNC   63 + 135 timer_id schedule_after(
  64 + std::chrono::duration<Rep, Period> dur,
  65 + std::function<void()> cb)
  66 + {
HITGNC   67 + 135 auto deadline = std::chrono::steady_clock::now() + dur;
HITGNC   68 + 135 return schedule_at(deadline, std::move(cb));
  69 + }
  70 +
  71 + /** Cancel a pending timer.
  72 +
  73 + After this function returns, the callback is guaranteed
  74 + not to be running and will never be invoked. If the
  75 + callback is currently executing on the timer thread,
  76 + this call blocks until it completes.
  77 +
  78 + Safe to call with any id, including ids that have
  79 + already fired, been cancelled, or were never issued.
  80 + */
  81 + void cancel(timer_id id);
  82 +
  83 + protected:
  84 + void shutdown() override;
  85 +
  86 + private:
  87 + void stop_and_join();
  88 + struct entry
  89 + {
  90 + std::chrono::steady_clock::time_point deadline;
  91 + timer_id id;
  92 + std::function<void()> callback;
  93 +
HITGNC   94 + 664 bool operator>(entry const& o) const noexcept
  95 + {
HITGNC   96 + 664 return deadline > o.deadline;
  97 + }
  98 + };
  99 +
  100 + timer_id schedule_at(
  101 + std::chrono::steady_clock::time_point deadline,
  102 + std::function<void()> cb);
  103 +
  104 + void run();
  105 +
  106 + // warning C4251: std types need to have dll-interface
  107 + BOOST_CAPY_MSVC_WARNING_PUSH
  108 + BOOST_CAPY_MSVC_WARNING_DISABLE(4251)
  109 + std::mutex mutex_;
  110 + std::condition_variable cv_;
  111 + std::condition_variable cancel_cv_;
  112 + std::priority_queue<
  113 + entry,
  114 + std::vector<entry>,
  115 + std::greater<>> queue_;
  116 + std::unordered_set<timer_id> active_ids_;
  117 + timer_id next_id_ = 0;
  118 + timer_id executing_id_ = 0;
  119 + bool stopped_ = false;
  120 + std::thread thread_;
  121 + BOOST_CAPY_MSVC_WARNING_POP
  122 + };
  123 +
  124 + } // detail
  125 + } // capy
  126 + } // boost
  127 +
  128 + #endif