VLink 2.0.0
A high-performance communication middleware
Loading...
Searching...
No Matches
wheel_timer.h
Go to the documentation of this file.
1/*
2 * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 * Author: Thun Lu <thun.lu@zohomail.cn>
4 * Repo: https://github.com/thun-res/vlink
5 * _ __ __ _ __
6 * | | / / / / (_) ____ / /__
7 * | | / / / / / / / __ \ / //_/
8 * | |/ / / /___ / / / / / / / ,<
9 * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 */
23
24/**
25 * @file wheel_timer.h
26 * @brief Hash-wheel timer for managing large numbers of concurrent timeouts efficiently.
27 *
28 * @details
29 * @c WheelTimer implements a hashed timing wheel -- a classic data structure for O(1) timer
30 * insertion, removal and expiry checking. It is appropriate when hundreds to hundreds of
31 * thousands of independent timeouts must be tracked simultaneously, such as in a session
32 * manager or a connection-pool keep-alive system.
33 *
34 * Algorithm:
35 * - The wheel contains @p slots evenly-spaced time buckets.
36 * - Each tick advances the wheel by one slot; the advance period is @p interval_ms.
37 * - Timeouts longer than @c slots * @p interval_ms are stored with a round counter and
38 * skipped until the counter reaches zero.
39 * - Adding a timer is O(1). Removal is O(1) via the internal key-to-slot index.
40 * - Expiry detection per tick is O(k) where k is the number of handlers in the current slot.
41 *
42 * Lifecycle:
43 * -# Construct with @c WheelTimer(slots, interval_ms).
44 * -# Call @c start() to launch the background worker thread.
45 * -# Add timers with @c add(); store the returned @c Key for later removal.
46 * -# Call @c stop() to terminate the background thread.
47 *
48 * @note
49 * - Expired callbacks are invoked from the internal worker thread. Use thread-safe
50 * structures or post results to a @c MessageLoop inside the callback.
51 * - @c set_catchup_limit() controls how many missed slots are processed in one tick
52 * to prevent a long stall from causing a burst of expired callbacks.
53 *
54 * @par Example
55 * @code
56 * // 256 slots, 10 ms per slot -> max 2.56 s without wrapping; higher durations use rounds.
57 * vlink::WheelTimer wheel(256, 10);
58 * wheel.start();
59 *
60 * auto key = wheel.add(1000, [](vlink::WheelTimer::Key k) {
61 * // fired after ~1000 ms
62 * });
63 *
64 * // Repeating every 500 ms:
65 * auto repeat_key = wheel.add(500, [](vlink::WheelTimer::Key k) {}, 500);
66 *
67 * wheel.remove(key); // cancel before expiry
68 * wheel.stop();
69 * @endcode
70 */
71
72#pragma once
73
74#include <cstdint>
75#include <functional>
76#include <memory>
77
78#include "./macros.h"
79
80namespace vlink {
81
82/**
83 * @class WheelTimer
84 * @brief O(1) hash-wheel timer backed by a fixed-size circular slot array.
85 *
86 * @details
87 * Runs its own internal background thread to advance the wheel on each @p interval_ms tick.
88 */
90 public:
91 /**
92 * @brief Opaque handle returned by @c add() and used to @c remove() a timer.
93 */
94 using Key = int64_t;
95
96 /**
97 * @brief Callback invoked when a timer expires.
98 *
99 * @details
100 * The @c Key parameter allows a single lambda to manage multiple timers.
101 */
102 using Callback = std::function<void(Key)>;
103
104 /**
105 * @brief Constructs the wheel timer.
106 *
107 * @details
108 * Creates the internal slot array. Call @c start() to begin advancing the wheel.
109 *
110 * @param slots Number of time buckets in the wheel. Higher values reduce the
111 * number of round counters needed for long timeouts.
112 * @param interval_ms Duration of one slot in milliseconds. Resolution of all timers.
113 */
114 explicit WheelTimer(uint32_t slots, uint32_t interval_ms);
115
116 /**
117 * @brief Destructor. Calls @c stop() if the wheel is still running.
118 */
120
121 /**
122 * @brief Starts the internal background thread and begins advancing the wheel.
123 */
124 void start();
125
126 /**
127 * @brief Stops the wheel and joins the background thread.
128 *
129 * @details
130 * Pending timers are not fired after @c stop() returns.
131 */
132 void stop();
133
134 /**
135 * @brief Temporarily suspends the wheel without terminating the background thread.
136 *
137 * @details
138 * Timers will not fire while paused. The background thread remains alive.
139 * Call @c resume() to continue normal operation.
140 */
141 void pause();
142
143 /**
144 * @brief Resumes a paused wheel.
145 *
146 * @details
147 * If the wheel is not paused, this is a no-op.
148 */
149 void resume();
150
151 /**
152 * @brief Wakes the internal worker thread if it is sleeping between ticks.
153 *
154 * @details
155 * Useful for triggering an immediate tick after adding a very short timeout.
156 */
157 void wakeup();
158
159 /**
160 * @brief Returns @c true if the wheel is currently running (started and not stopped).
161 *
162 * @return @c true if running.
163 */
164 [[nodiscard]] bool is_running() const;
165
166 /**
167 * @brief Adds a new timer to the wheel.
168 *
169 * @details
170 * The callback will be invoked from the wheel's internal thread after @p timeout_ms
171 * milliseconds. If @p repeat_ms > 0, the timer is automatically re-added with the
172 * @p repeat_ms interval each time it fires.
173 *
174 * @param timeout_ms Initial delay in milliseconds (rounded up to the nearest slot).
175 * @param callback Function to call on expiry. Receives the timer's @c Key as argument.
176 * @param repeat_ms Re-fire interval in milliseconds. 0 = one-shot. Default: 0.
177 * @return A unique @c Key identifying this timer entry.
178 */
179 Key add(uint32_t timeout_ms, Callback&& callback, uint32_t repeat_ms = 0);
180
181 /**
182 * @brief Removes a timer before it fires.
183 *
184 * @param key Key returned by @c add().
185 * @return @c true if the timer was found and removed; @c false if already expired or invalid.
186 */
187 bool remove(Key key);
188
189 /**
190 * @brief Returns the estimated remaining time for a timer.
191 *
192 * @details
193 * The returned value is approximate due to discretisation to slot boundaries.
194 *
195 * @param key Key returned by @c add().
196 * @return Estimated remaining time in milliseconds, or 0 if the key is not found.
197 */
198 [[nodiscard]] uint32_t get_remaining_time(Key key) const;
199
200 /**
201 * @brief Sets the maximum number of missed slots processed in a single tick.
202 *
203 * @details
204 * If the wheel falls behind (e.g., due to system sleep), it may have many slots to
205 * catch up. This limit prevents a single tick from blocking for too long by capping
206 * the number of catch-up slots processed per iteration. Default is unlimited.
207 *
208 * @param max_slots_to_catch_up Maximum slots to process per tick cycle.
209 */
210 void set_catchup_limit(uint32_t max_slots_to_catch_up);
211
212 private:
213 void run();
214
215 std::unique_ptr<struct WheelTimerImpl> impl_;
216
218};
219
220} // namespace vlink
Platform-independent macro definitions for the VLink library.
#define VLINK_EXPORT
Definition macros.h:85
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition macros.h:184