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::steady_clock::time_point getCurTime() override {
60 return std::chrono::steady_clock::now() - milliseconds(5);
64 struct HHWheelTimerTest : public ::testing::Test {
69 * Test firing some simple timeouts that are fired once and never rescheduled
71 TEST_F(HHWheelTimerTest, FireOnce) {
72 StackWheelTimer t(&eventBase, milliseconds(1));
78 ASSERT_EQ(t.count(), 0);
80 t.scheduleTimeout(&t1, milliseconds(5));
81 t.scheduleTimeout(&t2, milliseconds(5));
82 // Verify scheduling it twice cancels, then schedules.
83 // Should only get one callback.
84 t.scheduleTimeout(&t2, milliseconds(5));
85 t.scheduleTimeout(&t3, milliseconds(10));
87 ASSERT_EQ(t.count(), 3);
93 ASSERT_EQ(t1.timestamps.size(), 1);
94 ASSERT_EQ(t2.timestamps.size(), 1);
95 ASSERT_EQ(t3.timestamps.size(), 1);
97 ASSERT_EQ(t.count(), 0);
99 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
100 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
101 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
102 T_CHECK_TIMEOUT(start, end, milliseconds(10));
106 * Test scheduling a timeout from another timeout callback.
108 TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
109 HHWheelTimer& t = eventBase.timer();
112 // Delayed to simulate the steady_clock counter lagging
113 TestTimeoutDelayed t2;
115 t.scheduleTimeout(&t1, milliseconds(500));
116 t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
117 // If t is in an inconsistent state, detachEventBase should fail.
118 t2.fn = [&] { t.detachEventBase(); };
120 ASSERT_EQ(t.count(), 1);
124 ASSERT_EQ(t.count(), 0);
125 ASSERT_EQ(t1.timestamps.size(), 1);
126 ASSERT_EQ(t2.timestamps.size(), 1);
130 * Test cancelling a timeout when it is scheduled to be fired right away.
133 TEST_F(HHWheelTimerTest, CancelTimeout) {
134 StackWheelTimer t(&eventBase, milliseconds(1));
136 // Create several timeouts that will all fire in 5ms.
137 TestTimeout t5_1(&t, milliseconds(5));
138 TestTimeout t5_2(&t, milliseconds(5));
139 TestTimeout t5_3(&t, milliseconds(5));
140 TestTimeout t5_4(&t, milliseconds(5));
141 TestTimeout t5_5(&t, milliseconds(5));
143 // Also create a few timeouts to fire in 10ms
144 TestTimeout t10_1(&t, milliseconds(10));
145 TestTimeout t10_2(&t, milliseconds(10));
146 TestTimeout t10_3(&t, milliseconds(10));
148 TestTimeout t20_1(&t, milliseconds(20));
149 TestTimeout t20_2(&t, milliseconds(20));
151 // Have t5_1 cancel t5_2 and t5_4.
153 // Cancelling t5_2 will test cancelling a timeout that is at the head of the
154 // list and ready to be fired.
156 // Cancelling t5_4 will test cancelling a timeout in the middle of the list
158 t5_2.cancelTimeout();
159 t5_4.cancelTimeout();
162 // Have t5_3 cancel t5_5.
163 // This will test cancelling the last remaining timeout.
165 // Then have t5_3 reschedule itself.
167 t5_5.cancelTimeout();
168 // Reset our function so we won't continually reschedule ourself
169 std::function<void()> fnDtorGuard;
170 t5_3.fn.swap(fnDtorGuard);
171 t.scheduleTimeout(&t5_3, milliseconds(5));
173 // Also test cancelling timeouts in another timeset that isn't ready to
176 // Cancel the middle timeout in ts10.
177 t10_2.cancelTimeout();
178 // Cancel both the timeouts in ts20.
179 t20_1.cancelTimeout();
180 t20_2.cancelTimeout();
187 ASSERT_EQ(t5_1.timestamps.size(), 1);
188 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
190 ASSERT_EQ(t5_3.timestamps.size(), 2);
191 T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
192 T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
194 ASSERT_EQ(t10_1.timestamps.size(), 1);
195 T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
196 ASSERT_EQ(t10_3.timestamps.size(), 1);
197 T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
199 // Cancelled timeouts
200 ASSERT_EQ(t5_2.timestamps.size(), 0);
201 ASSERT_EQ(t5_4.timestamps.size(), 0);
202 ASSERT_EQ(t5_5.timestamps.size(), 0);
203 ASSERT_EQ(t10_2.timestamps.size(), 0);
204 ASSERT_EQ(t20_1.timestamps.size(), 0);
205 ASSERT_EQ(t20_2.timestamps.size(), 0);
207 T_CHECK_TIMEOUT(start, end, milliseconds(10));
211 * Test destroying a HHWheelTimer with timeouts outstanding
214 TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
215 HHWheelTimer::UniquePtr t(
216 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
218 TestTimeout t5_1(t.get(), milliseconds(5));
219 TestTimeout t5_2(t.get(), milliseconds(5));
220 TestTimeout t5_3(t.get(), milliseconds(5));
222 TestTimeout t10_1(t.get(), milliseconds(10));
223 TestTimeout t10_2(t.get(), milliseconds(10));
225 // Have t5_2 destroy t
226 // Note that this will call destroy() inside t's timeoutExpired()
229 t5_3.cancelTimeout();
230 t5_1.cancelTimeout();
231 t10_1.cancelTimeout();
232 t10_2.cancelTimeout();
239 ASSERT_EQ(t5_1.timestamps.size(), 1);
240 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
241 ASSERT_EQ(t5_2.timestamps.size(), 1);
242 T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
244 ASSERT_EQ(t5_3.timestamps.size(), 0);
245 ASSERT_EQ(t10_1.timestamps.size(), 0);
246 ASSERT_EQ(t10_2.timestamps.size(), 0);
248 T_CHECK_TIMEOUT(start, end, milliseconds(5));
252 * Test an event scheduled before the last event fires on time
255 TEST_F(HHWheelTimerTest, SlowFast) {
256 StackWheelTimer t(&eventBase, milliseconds(1));
261 ASSERT_EQ(t.count(), 0);
263 t.scheduleTimeout(&t1, milliseconds(10));
264 t.scheduleTimeout(&t2, milliseconds(5));
266 ASSERT_EQ(t.count(), 2);
272 ASSERT_EQ(t1.timestamps.size(), 1);
273 ASSERT_EQ(t2.timestamps.size(), 1);
274 ASSERT_EQ(t.count(), 0);
276 // Check that the timeout was delayed by sleep
277 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(10), milliseconds(1));
278 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5), milliseconds(1));
281 TEST_F(HHWheelTimerTest, ReschedTest) {
282 StackWheelTimer t(&eventBase, milliseconds(1));
287 ASSERT_EQ(t.count(), 0);
289 t.scheduleTimeout(&t1, milliseconds(128));
292 t.scheduleTimeout(&t2, milliseconds(255)); // WHEEL_SIZE - 1
294 ASSERT_EQ(t.count(), 1);
297 ASSERT_EQ(t.count(), 1);
303 ASSERT_EQ(t1.timestamps.size(), 1);
304 ASSERT_EQ(t2.timestamps.size(), 1);
305 ASSERT_EQ(t.count(), 0);
307 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
308 T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(255), milliseconds(1));
311 TEST_F(HHWheelTimerTest, DeleteWheelInTimeout) {
312 auto t = HHWheelTimer::newTimer(&eventBase, milliseconds(1));
318 ASSERT_EQ(t->count(), 0);
320 t->scheduleTimeout(&t1, milliseconds(128));
321 t->scheduleTimeout(&t2, milliseconds(128));
322 t->scheduleTimeout(&t3, milliseconds(128));
323 t1.fn = [&]() { t2.cancelTimeout(); };
324 t3.fn = [&]() { t.reset(); };
326 ASSERT_EQ(t->count(), 3);
332 ASSERT_EQ(t1.timestamps.size(), 1);
333 ASSERT_EQ(t2.timestamps.size(), 0);
335 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
339 * Test scheduling a mix of timers with default timeout and variable timeout.
341 TEST_F(HHWheelTimerTest, DefaultTimeout) {
342 milliseconds defaultTimeout(milliseconds(5));
343 StackWheelTimer t(&eventBase,
345 AsyncTimeout::InternalEnum::NORMAL,
351 ASSERT_EQ(t.count(), 0);
352 ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
354 t.scheduleTimeout(&t1);
355 t.scheduleTimeout(&t2, milliseconds(10));
357 ASSERT_EQ(t.count(), 2);
363 ASSERT_EQ(t1.timestamps.size(), 1);
364 ASSERT_EQ(t2.timestamps.size(), 1);
366 ASSERT_EQ(t.count(), 0);
368 T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
369 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
370 T_CHECK_TIMEOUT(start, end, milliseconds(10));
373 TEST_F(HHWheelTimerTest, lambda) {
374 StackWheelTimer t(&eventBase, milliseconds(1));
376 t.scheduleTimeoutFn([&]{ count++; }, milliseconds(1));
381 // shouldn't crash because we swallow and log the error (you'll have to look
382 // at the console to confirm logging)
383 TEST_F(HHWheelTimerTest, lambdaThrows) {
384 StackWheelTimer t(&eventBase, milliseconds(1));
385 t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
390 TEST_F(HHWheelTimerTest, cancelAll) {
391 StackWheelTimer t(&eventBase, milliseconds(1));
393 t.scheduleTimeout(&tt, std::chrono::minutes(1));
394 EXPECT_EQ(1, t.cancelAll());
395 EXPECT_EQ(1, tt.canceledTimestamps.size());
398 TEST_F(HHWheelTimerTest, IntrusivePtr) {
399 HHWheelTimer::UniquePtr t(
400 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
406 ASSERT_EQ(t->count(), 0);
408 t->scheduleTimeout(&t1, milliseconds(5));
409 t->scheduleTimeout(&t2, milliseconds(5));
411 DelayedDestruction::IntrusivePtr<HHWheelTimer> s(t);
413 s->scheduleTimeout(&t3, milliseconds(10));
415 ASSERT_EQ(t->count(), 3);
417 // Kill the UniquePtr, but the SharedPtr keeps it alive
424 ASSERT_EQ(t1.timestamps.size(), 1);
425 ASSERT_EQ(t2.timestamps.size(), 1);
426 ASSERT_EQ(t3.timestamps.size(), 1);
428 ASSERT_EQ(s->count(), 0);
430 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
431 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
432 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
433 T_CHECK_TIMEOUT(start, end, milliseconds(10));