2 * Copyright 2004-present Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include <folly/io/async/HHWheelTimer.h>
18 #include <folly/io/async/EventBase.h>
19 #include <folly/io/async/test/UndelayedDestruction.h>
20 #include <folly/io/async/test/Util.h>
21 #include <folly/portability/GTest.h>
23 using namespace folly;
24 using std::chrono::milliseconds;
26 typedef UndelayedDestruction<HHWheelTimer> StackWheelTimer;
28 class TestTimeout : public HHWheelTimer::Callback {
31 TestTimeout(HHWheelTimer* t, milliseconds timeout) {
32 t->scheduleTimeout(this, timeout);
35 void timeoutExpired() noexcept override {
36 timestamps.emplace_back();
42 void callbackCanceled() noexcept override {
43 canceledTimestamps.emplace_back();
49 std::deque<TimePoint> timestamps;
50 std::deque<TimePoint> canceledTimestamps;
51 std::function<void()> fn;
55 class TestTimeoutDelayed : public TestTimeout {
57 std::chrono::steady_clock::time_point getCurTime() override {
58 return std::chrono::steady_clock::now() - milliseconds(5);
62 struct HHWheelTimerTest : public ::testing::Test {
67 * Test firing some simple timeouts that are fired once and never rescheduled
69 TEST_F(HHWheelTimerTest, FireOnce) {
70 StackWheelTimer t(&eventBase, milliseconds(1));
76 ASSERT_EQ(t.count(), 0);
78 t.scheduleTimeout(&t1, milliseconds(5));
79 t.scheduleTimeout(&t2, milliseconds(5));
80 // Verify scheduling it twice cancels, then schedules.
81 // Should only get one callback.
82 t.scheduleTimeout(&t2, milliseconds(5));
83 t.scheduleTimeout(&t3, milliseconds(10));
85 ASSERT_EQ(t.count(), 3);
91 ASSERT_EQ(t1.timestamps.size(), 1);
92 ASSERT_EQ(t2.timestamps.size(), 1);
93 ASSERT_EQ(t3.timestamps.size(), 1);
95 ASSERT_EQ(t.count(), 0);
97 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
98 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
99 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
100 T_CHECK_TIMEOUT(start, end, milliseconds(10));
104 * Test scheduling a timeout from another timeout callback.
106 TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
107 HHWheelTimer& t = eventBase.timer();
110 // Delayed to simulate the steady_clock counter lagging
111 TestTimeoutDelayed t2;
113 t.scheduleTimeout(&t1, milliseconds(500));
114 t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
115 // If t is in an inconsistent state, detachEventBase should fail.
116 t2.fn = [&] { t.detachEventBase(); };
118 ASSERT_EQ(t.count(), 1);
122 ASSERT_EQ(t.count(), 0);
123 ASSERT_EQ(t1.timestamps.size(), 1);
124 ASSERT_EQ(t2.timestamps.size(), 1);
128 * Test cancelling a timeout when it is scheduled to be fired right away.
131 TEST_F(HHWheelTimerTest, CancelTimeout) {
132 StackWheelTimer t(&eventBase, milliseconds(1));
134 // Create several timeouts that will all fire in 5ms.
135 TestTimeout t5_1(&t, milliseconds(5));
136 TestTimeout t5_2(&t, milliseconds(5));
137 TestTimeout t5_3(&t, milliseconds(5));
138 TestTimeout t5_4(&t, milliseconds(5));
139 TestTimeout t5_5(&t, milliseconds(5));
141 // Also create a few timeouts to fire in 10ms
142 TestTimeout t10_1(&t, milliseconds(10));
143 TestTimeout t10_2(&t, milliseconds(10));
144 TestTimeout t10_3(&t, milliseconds(10));
146 TestTimeout t20_1(&t, milliseconds(20));
147 TestTimeout t20_2(&t, milliseconds(20));
149 // Have t5_1 cancel t5_2 and t5_4.
151 // Cancelling t5_2 will test cancelling a timeout that is at the head of the
152 // list and ready to be fired.
154 // Cancelling t5_4 will test cancelling a timeout in the middle of the list
156 t5_2.cancelTimeout();
157 t5_4.cancelTimeout();
160 // Have t5_3 cancel t5_5.
161 // This will test cancelling the last remaining timeout.
163 // Then have t5_3 reschedule itself.
165 t5_5.cancelTimeout();
166 // Reset our function so we won't continually reschedule ourself
167 std::function<void()> fnDtorGuard;
168 t5_3.fn.swap(fnDtorGuard);
169 t.scheduleTimeout(&t5_3, milliseconds(5));
171 // Also test cancelling timeouts in another timeset that isn't ready to
174 // Cancel the middle timeout in ts10.
175 t10_2.cancelTimeout();
176 // Cancel both the timeouts in ts20.
177 t20_1.cancelTimeout();
178 t20_2.cancelTimeout();
185 ASSERT_EQ(t5_1.timestamps.size(), 1);
186 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
188 ASSERT_EQ(t5_3.timestamps.size(), 2);
189 T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
190 T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
192 ASSERT_EQ(t10_1.timestamps.size(), 1);
193 T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
194 ASSERT_EQ(t10_3.timestamps.size(), 1);
195 T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
197 // Cancelled timeouts
198 ASSERT_EQ(t5_2.timestamps.size(), 0);
199 ASSERT_EQ(t5_4.timestamps.size(), 0);
200 ASSERT_EQ(t5_5.timestamps.size(), 0);
201 ASSERT_EQ(t10_2.timestamps.size(), 0);
202 ASSERT_EQ(t20_1.timestamps.size(), 0);
203 ASSERT_EQ(t20_2.timestamps.size(), 0);
205 T_CHECK_TIMEOUT(start, end, milliseconds(10));
209 * Test destroying a HHWheelTimer with timeouts outstanding
212 TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
213 HHWheelTimer::UniquePtr t(
214 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
216 TestTimeout t5_1(t.get(), milliseconds(5));
217 TestTimeout t5_2(t.get(), milliseconds(5));
218 TestTimeout t5_3(t.get(), milliseconds(5));
220 TestTimeout t10_1(t.get(), milliseconds(10));
221 TestTimeout t10_2(t.get(), milliseconds(10));
223 // Have t5_2 destroy t
224 // Note that this will call destroy() inside t's timeoutExpired()
227 t5_3.cancelTimeout();
228 t5_1.cancelTimeout();
229 t10_1.cancelTimeout();
230 t10_2.cancelTimeout();
237 ASSERT_EQ(t5_1.timestamps.size(), 1);
238 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
239 ASSERT_EQ(t5_2.timestamps.size(), 1);
240 T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
242 ASSERT_EQ(t5_3.timestamps.size(), 0);
243 ASSERT_EQ(t10_1.timestamps.size(), 0);
244 ASSERT_EQ(t10_2.timestamps.size(), 0);
246 T_CHECK_TIMEOUT(start, end, milliseconds(5));
250 * Test an event scheduled before the last event fires on time
253 TEST_F(HHWheelTimerTest, SlowFast) {
254 StackWheelTimer t(&eventBase, milliseconds(1));
259 ASSERT_EQ(t.count(), 0);
261 t.scheduleTimeout(&t1, milliseconds(10));
262 t.scheduleTimeout(&t2, milliseconds(5));
264 ASSERT_EQ(t.count(), 2);
270 ASSERT_EQ(t1.timestamps.size(), 1);
271 ASSERT_EQ(t2.timestamps.size(), 1);
272 ASSERT_EQ(t.count(), 0);
274 // Check that the timeout was delayed by sleep
275 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(10), milliseconds(1));
276 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5), milliseconds(1));
279 TEST_F(HHWheelTimerTest, ReschedTest) {
280 StackWheelTimer t(&eventBase, milliseconds(1));
285 ASSERT_EQ(t.count(), 0);
287 t.scheduleTimeout(&t1, milliseconds(128));
290 t.scheduleTimeout(&t2, milliseconds(255)); // WHEEL_SIZE - 1
292 ASSERT_EQ(t.count(), 1);
295 ASSERT_EQ(t.count(), 1);
301 ASSERT_EQ(t1.timestamps.size(), 1);
302 ASSERT_EQ(t2.timestamps.size(), 1);
303 ASSERT_EQ(t.count(), 0);
305 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
306 T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(255), milliseconds(1));
309 TEST_F(HHWheelTimerTest, DeleteWheelInTimeout) {
310 auto t = HHWheelTimer::newTimer(&eventBase, milliseconds(1));
316 ASSERT_EQ(t->count(), 0);
318 t->scheduleTimeout(&t1, milliseconds(128));
319 t->scheduleTimeout(&t2, milliseconds(128));
320 t->scheduleTimeout(&t3, milliseconds(128));
321 t1.fn = [&]() { t2.cancelTimeout(); };
322 t3.fn = [&]() { t.reset(); };
324 ASSERT_EQ(t->count(), 3);
330 ASSERT_EQ(t1.timestamps.size(), 1);
331 ASSERT_EQ(t2.timestamps.size(), 0);
333 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
337 * Test scheduling a mix of timers with default timeout and variable timeout.
339 TEST_F(HHWheelTimerTest, DefaultTimeout) {
340 milliseconds defaultTimeout(milliseconds(5));
341 StackWheelTimer t(&eventBase,
343 AsyncTimeout::InternalEnum::NORMAL,
349 ASSERT_EQ(t.count(), 0);
350 ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
352 t.scheduleTimeout(&t1);
353 t.scheduleTimeout(&t2, milliseconds(10));
355 ASSERT_EQ(t.count(), 2);
361 ASSERT_EQ(t1.timestamps.size(), 1);
362 ASSERT_EQ(t2.timestamps.size(), 1);
364 ASSERT_EQ(t.count(), 0);
366 T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
367 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
368 T_CHECK_TIMEOUT(start, end, milliseconds(10));
371 TEST_F(HHWheelTimerTest, lambda) {
372 StackWheelTimer t(&eventBase, milliseconds(1));
374 t.scheduleTimeoutFn([&]{ count++; }, milliseconds(1));
379 // shouldn't crash because we swallow and log the error (you'll have to look
380 // at the console to confirm logging)
381 TEST_F(HHWheelTimerTest, lambdaThrows) {
382 StackWheelTimer t(&eventBase, milliseconds(1));
383 t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
388 TEST_F(HHWheelTimerTest, cancelAll) {
389 StackWheelTimer t(&eventBase, milliseconds(1));
391 t.scheduleTimeout(&tt, std::chrono::minutes(1));
392 EXPECT_EQ(1, t.cancelAll());
393 EXPECT_EQ(1, tt.canceledTimestamps.size());
396 TEST_F(HHWheelTimerTest, IntrusivePtr) {
397 HHWheelTimer::UniquePtr t(
398 HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
404 ASSERT_EQ(t->count(), 0);
406 t->scheduleTimeout(&t1, milliseconds(5));
407 t->scheduleTimeout(&t2, milliseconds(5));
409 DelayedDestruction::IntrusivePtr<HHWheelTimer> s(t);
411 s->scheduleTimeout(&t3, milliseconds(10));
413 ASSERT_EQ(t->count(), 3);
415 // Kill the UniquePtr, but the SharedPtr keeps it alive
422 ASSERT_EQ(t1.timestamps.size(), 1);
423 ASSERT_EQ(t2.timestamps.size(), 1);
424 ASSERT_EQ(t3.timestamps.size(), 1);
426 ASSERT_EQ(s->count(), 0);
428 T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
429 T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
430 T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
431 T_CHECK_TIMEOUT(start, end, milliseconds(10));