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>
25 using namespace folly;
26 using std::chrono::milliseconds;
28 typedef UndelayedDestruction<HHWheelTimer> StackWheelTimer;
30 class TestTimeout : public HHWheelTimer::Callback {
33 TestTimeout(HHWheelTimer* t, milliseconds timeout) {
34 t->scheduleTimeout(this, timeout);
37 void timeoutExpired() noexcept override {
38 timestamps.emplace_back();
44 void callbackCanceled() noexcept override {
45 canceledTimestamps.emplace_back();
51 std::deque<TimePoint> timestamps;
52 std::deque<TimePoint> canceledTimestamps;
53 std::function<void()> fn;
57 class TestTimeoutDelayed : public TestTimeout {
59 std::chrono::milliseconds getCurTime() override {
60 return std::chrono::duration_cast<std::chrono::milliseconds>(
61 std::chrono::steady_clock::now().time_since_epoch()) -
66 struct HHWheelTimerTest : public ::testing::Test {
71 * Test firing some simple timeouts that are fired once and never rescheduled
73 TEST_F(HHWheelTimerTest, FireOnce) {
74 StackWheelTimer t(&eventBase, milliseconds(1));
80 ASSERT_EQ(t.count(), 0);
82 t.scheduleTimeout(&t1, milliseconds(5));
83 t.scheduleTimeout(&t2, milliseconds(5));
84 // Verify scheduling it twice cancels, then schedules.
85 // Should only get one callback.
86 t.scheduleTimeout(&t2, milliseconds(5));
87 t.scheduleTimeout(&t3, milliseconds(10));
89 ASSERT_EQ(t.count(), 3);
95 ASSERT_EQ(t1.timestamps.size(), 1);
96 ASSERT_EQ(t2.timestamps.size(), 1);
97 ASSERT_EQ(t3.timestamps.size(), 1);
99 ASSERT_EQ(t.count(), 0);
101 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
102 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
103 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
104 T_CHECK_TIMEOUT(start, end, milliseconds(10));
108 * Test scheduling a timeout from another timeout callback.
110 TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
111 HHWheelTimer& t = eventBase.timer();
114 // Delayed to simulate the steady_clock counter lagging
115 TestTimeoutDelayed t2;
117 t.scheduleTimeout(&t1, milliseconds(500));
118 t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
119 // If t is in an inconsistent state, detachEventBase should fail.
120 t2.fn = [&] { t.detachEventBase(); };
122 ASSERT_EQ(t.count(), 1);
126 ASSERT_EQ(t.count(), 0);
127 ASSERT_EQ(t1.timestamps.size(), 1);
128 ASSERT_EQ(t2.timestamps.size(), 1);
132 * Test cancelling a timeout when it is scheduled to be fired right away.
135 TEST_F(HHWheelTimerTest, CancelTimeout) {
136 StackWheelTimer t(&eventBase, milliseconds(1));
138 // Create several timeouts that will all fire in 5ms.
139 TestTimeout t5_1(&t, milliseconds(5));
140 TestTimeout t5_2(&t, milliseconds(5));
141 TestTimeout t5_3(&t, milliseconds(5));
142 TestTimeout t5_4(&t, milliseconds(5));
143 TestTimeout t5_5(&t, milliseconds(5));
145 // Also create a few timeouts to fire in 10ms
146 TestTimeout t10_1(&t, milliseconds(10));
147 TestTimeout t10_2(&t, milliseconds(10));
148 TestTimeout t10_3(&t, milliseconds(10));
150 TestTimeout t20_1(&t, milliseconds(20));
151 TestTimeout t20_2(&t, milliseconds(20));
153 // Have t5_1 cancel t5_2 and t5_4.
155 // Cancelling t5_2 will test cancelling a timeout that is at the head of the
156 // list and ready to be fired.
158 // Cancelling t5_4 will test cancelling a timeout in the middle of the list
160 t5_2.cancelTimeout();
161 t5_4.cancelTimeout();
164 // Have t5_3 cancel t5_5.
165 // This will test cancelling the last remaining timeout.
167 // Then have t5_3 reschedule itself.
169 t5_5.cancelTimeout();
170 // Reset our function so we won't continually reschedule ourself
171 std::function<void()> fnDtorGuard;
172 t5_3.fn.swap(fnDtorGuard);
173 t.scheduleTimeout(&t5_3, milliseconds(5));
175 // Also test cancelling timeouts in another timeset that isn't ready to
178 // Cancel the middle timeout in ts10.
179 t10_2.cancelTimeout();
180 // Cancel both the timeouts in ts20.
181 t20_1.cancelTimeout();
182 t20_2.cancelTimeout();
189 ASSERT_EQ(t5_1.timestamps.size(), 1);
190 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
192 ASSERT_EQ(t5_3.timestamps.size(), 2);
193 T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
194 T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
196 ASSERT_EQ(t10_1.timestamps.size(), 1);
197 T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
198 ASSERT_EQ(t10_3.timestamps.size(), 1);
199 T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
201 // Cancelled timeouts
202 ASSERT_EQ(t5_2.timestamps.size(), 0);
203 ASSERT_EQ(t5_4.timestamps.size(), 0);
204 ASSERT_EQ(t5_5.timestamps.size(), 0);
205 ASSERT_EQ(t10_2.timestamps.size(), 0);
206 ASSERT_EQ(t20_1.timestamps.size(), 0);
207 ASSERT_EQ(t20_2.timestamps.size(), 0);
209 T_CHECK_TIMEOUT(start, end, milliseconds(10));
213 * Test destroying a HHWheelTimer with timeouts outstanding
216 TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
217 HHWheelTimer::UniquePtr t(
218 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
220 TestTimeout t5_1(t.get(), milliseconds(5));
221 TestTimeout t5_2(t.get(), milliseconds(5));
222 TestTimeout t5_3(t.get(), milliseconds(5));
224 TestTimeout t10_1(t.get(), milliseconds(10));
225 TestTimeout t10_2(t.get(), milliseconds(10));
227 // Have t5_2 destroy t
228 // Note that this will call destroy() inside t's timeoutExpired()
231 t5_3.cancelTimeout();
232 t5_1.cancelTimeout();
233 t10_1.cancelTimeout();
234 t10_2.cancelTimeout();
241 ASSERT_EQ(t5_1.timestamps.size(), 1);
242 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
243 ASSERT_EQ(t5_2.timestamps.size(), 1);
244 T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
246 ASSERT_EQ(t5_3.timestamps.size(), 0);
247 ASSERT_EQ(t10_1.timestamps.size(), 0);
248 ASSERT_EQ(t10_2.timestamps.size(), 0);
250 T_CHECK_TIMEOUT(start, end, milliseconds(5));
254 * Test an event scheduled before the last event fires on time
257 TEST_F(HHWheelTimerTest, SlowFast) {
258 StackWheelTimer t(&eventBase, milliseconds(1));
263 ASSERT_EQ(t.count(), 0);
265 t.scheduleTimeout(&t1, milliseconds(10));
266 t.scheduleTimeout(&t2, milliseconds(5));
268 ASSERT_EQ(t.count(), 2);
274 ASSERT_EQ(t1.timestamps.size(), 1);
275 ASSERT_EQ(t2.timestamps.size(), 1);
276 ASSERT_EQ(t.count(), 0);
278 // Check that the timeout was delayed by sleep
279 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(10), milliseconds(1));
280 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5), milliseconds(1));
283 TEST_F(HHWheelTimerTest, ReschedTest) {
284 StackWheelTimer t(&eventBase, milliseconds(1));
289 ASSERT_EQ(t.count(), 0);
291 t.scheduleTimeout(&t1, milliseconds(128));
294 t.scheduleTimeout(&t2, milliseconds(255)); // WHEEL_SIZE - 1
296 ASSERT_EQ(t.count(), 1);
299 ASSERT_EQ(t.count(), 1);
305 ASSERT_EQ(t1.timestamps.size(), 1);
306 ASSERT_EQ(t2.timestamps.size(), 1);
307 ASSERT_EQ(t.count(), 0);
309 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
310 T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(255), milliseconds(1));
313 TEST_F(HHWheelTimerTest, DeleteWheelInTimeout) {
314 auto t = HHWheelTimer::newTimer(&eventBase, milliseconds(1));
320 ASSERT_EQ(t->count(), 0);
322 t->scheduleTimeout(&t1, milliseconds(128));
323 t->scheduleTimeout(&t2, milliseconds(128));
324 t->scheduleTimeout(&t3, milliseconds(128));
325 t1.fn = [&]() { t2.cancelTimeout(); };
326 t3.fn = [&]() { t.reset(); };
328 ASSERT_EQ(t->count(), 3);
334 ASSERT_EQ(t1.timestamps.size(), 1);
335 ASSERT_EQ(t2.timestamps.size(), 0);
337 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
341 * Test scheduling a mix of timers with default timeout and variable timeout.
343 TEST_F(HHWheelTimerTest, DefaultTimeout) {
344 milliseconds defaultTimeout(milliseconds(5));
345 StackWheelTimer t(&eventBase,
347 AsyncTimeout::InternalEnum::NORMAL,
353 ASSERT_EQ(t.count(), 0);
354 ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
356 t.scheduleTimeout(&t1);
357 t.scheduleTimeout(&t2, milliseconds(10));
359 ASSERT_EQ(t.count(), 2);
365 ASSERT_EQ(t1.timestamps.size(), 1);
366 ASSERT_EQ(t2.timestamps.size(), 1);
368 ASSERT_EQ(t.count(), 0);
370 T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
371 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
372 T_CHECK_TIMEOUT(start, end, milliseconds(10));
375 TEST_F(HHWheelTimerTest, lambda) {
376 StackWheelTimer t(&eventBase, milliseconds(1));
378 t.scheduleTimeoutFn([&]{ count++; }, milliseconds(1));
383 // shouldn't crash because we swallow and log the error (you'll have to look
384 // at the console to confirm logging)
385 TEST_F(HHWheelTimerTest, lambdaThrows) {
386 StackWheelTimer t(&eventBase, milliseconds(1));
387 t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
392 TEST_F(HHWheelTimerTest, cancelAll) {
393 StackWheelTimer t(&eventBase, milliseconds(1));
395 t.scheduleTimeout(&tt, std::chrono::minutes(1));
396 EXPECT_EQ(1, t.cancelAll());
397 EXPECT_EQ(1, tt.canceledTimestamps.size());
400 TEST_F(HHWheelTimerTest, IntrusivePtr) {
401 HHWheelTimer::UniquePtr t(
402 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
408 ASSERT_EQ(t->count(), 0);
410 t->scheduleTimeout(&t1, milliseconds(5));
411 t->scheduleTimeout(&t2, milliseconds(5));
413 DelayedDestruction::IntrusivePtr<HHWheelTimer> s(t);
415 s->scheduleTimeout(&t3, milliseconds(10));
417 ASSERT_EQ(t->count(), 3);
419 // Kill the UniquePtr, but the SharedPtr keeps it alive
426 ASSERT_EQ(t1.timestamps.size(), 1);
427 ASSERT_EQ(t2.timestamps.size(), 1);
428 ASSERT_EQ(t3.timestamps.size(), 1);
430 ASSERT_EQ(s->count(), 0);
432 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
433 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
434 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
435 T_CHECK_TIMEOUT(start, end, milliseconds(10));