proxygen
HHWheelTimerTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2014-present Facebook, Inc.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
22 
23 using namespace folly;
24 using std::chrono::milliseconds;
25 
27 
28 class TestTimeout : public HHWheelTimer::Callback {
29  public:
31  TestTimeout(HHWheelTimer* t, milliseconds timeout) {
32  t->scheduleTimeout(this, timeout);
33  }
34 
35  void timeoutExpired() noexcept override {
36  timestamps.emplace_back();
37  if (fn) {
38  fn();
39  }
40  }
41 
42  void callbackCanceled() noexcept override {
43  canceledTimestamps.emplace_back();
44  if (fn) {
45  fn();
46  }
47  }
48 
49  std::deque<TimePoint> timestamps;
50  std::deque<TimePoint> canceledTimestamps;
51  std::function<void()> fn;
52 };
53 
54 class TestTimeoutDelayed : public TestTimeout {
55  protected:
56  std::chrono::steady_clock::time_point getCurTime() override {
57  return std::chrono::steady_clock::now() - milliseconds(5);
58  }
59 };
60 
61 struct HHWheelTimerTest : public ::testing::Test {
62  EventBase eventBase;
63 };
64 
65 /*
66  * Test firing some simple timeouts that are fired once and never rescheduled
67  */
69  StackWheelTimer t(&eventBase, milliseconds(1));
70 
71  TestTimeout t1;
72  TestTimeout t2;
73  TestTimeout t3;
74 
75  ASSERT_EQ(t.count(), 0);
76 
77  t.scheduleTimeout(&t1, milliseconds(5));
78  t.scheduleTimeout(&t2, milliseconds(5));
79  // Verify scheduling it twice cancels, then schedules.
80  // Should only get one callback.
81  t.scheduleTimeout(&t2, milliseconds(5));
82  t.scheduleTimeout(&t3, milliseconds(10));
83 
84  ASSERT_EQ(t.count(), 3);
85 
87  eventBase.loop();
88  TimePoint end;
89 
90  ASSERT_EQ(t1.timestamps.size(), 1);
91  ASSERT_EQ(t2.timestamps.size(), 1);
92  ASSERT_EQ(t3.timestamps.size(), 1);
93 
94  ASSERT_EQ(t.count(), 0);
95 
96  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
97  T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
98  T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
99  T_CHECK_TIMEOUT(start, end, milliseconds(10));
100 }
101 
102 /*
103  * Test scheduling a timeout from another timeout callback.
104  */
105 TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
106  HHWheelTimer& t = eventBase.timer();
107 
108  TestTimeout t1;
109  // Delayed to simulate the steady_clock counter lagging
111 
112  t.scheduleTimeout(&t1, milliseconds(500));
113  t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
114  // If t is in an inconsistent state, detachEventBase should fail.
115  t2.fn = [&] { t.detachEventBase(); };
116 
117  ASSERT_EQ(t.count(), 1);
118 
119  eventBase.loop();
120 
121  ASSERT_EQ(t.count(), 0);
122  ASSERT_EQ(t1.timestamps.size(), 1);
123  ASSERT_EQ(t2.timestamps.size(), 1);
124 }
125 
126 /*
127  * Test changing default-timeout in timer.
128  */
129 TEST_F(HHWheelTimerTest, TestSetDefaultTimeout) {
130  HHWheelTimer& t = eventBase.timer();
131 
132  t.setDefaultTimeout(milliseconds(1000));
133  // verify: default-time has been modified
134  ASSERT_EQ(t.getDefaultTimeout(), milliseconds(1000));
135 }
136 
137 /*
138  * Test cancelling a timeout when it is scheduled to be fired right away.
139  */
140 
141 TEST_F(HHWheelTimerTest, CancelTimeout) {
142  StackWheelTimer t(&eventBase, milliseconds(1));
143 
144  // Create several timeouts that will all fire in 5ms.
145  TestTimeout t5_1(&t, milliseconds(5));
146  TestTimeout t5_2(&t, milliseconds(5));
147  TestTimeout t5_3(&t, milliseconds(5));
148  TestTimeout t5_4(&t, milliseconds(5));
149  TestTimeout t5_5(&t, milliseconds(5));
150 
151  // Also create a few timeouts to fire in 10ms
152  TestTimeout t10_1(&t, milliseconds(10));
153  TestTimeout t10_2(&t, milliseconds(10));
154  TestTimeout t10_3(&t, milliseconds(10));
155 
156  TestTimeout t20_1(&t, milliseconds(20));
157  TestTimeout t20_2(&t, milliseconds(20));
158 
159  // Have t5_1 cancel t5_2 and t5_4.
160  //
161  // Cancelling t5_2 will test cancelling a timeout that is at the head of the
162  // list and ready to be fired.
163  //
164  // Cancelling t5_4 will test cancelling a timeout in the middle of the list
165  t5_1.fn = [&] {
166  t5_2.cancelTimeout();
167  t5_4.cancelTimeout();
168  };
169 
170  // Have t5_3 cancel t5_5.
171  // This will test cancelling the last remaining timeout.
172  //
173  // Then have t5_3 reschedule itself.
174  t5_3.fn = [&] {
175  t5_5.cancelTimeout();
176  // Reset our function so we won't continually reschedule ourself
177  std::function<void()> fnDtorGuard;
178  t5_3.fn.swap(fnDtorGuard);
179  t.scheduleTimeout(&t5_3, milliseconds(5));
180 
181  // Also test cancelling timeouts in another timeset that isn't ready to
182  // fire yet.
183  //
184  // Cancel the middle timeout in ts10.
185  t10_2.cancelTimeout();
186  // Cancel both the timeouts in ts20.
187  t20_1.cancelTimeout();
188  t20_2.cancelTimeout();
189  };
190 
192  eventBase.loop();
193  TimePoint end;
194 
195  ASSERT_EQ(t5_1.timestamps.size(), 1);
196  T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
197 
198  ASSERT_EQ(t5_3.timestamps.size(), 2);
199  T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
200  T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
201 
202  ASSERT_EQ(t10_1.timestamps.size(), 1);
203  T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
204  ASSERT_EQ(t10_3.timestamps.size(), 1);
205  T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
206 
207  // Cancelled timeouts
208  ASSERT_EQ(t5_2.timestamps.size(), 0);
209  ASSERT_EQ(t5_4.timestamps.size(), 0);
210  ASSERT_EQ(t5_5.timestamps.size(), 0);
211  ASSERT_EQ(t10_2.timestamps.size(), 0);
212  ASSERT_EQ(t20_1.timestamps.size(), 0);
213  ASSERT_EQ(t20_2.timestamps.size(), 0);
214 
215  T_CHECK_TIMEOUT(start, end, milliseconds(10));
216 }
217 
218 /*
219  * Test destroying a HHWheelTimer with timeouts outstanding
220  */
221 
222 TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
224  HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
225 
226  TestTimeout t5_1(t.get(), milliseconds(5));
227  TestTimeout t5_2(t.get(), milliseconds(5));
228  TestTimeout t5_3(t.get(), milliseconds(5));
229 
230  TestTimeout t10_1(t.get(), milliseconds(10));
231  TestTimeout t10_2(t.get(), milliseconds(10));
232 
233  // Have t5_2 destroy t
234  // Note that this will call destroy() inside t's timeoutExpired()
235  // method.
236  t5_2.fn = [&] {
237  t5_3.cancelTimeout();
238  t5_1.cancelTimeout();
239  t10_1.cancelTimeout();
240  t10_2.cancelTimeout();
241  t.reset();
242  };
243 
245  eventBase.loop();
246  TimePoint end;
247 
248  ASSERT_EQ(t5_1.timestamps.size(), 1);
249  T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
250  ASSERT_EQ(t5_2.timestamps.size(), 1);
251  T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
252 
253  ASSERT_EQ(t5_3.timestamps.size(), 0);
254  ASSERT_EQ(t10_1.timestamps.size(), 0);
255  ASSERT_EQ(t10_2.timestamps.size(), 0);
256 
257  T_CHECK_TIMEOUT(start, end, milliseconds(5));
258 }
259 
260 /*
261  * Test an event scheduled before the last event fires on time
262  */
263 
265  StackWheelTimer t(&eventBase, milliseconds(1));
266 
267  TestTimeout t1;
268  TestTimeout t2;
269 
270  ASSERT_EQ(t.count(), 0);
271 
272  t.scheduleTimeout(&t1, milliseconds(10));
273  t.scheduleTimeout(&t2, milliseconds(5));
274 
275  ASSERT_EQ(t.count(), 2);
276 
278  eventBase.loop();
279  TimePoint end;
280 
281  ASSERT_EQ(t1.timestamps.size(), 1);
282  ASSERT_EQ(t2.timestamps.size(), 1);
283  ASSERT_EQ(t.count(), 0);
284 
285  // Check that the timeout was delayed by sleep
286  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(10), milliseconds(1));
287  T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5), milliseconds(1));
288 }
289 
290 TEST_F(HHWheelTimerTest, ReschedTest) {
291  StackWheelTimer t(&eventBase, milliseconds(1));
292 
293  TestTimeout t1;
294  TestTimeout t2;
295 
296  ASSERT_EQ(t.count(), 0);
297 
298  t.scheduleTimeout(&t1, milliseconds(128));
299  TimePoint start2;
300  t1.fn = [&]() {
301  t.scheduleTimeout(&t2, milliseconds(255)); // WHEEL_SIZE - 1
302  start2.reset();
303  ASSERT_EQ(t.count(), 1);
304  };
305 
306  ASSERT_EQ(t.count(), 1);
307 
309  eventBase.loop();
310  TimePoint end;
311 
312  ASSERT_EQ(t1.timestamps.size(), 1);
313  ASSERT_EQ(t2.timestamps.size(), 1);
314  ASSERT_EQ(t.count(), 0);
315 
316  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
317  T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(255), milliseconds(1));
318 }
319 
320 TEST_F(HHWheelTimerTest, DeleteWheelInTimeout) {
321  auto t = HHWheelTimer::newTimer(&eventBase, milliseconds(1));
322 
323  TestTimeout t1;
324  TestTimeout t2;
325  TestTimeout t3;
326 
327  ASSERT_EQ(t->count(), 0);
328 
329  t->scheduleTimeout(&t1, milliseconds(128));
330  t->scheduleTimeout(&t2, milliseconds(128));
331  t->scheduleTimeout(&t3, milliseconds(128));
332  t1.fn = [&]() { t2.cancelTimeout(); };
333  t3.fn = [&]() { t.reset(); };
334 
335  ASSERT_EQ(t->count(), 3);
336 
338  eventBase.loop();
339  TimePoint end;
340 
341  ASSERT_EQ(t1.timestamps.size(), 1);
342  ASSERT_EQ(t2.timestamps.size(), 0);
343 
344  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
345 }
346 
347 /*
348  * Test scheduling a mix of timers with default timeout and variable timeout.
349  */
350 TEST_F(HHWheelTimerTest, DefaultTimeout) {
351  milliseconds defaultTimeout(milliseconds(5));
353  &eventBase,
354  milliseconds(1),
356  defaultTimeout);
357 
358  TestTimeout t1;
359  TestTimeout t2;
360 
361  ASSERT_EQ(t.count(), 0);
362  ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
363 
364  t.scheduleTimeout(&t1);
365  t.scheduleTimeout(&t2, milliseconds(10));
366 
367  ASSERT_EQ(t.count(), 2);
368 
370  eventBase.loop();
371  TimePoint end;
372 
373  ASSERT_EQ(t1.timestamps.size(), 1);
374  ASSERT_EQ(t2.timestamps.size(), 1);
375 
376  ASSERT_EQ(t.count(), 0);
377 
378  T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
379  T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
380  T_CHECK_TIMEOUT(start, end, milliseconds(10));
381 }
382 
384  StackWheelTimer t(&eventBase, milliseconds(1));
385  size_t count = 0;
386  t.scheduleTimeoutFn([&] { count++; }, milliseconds(1));
387  eventBase.loop();
388  EXPECT_EQ(1, count);
389 }
390 
391 // shouldn't crash because we swallow and log the error (you'll have to look
392 // at the console to confirm logging)
393 TEST_F(HHWheelTimerTest, lambdaThrows) {
394  StackWheelTimer t(&eventBase, milliseconds(1));
396  [&] { throw std::runtime_error("expected"); }, milliseconds(1));
397  eventBase.loop();
398 }
399 
401  StackWheelTimer t(&eventBase, milliseconds(1));
402  TestTimeout tt;
403  t.scheduleTimeout(&tt, std::chrono::minutes(1));
404  EXPECT_EQ(1, t.cancelAll());
405  EXPECT_EQ(1, tt.canceledTimestamps.size());
406 }
407 
408 TEST_F(HHWheelTimerTest, IntrusivePtr) {
410  HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
411 
412  TestTimeout t1;
413  TestTimeout t2;
414  TestTimeout t3;
415 
416  ASSERT_EQ(t->count(), 0);
417 
418  t->scheduleTimeout(&t1, milliseconds(5));
419  t->scheduleTimeout(&t2, milliseconds(5));
420 
421  DelayedDestruction::IntrusivePtr<HHWheelTimer> s(t);
422 
423  s->scheduleTimeout(&t3, milliseconds(10));
424 
425  ASSERT_EQ(t->count(), 3);
426 
427  // Kill the UniquePtr, but the SharedPtr keeps it alive
428  t.reset();
429 
431  eventBase.loop();
432  TimePoint end;
433 
434  ASSERT_EQ(t1.timestamps.size(), 1);
435  ASSERT_EQ(t2.timestamps.size(), 1);
436  ASSERT_EQ(t3.timestamps.size(), 1);
437 
438  ASSERT_EQ(s->count(), 0);
439 
440  T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
441  T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
442  T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
443  T_CHECK_TIMEOUT(start, end, milliseconds(10));
444 }
445 
446 TEST_F(HHWheelTimerTest, GetTimeRemaining) {
447  StackWheelTimer t(&eventBase, milliseconds(1));
448  TestTimeout t1;
449 
450  // Not scheduled yet, time remaining should be zero
451  ASSERT_EQ(t1.getTimeRemaining(), milliseconds(0));
452  ASSERT_EQ(t.count(), 0);
453 
454  // Scheduled, time remaining should be less than or equal to the scheduled
455  // timeout
456  t.scheduleTimeout(&t1, milliseconds(10));
457  ASSERT_LE(t1.getTimeRemaining(), milliseconds(10));
458 
460  eventBase.loop();
461  TimePoint end;
462 
463  // Expired and time remaining should be zero
464  ASSERT_EQ(t1.getTimeRemaining(), milliseconds(0));
465 
466  ASSERT_EQ(t.count(), 0);
467  T_CHECK_TIMEOUT(start, end, milliseconds(10));
468 }
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
std::deque< TimePoint > canceledTimestamps
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
UndelayedDestruction< HHWheelTimer > StackWheelTimer
std::chrono::steady_clock::time_point now()
#define ASSERT_LE(val1, val2)
Definition: gtest.h:1964
void setDefaultTimeout(std::chrono::milliseconds timeout)
Definition: HHWheelTimer.h:200
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires E e noexcept(noexcept(s.error(std::move(e))))
void scheduleTimeout(Callback *callback, std::chrono::milliseconds timeout)
std::deque< TimePoint > timestamps
TestTimeout(HHWheelTimer *t, milliseconds timeout)
static UniquePtr newTimer(Args &&...args)
Definition: HHWheelTimer.h:61
void callbackCanceled() noexceptoverride
auto end(TestAdlIterable &instance)
Definition: ForeachTest.cpp:62
void timeoutExpired() noexceptoverride
void scheduleTimeoutFn(F fn, std::chrono::milliseconds timeout)
Definition: HHWheelTimer.h:229
std::unique_ptr< HHWheelTimer, Destructor > UniquePtr
Definition: HHWheelTimer.h:57
auto start
TEST_F(AsyncSSLSocketWriteTest, write_coalescing1)
int * count
static set< string > s
std::size_t count() const
Definition: HHWheelTimer.h:252
std::chrono::milliseconds getDefaultTimeout() const
Definition: HHWheelTimer.h:193
std::chrono::steady_clock::time_point getCurTime() override
std::function< void()> fn
#define T_CHECK_TIMEOUT(start, end, expectedMS,...)
Definition: Util.h:38