proxygen
AsyncTimeoutSetTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
17 #include <boost/container/flat_map.hpp>
18 #include <vector>
19 
20 
21 using namespace proxygen;
22 using namespace testing;
25 using std::chrono::milliseconds;
26 
28 
30  public:
32  }
33 
34  MOCK_METHOD0(millisecondsSinceEpoch, std::chrono::milliseconds());
35 };
36 
38  public:
39  template<typename ...Args>
40  explicit TestTimeout(Args&& ...args) {
41  addTimeout(std::forward<Args>(args)...);
42  _scheduleNext();
43  }
45 
47  nextSets_.push_back(set);
48  }
49 
50  template<typename ...Args>
51  void addTimeout(AsyncTimeoutSet* set, Args&& ...args) {
52  addTimeout(set);
53  addTimeout(std::forward<Args>(args)...);
54  }
55 
56  void timeoutExpired() noexcept override {
57  timestamps.emplace_back(clock_->millisecondsSinceEpoch());
58  _scheduleNext();
59  if (fn) {
60  fn();
61  }
62  }
63 
64  void _scheduleNext() {
65  if (nextSets_.empty()) {
66  return;
67  }
68  AsyncTimeoutSet* nextSet = nextSets_.front();
69  nextSets_.pop_front();
70  nextSet->scheduleTimeout(this);
71  }
72 
73  std::deque<milliseconds> timestamps;
74  std::function<void()> fn;
75 
76  static void setTimeoutClock(MockTimeoutClock& clock) {
77  clock_ = &clock;
78  }
79 
80  private:
82  std::deque<AsyncTimeoutSet*> nextSets_;
83 };
84 
86 
87 class TimeoutTest : public testing::Test {
88  public:
89 
90  void SetUp() override {
91  TestTimeout::setTimeoutClock(timeoutClock_);
92  setClock(milliseconds(0));
93 
94  EXPECT_CALL(timeoutManager_, attachTimeoutManager(_, _))
95  .WillRepeatedly(Return());
96 
97  EXPECT_CALL(timeoutManager_, scheduleTimeout(_, _))
98  .WillRepeatedly(Invoke([this] (AsyncTimeout* p, milliseconds t) {
99  timeoutManager_.cancelTimeout(p);
100  folly::event_ref_flags(p->getEvent()) |= EVLIST_TIMEOUT;
101  timeouts_.emplace(t + timeoutClock_.millisecondsSinceEpoch(),
102  p);
103  return true;
104  }));
105 
106  EXPECT_CALL(timeoutManager_, cancelTimeout(_))
107  .WillRepeatedly(Invoke([this] (AsyncTimeout* p) {
108  for (auto it = timeouts_.begin(); it != timeouts_.end(); it++) {
109  if (it->second == p) {
110  timeouts_.erase(it);
111  break;
112  }
113  }
114  }));
115  }
116 
117  void loop() {
118  for (auto t = timeoutClock_.millisecondsSinceEpoch() + milliseconds(1);
119  !timeouts_.empty(); t++) {
120  setClock(t);
121  }
122  }
123 
124  void setClock(milliseconds ms) {
125  EXPECT_CALL(timeoutClock_, millisecondsSinceEpoch())
126  .WillRepeatedly(Return(ms));
127 
128  while (!timeouts_.empty() &&
129  timeoutClock_.millisecondsSinceEpoch() >= timeouts_.begin()->first) {
130  AsyncTimeout* timeout = timeouts_.begin()->second;
131  timeouts_.erase(timeouts_.begin());
132  folly::event_ref_flags(timeout->getEvent()) &= ~EVLIST_TIMEOUT;
133  timeout->timeoutExpired();
134  }
135  }
136 
137  protected:
140  boost::container::flat_multimap<milliseconds, AsyncTimeout*> timeouts_;
141 };
142 
143 /*
144  * Test firing some simple timeouts that are fired once and never rescheduled
145  */
146 TEST_F(TimeoutTest, FireOnce) {
147  StackTimeoutSet ts10(&timeoutManager_, milliseconds(10), milliseconds(0),
148  &timeoutClock_);
149  StackTimeoutSet ts5(&timeoutManager_, milliseconds(5), milliseconds(0),
150  &timeoutClock_);
151 
152  const AsyncTimeoutSet::Callback* nullCallback = nullptr;
153  ASSERT_EQ(ts10.front(), nullCallback);
154  ASSERT_EQ(ts5.front(), nullCallback);
155 
156  TestTimeout t1;
157  TestTimeout t2;
158  TestTimeout t3;
159 
160  ts5.scheduleTimeout(&t1); // fires at time=5
161 
162  // tick forward to time=2 and schedule another 5ms (@7ms) and a
163  // 10ms (12ms) timeout
164 
165  setClock(milliseconds(2));
166 
167  ts5.scheduleTimeout(&t2);
168  ts10.scheduleTimeout(&t3);
169 
170  ASSERT_EQ(ts10.front(), &t3);
171  ASSERT_EQ(ts5.front(), &t1);
172 
173  setClock(milliseconds(5));
174  ASSERT_EQ(ts5.front(), &t2);
175 
176  setClock(milliseconds(7));
177  ASSERT_EQ(ts5.front(), nullCallback);
178 
179  ASSERT_EQ(t1.timestamps.size(), 1);
180  ASSERT_EQ(t2.timestamps.size(), 1);
181 
182  setClock(milliseconds(12));
183 
184  ASSERT_EQ(t3.timestamps.size(), 1);
185 
186  ASSERT_EQ(ts10.front(), nullCallback);
187 
188  ASSERT_EQ(t1.timestamps[0], milliseconds(5));
189  ASSERT_EQ(t2.timestamps[0], milliseconds(7));
190  ASSERT_EQ(t3.timestamps[0], milliseconds(12));
191 }
192 
193 /*
194  * Test some timeouts that are scheduled on one timeout set, then moved to
195  * another timeout set.
196  */
197 TEST_F(TimeoutTest, SwitchTimeoutSet) {
198  StackTimeoutSet ts10(&timeoutManager_, milliseconds(10), milliseconds(0),
199  &timeoutClock_);
200  StackTimeoutSet ts5(&timeoutManager_, milliseconds(5), milliseconds(0),
201  &timeoutClock_);
202 
203  TestTimeout t1(&ts5, &ts10, &ts5);
204  TestTimeout t2(&ts10, &ts10, &ts5);
205  TestTimeout t3(&ts5, &ts5, &ts10, &ts5);
206 
207  ts5.scheduleTimeout(&t1);
208 
209  loop();
210 
211  ASSERT_EQ(t1.timestamps.size(), 3);
212  ASSERT_EQ(t2.timestamps.size(), 3);
213  ASSERT_EQ(t3.timestamps.size(), 4);
214 
215  ASSERT_EQ(t1.timestamps[0], milliseconds(5));
216  ASSERT_EQ(t1.timestamps[1] - t1.timestamps[0], milliseconds(10));
217  ASSERT_EQ(t1.timestamps[2] - t1.timestamps[1], milliseconds(5));
218 
219  ASSERT_EQ(t2.timestamps[0], milliseconds(10));
220  ASSERT_EQ(t2.timestamps[1] - t2.timestamps[0], milliseconds(10));
221  ASSERT_EQ(t2.timestamps[2] - t2.timestamps[1], milliseconds(5));
222 
223  ASSERT_EQ(t3.timestamps[0], milliseconds(5));
224  ASSERT_EQ(t3.timestamps[1] - t3.timestamps[0], milliseconds(5));
225  ASSERT_EQ(t3.timestamps[2] - t3.timestamps[1], milliseconds(10));
226  ASSERT_EQ(t3.timestamps[3] - t3.timestamps[2], milliseconds(5));
227  ASSERT_EQ(timeoutClock_.millisecondsSinceEpoch(), milliseconds(25));
228 }
229 
230 /*
231  * Test cancelling a timeout when it is scheduled to be fired right away.
232  */
233 TEST_F(TimeoutTest, CancelTimeout) {
234  StackTimeoutSet ts5(&timeoutManager_, milliseconds(5), milliseconds(0),
235  &timeoutClock_);
236  StackTimeoutSet ts10(&timeoutManager_, milliseconds(10), milliseconds(0),
237  &timeoutClock_);
238  StackTimeoutSet ts20(&timeoutManager_, milliseconds(20), milliseconds(0),
239  &timeoutClock_);
240 
241  // Create several timeouts that will all fire in 5ms.
242  TestTimeout t5_1(&ts5);
243  TestTimeout t5_2(&ts5);
244  TestTimeout t5_3(&ts5);
245  TestTimeout t5_4(&ts5);
246  TestTimeout t5_5(&ts5);
247 
248  // Also create a few timeouts to fire in 10ms
249  TestTimeout t10_1(&ts10);
250  TestTimeout t10_2(&ts10);
251  TestTimeout t10_3(&ts10);
252 
253  TestTimeout t20_1(&ts20);
254  TestTimeout t20_2(&ts20);
255 
256  // Have t5_1 cancel t5_2 and t5_4.
257  //
258  // Cancelling t5_2 will test cancelling a timeout that is at the head of the
259  // list and ready to be fired.
260  //
261  // Cancelling t5_4 will test cancelling a timeout in the middle of the list
262  t5_1.fn = [&] {
263  t5_2.cancelTimeout();
264  t5_4.cancelTimeout();
265  };
266 
267  // Have t5_3 cancel t5_5.
268  // This will test cancelling the last remaining timeout.
269  //
270  // Then have t5_3 reschedule itself.
271  t5_3.fn = [&] {
272  t5_5.cancelTimeout();
273  // Reset our function so we won't continually reschedule ourself
274  auto fn = std::move(t5_3.fn);
275  ts5.scheduleTimeout(&t5_3);
276 
277  // Also test cancelling timeouts in another timeset that isn't ready to
278  // fire yet.
279  //
280  // Cancel the middle timeout in ts10.
281  t10_2.cancelTimeout();
282  // Cancel both the timeouts in ts20.
283  t20_1.cancelTimeout();
284  t20_2.cancelTimeout();
285  };
286 
287  loop();
288 
289  ASSERT_EQ(t5_1.timestamps.size(), 1);
290  ASSERT_EQ(t5_1.timestamps[0], milliseconds(5));
291 
292  ASSERT_EQ(t5_3.timestamps.size(), 2);
293  ASSERT_EQ(t5_3.timestamps[0], milliseconds(5));
294  ASSERT_EQ(t5_3.timestamps[1] - t5_3.timestamps[0], milliseconds(5));
295 
296  ASSERT_EQ(t10_1.timestamps.size(), 1);
297  ASSERT_EQ(t10_1.timestamps[0], milliseconds(10));
298 
299  ASSERT_EQ(t10_3.timestamps.size(), 1);
300  ASSERT_EQ(t10_3.timestamps[0], milliseconds(10));
301 
302  // Cancelled timeouts
303  ASSERT_EQ(t5_2.timestamps.size(), 0);
304  ASSERT_EQ(t5_4.timestamps.size(), 0);
305  ASSERT_EQ(t5_5.timestamps.size(), 0);
306  ASSERT_EQ(t10_2.timestamps.size(), 0);
307  ASSERT_EQ(t20_1.timestamps.size(), 0);
308  ASSERT_EQ(t20_2.timestamps.size(), 0);
309  ASSERT_EQ(timeoutClock_.millisecondsSinceEpoch(), milliseconds(10));
310 }
311 
312 /*
313  * Test destroying a AsyncTimeoutSet with timeouts outstanding
314  */
315 TEST_F(TimeoutTest, DestroyTimeoutSet) {
317  &timeoutManager_, milliseconds(5), milliseconds(0), &timeoutClock_));
319  &timeoutManager_, milliseconds(10), milliseconds(0), &timeoutClock_));
320 
321  TestTimeout t5_1(ts5.get());
322  TestTimeout t5_2(ts5.get());
323  TestTimeout t5_3(ts5.get());
324 
325  TestTimeout t10_1(ts10.get());
326  TestTimeout t10_2(ts10.get());
327 
328  // Have t5_1 destroy ts10
329  t5_1.fn = [&] { ts10.reset(); };
330  // Have t5_2 destroy ts5
331  // Note that this will call destroy() on ts5 inside ts5's timeoutExpired()
332  // method.
333  t5_2.fn = [&] { ts5.reset(); };
334 
335  loop();
336 
337  ASSERT_EQ(t5_1.timestamps.size(), 1);
338  ASSERT_EQ(t5_1.timestamps[0], milliseconds(5));
339  ASSERT_EQ(t5_2.timestamps.size(), 1);
340  ASSERT_EQ(t5_2.timestamps[0], milliseconds(5));
341 
342  ASSERT_EQ(t5_3.timestamps.size(), 0);
343  ASSERT_EQ(t10_1.timestamps.size(), 0);
344  ASSERT_EQ(t10_2.timestamps.size(), 0);
345  ASSERT_EQ(timeoutClock_.millisecondsSinceEpoch(), milliseconds(5));
346 }
347 
348 /*
349  * Test the atMostEveryN parameter, to ensure that the timeout does not fire
350  * too frequently.
351  */
352 TEST_F(TimeoutTest, AtMostEveryN) {
353  // Create a timeout set with a 25ms interval, to fire no more than once
354  // every 6ms.
355  milliseconds interval(25);
356  milliseconds atMostEveryN(6);
357  StackTimeoutSet ts25(&timeoutManager_, interval, atMostEveryN,
358  &timeoutClock_);
359 
360  // Create 60 timeouts to be added to ts25 at 1ms intervals.
361  uint32_t numTimeouts = 60;
362  std::vector<TestTimeout> timeouts(numTimeouts);
363 
364  // Create a scheduler timeout to add the timeouts 1ms apart.
365  // Note, these will start firing partway through scheduling them
366  for (uint32_t index = 0; index < numTimeouts; index++) {
367  setClock(milliseconds(index));
368  timeouts[index].timeoutExpired();
369  ts25.scheduleTimeout(&timeouts[index]);
370  }
371 
372  loop();
373 
374  // We scheduled timeouts 1ms apart, when the AsyncTimeoutSet is only allowed
375  // to wake up at most once every 3ms. It will therefore wake up every 3ms
376  // and fire groups of approximately 3 timeouts at a time.
377  //
378  // This is "approximately 3" since it may get slightly behind and fire 4 in
379  // one interval, etc. CHECK_TIMEOUT normally allows a few milliseconds of
380  // tolerance. We have to add the same into our checking algorithm here.
381  for (uint32_t idx = 0; idx < numTimeouts; ++idx) {
382  ASSERT_EQ(timeouts[idx].timestamps.size(), 2);
383 
384  auto scheduledTime = timeouts[idx].timestamps[0] + interval;
385  auto firedTime = timeouts[idx].timestamps[1];
386  // Assert that the timeout fired at roughly the right time.
387  // CHECK_TIMEOUT() normally has a tolerance of 5ms. Allow an additional
388  // atMostEveryN.
389  milliseconds tolerance = atMostEveryN;
390  ASSERT_GE(firedTime, scheduledTime);
391  ASSERT_LT(firedTime, scheduledTime + tolerance);
392 
393  // Assert that the difference between the previous timeout and now was
394  // either very small (fired in the same event loop), or larger than
395  // atMostEveryN.
396  if (idx == 0) {
397  // no previous value
398  continue;
399  }
400  auto prev = timeouts[idx - 1].timestamps[1];
401 
402  auto delta = firedTime - prev;
403  if (delta >= milliseconds(1)) {
404  ASSERT_GE(delta, atMostEveryN);
405  }
406  }
407  ASSERT_LE(timeoutClock_.millisecondsSinceEpoch(),
408  milliseconds(numTimeouts) + interval + atMostEveryN);
409 }
boost::container::flat_multimap< milliseconds, AsyncTimeout * > timeouts_
#define ASSERT_GE(val1, val2)
Definition: gtest.h:1972
void scheduleTimeout(Callback *callback)
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
TestTimeout(Args &&...args)
#define ASSERT_LT(val1, val2)
Definition: gtest.h:1968
MockTimeoutClock timeoutClock_
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
static MockTimeoutClock * clock_
#define ASSERT_LE(val1, val2)
Definition: gtest.h:1964
internal::ArgsMatcher< InnerMatcher > Args(const InnerMatcher &matcher)
requires E e noexcept(noexcept(s.error(std::move(e))))
struct event * getEvent()
Definition: AsyncTimeout.h:150
auto event_ref_flags(struct event *ev) -> decltype(std::ref(ev->ev_flags))
Definition: EventUtil.h:41
std::deque< TimePoint > timestamps
PolymorphicAction< internal::InvokeAction< FunctionImpl > > Invoke(FunctionImpl function_impl)
std::deque< milliseconds > timestamps
void SetUp() override
void setClock(milliseconds ms)
static void setTimeoutClock(MockTimeoutClock &clock)
void timeoutExpired() noexceptoverride
virtual void timeoutExpired() noexcept=0
void addTimeout(AsyncTimeoutSet *set)
void loop(int iters)
std::deque< AsyncTimeoutSet * > nextSets_
void addTimeout(AsyncTimeoutSet *set, Args &&...args)
#define EXPECT_CALL(obj, call)
const internal::AnythingMatcher _
TEST_F(HeaderTableTests, IndexTranslation)
MockTimeoutManager timeoutManager_
std::chrono::milliseconds millisecondsSinceEpoch()
Definition: Time.h:56
std::unique_ptr< AsyncTimeoutSet, Destructor > UniquePtr
std::function< void()> fn
internal::ReturnAction< R > Return(R value)
#define MOCK_METHOD0(m,...)