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 -
    /** Schedule a callback to fire after a duration.
 
51 -

 
52 -
        The callback is invoked on the timer service's background
 
53 -
        thread. It must not block for extended periods.
 
54 -

 
55 -
        @return An id that can be passed to cancel().
 
56 -
    */
 
57 -
    template<typename Rep, typename Period>
 
58 -
    timer_id schedule_after(
 
59 -
        std::chrono::duration<Rep, Period> dur,
 
60 -
        std::function<void()> cb)
 
61 -
    {
 
62 -
        auto deadline = std::chrono::steady_clock::now() + dur;
 
63 -
        return schedule_at(deadline, std::move(cb));
 
64 -
    }
 
65 -

 
66 -
    /** Cancel a pending timer.
 
67 -

 
68 -
        After this function returns, the callback is guaranteed
 
69 -
        not to be running and will never be invoked. If the
 
70 -
        callback is currently executing on the timer thread,
 
71 -
        this call blocks until it completes.
 
72 -

 
73 -
        Safe to call with any id, including ids that have
 
74 -
        already fired, been cancelled, or were never issued.
 
75 -
    */
 
76 -
    void cancel(timer_id id);
 
77 -

 
78 -
protected:
 
79 -
    void shutdown() override;
 
80 -

 
81 -
private:
 
82 -
    struct entry
 
83 -
    {
 
84 -
        std::chrono::steady_clock::time_point deadline;
 
85 -
        timer_id id;
 
86 -
        std::function<void()> callback;
 
87 -

 
88 -
        bool operator>(entry const& o) const noexcept
 
89 -
        {
 
90 -
            return deadline > o.deadline;
 
91 -
        }
 
92 -
    };
 
93 -

 
94 -
    timer_id schedule_at(
 
95 -
        std::chrono::steady_clock::time_point deadline,
 
96 -
        std::function<void()> cb);
 
97 -

 
98 -
    void run();
 
99 -

 
100 -
// warning C4251: std types need to have dll-interface
 
101 -
#ifdef _MSC_VER
 
102 -
# pragma warning(push)
 
103 -
# pragma warning(disable: 4251)
 
104 -
#endif
 
105 -
    std::mutex mutex_;
 
106 -
    std::condition_variable cv_;
 
107 -
    std::condition_variable cancel_cv_;
 
108 -
    std::priority_queue<
 
109 -
        entry,
 
110 -
        std::vector<entry>,
 
111 -
        std::greater<>> queue_;
 
112 -
    std::unordered_set<timer_id> active_ids_;
 
113 -
    timer_id next_id_ = 0;
 
114 -
    timer_id executing_id_ = 0;
 
115 -
    bool stopped_ = false;
 
116 -
    std::thread thread_;
 
117 -
#ifdef _MSC_VER
 
118 -
# pragma warning(pop)
 
119 -
#endif
 
120 -
};
 
121 -

 
122 -
} // detail
 
123 -
} // capy
 
124 -
} // boost
 
125 -

 
126 -
#endif