2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
19 #include <folly/io/async/HHWheelTimer.h>
20 #include <folly/io/async/EventBase.h>
21 #include <folly/io/async/test/UndelayedDestruction.h>
22 #include <folly/io/async/test/Util.h>
23 #include <folly/portability/GTest.h>
27 using namespace folly;
28 using std::chrono::milliseconds;
30 typedef UndelayedDestruction<HHWheelTimer> StackWheelTimer;
32 class TestTimeout : public HHWheelTimer::Callback {
35 TestTimeout(HHWheelTimer* t, milliseconds timeout) {
36 t->scheduleTimeout(this, timeout);
39 void timeoutExpired() noexcept override {
40 timestamps.emplace_back();
46 void callbackCanceled() noexcept override {
47 canceledTimestamps.emplace_back();
53 std::deque<TimePoint> timestamps;
54 std::deque<TimePoint> canceledTimestamps;
55 std::function<void()> fn;
59 class TestTimeoutDelayed : public TestTimeout {
61 std::chrono::milliseconds getCurTime() override {
62 return std::chrono::duration_cast<std::chrono::milliseconds>(
63 std::chrono::steady_clock::now().time_since_epoch()) -
68 struct HHWheelTimerTest : public ::testing::Test {
73 * Test firing some simple timeouts that are fired once and never rescheduled
75 TEST_F(HHWheelTimerTest, FireOnce) {
76 StackWheelTimer t(&eventBase, milliseconds(1));
82 ASSERT_EQ(t.count(), 0);
84 t.scheduleTimeout(&t1, milliseconds(5));
85 t.scheduleTimeout(&t2, milliseconds(5));
86 // Verify scheduling it twice cancels, then schedules.
87 // Should only get one callback.
88 t.scheduleTimeout(&t2, milliseconds(5));
89 t.scheduleTimeout(&t3, milliseconds(10));
91 ASSERT_EQ(t.count(), 3);
97 ASSERT_EQ(t1.timestamps.size(), 1);
98 ASSERT_EQ(t2.timestamps.size(), 1);
99 ASSERT_EQ(t3.timestamps.size(), 1);
101 ASSERT_EQ(t.count(), 0);
103 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
104 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
105 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
106 T_CHECK_TIMEOUT(start, end, milliseconds(10));
110 * Test scheduling a timeout from another timeout callback.
112 TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
113 HHWheelTimer& t = eventBase.timer();
116 // Delayed to simulate the steady_clock counter lagging
117 TestTimeoutDelayed t2;
119 t.scheduleTimeout(&t1, milliseconds(500));
120 t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
121 // If t is in an inconsistent state, detachEventBase should fail.
122 t2.fn = [&] { t.detachEventBase(); };
124 ASSERT_EQ(t.count(), 1);
128 ASSERT_EQ(t.count(), 0);
129 ASSERT_EQ(t1.timestamps.size(), 1);
130 ASSERT_EQ(t2.timestamps.size(), 1);
134 * Test cancelling a timeout when it is scheduled to be fired right away.
137 TEST_F(HHWheelTimerTest, CancelTimeout) {
138 StackWheelTimer t(&eventBase, milliseconds(1));
140 // Create several timeouts that will all fire in 5ms.
141 TestTimeout t5_1(&t, milliseconds(5));
142 TestTimeout t5_2(&t, milliseconds(5));
143 TestTimeout t5_3(&t, milliseconds(5));
144 TestTimeout t5_4(&t, milliseconds(5));
145 TestTimeout t5_5(&t, milliseconds(5));
147 // Also create a few timeouts to fire in 10ms
148 TestTimeout t10_1(&t, milliseconds(10));
149 TestTimeout t10_2(&t, milliseconds(10));
150 TestTimeout t10_3(&t, milliseconds(10));
152 TestTimeout t20_1(&t, milliseconds(20));
153 TestTimeout t20_2(&t, milliseconds(20));
155 // Have t5_1 cancel t5_2 and t5_4.
157 // Cancelling t5_2 will test cancelling a timeout that is at the head of the
158 // list and ready to be fired.
160 // Cancelling t5_4 will test cancelling a timeout in the middle of the list
162 t5_2.cancelTimeout();
163 t5_4.cancelTimeout();
166 // Have t5_3 cancel t5_5.
167 // This will test cancelling the last remaining timeout.
169 // Then have t5_3 reschedule itself.
171 t5_5.cancelTimeout();
172 // Reset our function so we won't continually reschedule ourself
173 std::function<void()> fnDtorGuard;
174 t5_3.fn.swap(fnDtorGuard);
175 t.scheduleTimeout(&t5_3, milliseconds(5));
177 // Also test cancelling timeouts in another timeset that isn't ready to
180 // Cancel the middle timeout in ts10.
181 t10_2.cancelTimeout();
182 // Cancel both the timeouts in ts20.
183 t20_1.cancelTimeout();
184 t20_2.cancelTimeout();
191 ASSERT_EQ(t5_1.timestamps.size(), 1);
192 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
194 ASSERT_EQ(t5_3.timestamps.size(), 2);
195 T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
196 T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
198 ASSERT_EQ(t10_1.timestamps.size(), 1);
199 T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
200 ASSERT_EQ(t10_3.timestamps.size(), 1);
201 T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
203 // Cancelled timeouts
204 ASSERT_EQ(t5_2.timestamps.size(), 0);
205 ASSERT_EQ(t5_4.timestamps.size(), 0);
206 ASSERT_EQ(t5_5.timestamps.size(), 0);
207 ASSERT_EQ(t10_2.timestamps.size(), 0);
208 ASSERT_EQ(t20_1.timestamps.size(), 0);
209 ASSERT_EQ(t20_2.timestamps.size(), 0);
211 T_CHECK_TIMEOUT(start, end, milliseconds(10));
215 * Test destroying a HHWheelTimer with timeouts outstanding
218 TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
219 HHWheelTimer::UniquePtr t(
220 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
222 TestTimeout t5_1(t.get(), milliseconds(5));
223 TestTimeout t5_2(t.get(), milliseconds(5));
224 TestTimeout t5_3(t.get(), milliseconds(5));
226 TestTimeout t10_1(t.get(), milliseconds(10));
227 TestTimeout t10_2(t.get(), milliseconds(10));
229 // Have t5_2 destroy t
230 // Note that this will call destroy() inside t's timeoutExpired()
233 t5_3.cancelTimeout();
234 t5_1.cancelTimeout();
235 t10_1.cancelTimeout();
236 t10_2.cancelTimeout();
243 ASSERT_EQ(t5_1.timestamps.size(), 1);
244 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
245 ASSERT_EQ(t5_2.timestamps.size(), 1);
246 T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
248 ASSERT_EQ(t5_3.timestamps.size(), 0);
249 ASSERT_EQ(t10_1.timestamps.size(), 0);
250 ASSERT_EQ(t10_2.timestamps.size(), 0);
252 T_CHECK_TIMEOUT(start, end, milliseconds(5));
256 * Test an event scheduled before the last event fires on time
259 TEST_F(HHWheelTimerTest, SlowFast) {
260 StackWheelTimer t(&eventBase, milliseconds(1));
265 ASSERT_EQ(t.count(), 0);
267 t.scheduleTimeout(&t1, milliseconds(10));
268 t.scheduleTimeout(&t2, milliseconds(5));
270 ASSERT_EQ(t.count(), 2);
276 ASSERT_EQ(t1.timestamps.size(), 1);
277 ASSERT_EQ(t2.timestamps.size(), 1);
278 ASSERT_EQ(t.count(), 0);
280 // Check that the timeout was delayed by sleep
281 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(10), milliseconds(1));
282 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5), milliseconds(1));
285 TEST_F(HHWheelTimerTest, ReschedTest) {
286 StackWheelTimer t(&eventBase, milliseconds(1));
291 ASSERT_EQ(t.count(), 0);
293 t.scheduleTimeout(&t1, milliseconds(128));
296 t.scheduleTimeout(&t2, milliseconds(255)); // WHEEL_SIZE - 1
298 ASSERT_EQ(t.count(), 1);
301 ASSERT_EQ(t.count(), 1);
307 ASSERT_EQ(t1.timestamps.size(), 1);
308 ASSERT_EQ(t2.timestamps.size(), 1);
309 ASSERT_EQ(t.count(), 0);
311 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
312 T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(255), milliseconds(1));
315 TEST_F(HHWheelTimerTest, DeleteWheelInTimeout) {
316 auto t = HHWheelTimer::newTimer(&eventBase, milliseconds(1));
322 ASSERT_EQ(t->count(), 0);
324 t->scheduleTimeout(&t1, milliseconds(128));
325 t->scheduleTimeout(&t2, milliseconds(128));
326 t->scheduleTimeout(&t3, milliseconds(128));
327 t1.fn = [&]() { t2.cancelTimeout(); };
328 t3.fn = [&]() { t.reset(); };
330 ASSERT_EQ(t->count(), 3);
336 ASSERT_EQ(t1.timestamps.size(), 1);
337 ASSERT_EQ(t2.timestamps.size(), 0);
339 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
343 * Test scheduling a mix of timers with default timeout and variable timeout.
345 TEST_F(HHWheelTimerTest, DefaultTimeout) {
346 milliseconds defaultTimeout(milliseconds(5));
347 StackWheelTimer t(&eventBase,
349 AsyncTimeout::InternalEnum::NORMAL,
355 ASSERT_EQ(t.count(), 0);
356 ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
358 t.scheduleTimeout(&t1);
359 t.scheduleTimeout(&t2, milliseconds(10));
361 ASSERT_EQ(t.count(), 2);
367 ASSERT_EQ(t1.timestamps.size(), 1);
368 ASSERT_EQ(t2.timestamps.size(), 1);
370 ASSERT_EQ(t.count(), 0);
372 T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
373 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
374 T_CHECK_TIMEOUT(start, end, milliseconds(10));
377 TEST_F(HHWheelTimerTest, lambda) {
378 StackWheelTimer t(&eventBase, milliseconds(1));
380 t.scheduleTimeoutFn([&]{ count++; }, milliseconds(1));
385 // shouldn't crash because we swallow and log the error (you'll have to look
386 // at the console to confirm logging)
387 TEST_F(HHWheelTimerTest, lambdaThrows) {
388 StackWheelTimer t(&eventBase, milliseconds(1));
389 t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
394 TEST_F(HHWheelTimerTest, cancelAll) {
395 StackWheelTimer t(&eventBase, milliseconds(1));
397 t.scheduleTimeout(&tt, std::chrono::minutes(1));
398 EXPECT_EQ(1, t.cancelAll());
399 EXPECT_EQ(1, tt.canceledTimestamps.size());
402 TEST_F(HHWheelTimerTest, IntrusivePtr) {
403 HHWheelTimer::UniquePtr t(
404 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
410 ASSERT_EQ(t->count(), 0);
412 t->scheduleTimeout(&t1, milliseconds(5));
413 t->scheduleTimeout(&t2, milliseconds(5));
415 DelayedDestruction::IntrusivePtr<HHWheelTimer> s(t);
417 s->scheduleTimeout(&t3, milliseconds(10));
419 ASSERT_EQ(t->count(), 3);
421 // Kill the UniquePtr, but the SharedPtr keeps it alive
428 ASSERT_EQ(t1.timestamps.size(), 1);
429 ASSERT_EQ(t2.timestamps.size(), 1);
430 ASSERT_EQ(t3.timestamps.size(), 1);
432 ASSERT_EQ(s->count(), 0);
434 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
435 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
436 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
437 T_CHECK_TIMEOUT(start, end, milliseconds(10));