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>
24 #include <gtest/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);
38 virtual void timeoutExpired() noexcept {
39 timestamps.push_back(TimePoint());
45 std::deque<TimePoint> timestamps;
46 std::function<void()> fn;
50 * Test firing some simple timeouts that are fired once and never rescheduled
52 TEST(HHWheelTimerTest, FireOnce) {
54 StackWheelTimer t(&eventBase, milliseconds(1));
56 const HHWheelTimer::Callback* nullCallback = nullptr;
62 ASSERT_EQ(t.count(), 0);
64 t.scheduleTimeout(&t1, milliseconds(5));
65 t.scheduleTimeout(&t2, milliseconds(5));
66 // Verify scheduling it twice cancels, then schedules.
67 // Should only get one callback.
68 t.scheduleTimeout(&t2, milliseconds(5));
69 t.scheduleTimeout(&t3, milliseconds(10));
71 ASSERT_EQ(t.count(), 3);
77 ASSERT_EQ(t1.timestamps.size(), 1);
78 ASSERT_EQ(t2.timestamps.size(), 1);
79 ASSERT_EQ(t3.timestamps.size(), 1);
81 ASSERT_EQ(t.count(), 0);
83 T_CHECK_TIMEOUT(start, t1.timestamps[0], 5);
84 T_CHECK_TIMEOUT(start, t2.timestamps[0], 5);
85 T_CHECK_TIMEOUT(start, t3.timestamps[0], 10);
86 T_CHECK_TIMEOUT(start, end, 10);
90 * Test cancelling a timeout when it is scheduled to be fired right away.
93 TEST(HHWheelTimerTest, CancelTimeout) {
95 StackWheelTimer t(&eventBase, milliseconds(1));
97 // Create several timeouts that will all fire in 5ms.
98 TestTimeout t5_1(&t, milliseconds(5));
99 TestTimeout t5_2(&t, milliseconds(5));
100 TestTimeout t5_3(&t, milliseconds(5));
101 TestTimeout t5_4(&t, milliseconds(5));
102 TestTimeout t5_5(&t, milliseconds(5));
104 // Also create a few timeouts to fire in 10ms
105 TestTimeout t10_1(&t, milliseconds(10));
106 TestTimeout t10_2(&t, milliseconds(10));
107 TestTimeout t10_3(&t, milliseconds(10));
109 TestTimeout t20_1(&t, milliseconds(20));
110 TestTimeout t20_2(&t, milliseconds(20));
112 // Have t5_1 cancel t5_2 and t5_4.
114 // Cancelling t5_2 will test cancelling a timeout that is at the head of the
115 // list and ready to be fired.
117 // Cancelling t5_4 will test cancelling a timeout in the middle of the list
119 t5_2.cancelTimeout();
120 t5_4.cancelTimeout();
123 // Have t5_3 cancel t5_5.
124 // This will test cancelling the last remaining timeout.
126 // Then have t5_3 reschedule itself.
128 t5_5.cancelTimeout();
129 // Reset our function so we won't continually reschedule ourself
130 std::function<void()> fnDtorGuard;
131 t5_3.fn.swap(fnDtorGuard);
132 t.scheduleTimeout(&t5_3, milliseconds(5));
134 // Also test cancelling timeouts in another timeset that isn't ready to
137 // Cancel the middle timeout in ts10.
138 t10_2.cancelTimeout();
139 // Cancel both the timeouts in ts20.
140 t20_1.cancelTimeout();
141 t20_2.cancelTimeout();
148 ASSERT_EQ(t5_1.timestamps.size(), 1);
149 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], 5);
151 ASSERT_EQ(t5_3.timestamps.size(), 2);
152 T_CHECK_TIMEOUT(start, t5_3.timestamps[0], 5);
153 T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], 5);
155 ASSERT_EQ(t10_1.timestamps.size(), 1);
156 T_CHECK_TIMEOUT(start, t10_1.timestamps[0], 10);
157 ASSERT_EQ(t10_3.timestamps.size(), 1);
158 T_CHECK_TIMEOUT(start, t10_3.timestamps[0], 10);
160 // Cancelled timeouts
161 ASSERT_EQ(t5_2.timestamps.size(), 0);
162 ASSERT_EQ(t5_4.timestamps.size(), 0);
163 ASSERT_EQ(t5_5.timestamps.size(), 0);
164 ASSERT_EQ(t10_2.timestamps.size(), 0);
165 ASSERT_EQ(t20_1.timestamps.size(), 0);
166 ASSERT_EQ(t20_2.timestamps.size(), 0);
168 T_CHECK_TIMEOUT(start, end, 10);
172 * Test destroying a HHWheelTimer with timeouts outstanding
175 TEST(HHWheelTimerTest, DestroyTimeoutSet) {
178 HHWheelTimer::UniquePtr t(
179 new HHWheelTimer(&eventBase, milliseconds(1)));
181 TestTimeout t5_1(t.get(), milliseconds(5));
182 TestTimeout t5_2(t.get(), milliseconds(5));
183 TestTimeout t5_3(t.get(), milliseconds(5));
185 TestTimeout t10_1(t.get(), milliseconds(10));
186 TestTimeout t10_2(t.get(), milliseconds(10));
188 // Have t5_2 destroy t
189 // Note that this will call destroy() inside t's timeoutExpired()
192 t5_3.cancelTimeout();
193 t5_1.cancelTimeout();
194 t10_1.cancelTimeout();
195 t10_2.cancelTimeout();
202 ASSERT_EQ(t5_1.timestamps.size(), 1);
203 T_CHECK_TIMEOUT(start, t5_1.timestamps[0], 5);
204 ASSERT_EQ(t5_2.timestamps.size(), 1);
205 T_CHECK_TIMEOUT(start, t5_2.timestamps[0], 5);
207 ASSERT_EQ(t5_3.timestamps.size(), 0);
208 ASSERT_EQ(t10_1.timestamps.size(), 0);
209 ASSERT_EQ(t10_2.timestamps.size(), 0);
211 T_CHECK_TIMEOUT(start, end, 5);
215 * Test the tick interval parameter
217 TEST(HHWheelTimerTest, AtMostEveryN) {
220 // Create a timeout set with a 10ms interval, to fire no more than once
222 milliseconds interval(25);
223 milliseconds atMostEveryN(6);
224 StackWheelTimer t(&eventBase, atMostEveryN);
225 t.setCatchupEveryN(70);
227 // Create 60 timeouts to be added to ts10 at 1ms intervals.
228 uint32_t numTimeouts = 60;
229 std::vector<TestTimeout> timeouts(numTimeouts);
231 // Create a scheduler timeout to add the timeouts 1ms apart.
233 StackWheelTimer ts1(&eventBase, milliseconds(1));
234 TestTimeout scheduler(&ts1, milliseconds(1));
236 if (index >= numTimeouts) {
239 // Call timeoutExpired() on the timeout so it will record a timestamp.
240 // This is done only so we can record when we scheduled the timeout.
241 // This way if ts1 starts to fall behind a little over time we will still
242 // be comparing the ts10 timeouts to when they were first scheduled (rather
243 // than when we intended to schedule them). The scheduler may fall behind
244 // eventually since we don't really schedule it once every millisecond.
245 // Each time it finishes we schedule it for 1 millisecond in the future.
246 // The amount of time it takes to run, and any delays it encounters
247 // getting scheduled may eventually add up over time.
248 timeouts[index].timeoutExpired();
250 // Schedule the new timeout
251 t.scheduleTimeout(&timeouts[index], interval);
252 // Reschedule ourself
253 ts1.scheduleTimeout(&scheduler, milliseconds(1));
257 // Go ahead and schedule the first timeout now.
264 // We scheduled timeouts 1ms apart, when the HHWheelTimer is only allowed
265 // to wake up at most once every 3ms. It will therefore wake up every 3ms
266 // and fire groups of approximately 3 timeouts at a time.
268 // This is "approximately 3" since it may get slightly behind and fire 4 in
269 // one interval, etc. T_CHECK_TIMEOUT normally allows a few milliseconds of
270 // tolerance. We have to add the same into our checking algorithm here.
271 for (uint32_t idx = 0; idx < numTimeouts; ++idx) {
272 ASSERT_EQ(timeouts[idx].timestamps.size(), 2);
274 TimePoint scheduledTime(timeouts[idx].timestamps[0]);
275 TimePoint firedTime(timeouts[idx].timestamps[1]);
277 // Assert that the timeout fired at roughly the right time.
278 // T_CHECK_TIMEOUT() normally has a tolerance of 5ms. Allow an additional
280 milliseconds tolerance = milliseconds(5) + interval;
281 T_CHECK_TIMEOUT(scheduledTime, firedTime, atMostEveryN.count(),
284 // Assert that the difference between the previous timeout and now was
285 // either very small (fired in the same event loop), or larger than
291 TimePoint prev(timeouts[idx - 1].timestamps[1]);
293 milliseconds delta((firedTime.getTimeStart() - prev.getTimeEnd()) -
294 (firedTime.getTimeWaiting() - prev.getTimeWaiting()));
295 if (delta > milliseconds(1)) {
296 T_CHECK_TIMEOUT(prev, firedTime, atMostEveryN.count()); }
301 * Test an event loop that is blocking
304 TEST(HHWheelTimerTest, SlowLoop) {
306 StackWheelTimer t(&eventBase, milliseconds(1));
311 ASSERT_EQ(t.count(), 0);
313 eventBase.runInLoop([](){usleep(10000);});
314 t.scheduleTimeout(&t1, milliseconds(5));
316 ASSERT_EQ(t.count(), 1);
322 ASSERT_EQ(t1.timestamps.size(), 1);
323 ASSERT_EQ(t.count(), 0);
325 // Check that the timeout was delayed by sleep
326 T_CHECK_TIMEOUT(start, t1.timestamps[0], 15, 1);
327 T_CHECK_TIMEOUT(start, end, 15, 1);
329 // Try it again, this time with catchup timing every loop
330 t.setCatchupEveryN(1);
332 eventBase.runInLoop([](){usleep(10000);});
333 t.scheduleTimeout(&t2, milliseconds(5));
335 ASSERT_EQ(t.count(), 1);
341 ASSERT_EQ(t2.timestamps.size(), 1);
342 ASSERT_EQ(t.count(), 0);
344 // Check that the timeout was NOT delayed by sleep
345 T_CHECK_TIMEOUT(start2, t2.timestamps[0], 10, 1);
346 T_CHECK_TIMEOUT(start2, end2, 10, 1);