HHWheelTimerTest.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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. #include <folly/io/async/HHWheelTimer.h>
  17. #include <folly/io/async/EventBase.h>
  18. #include <folly/io/async/test/UndelayedDestruction.h>
  19. #include <folly/io/async/test/Util.h>
  20. #include <folly/portability/GTest.h>
  21. using namespace folly;
  22. using std::chrono::milliseconds;
  23. typedef UndelayedDestruction<HHWheelTimer> StackWheelTimer;
  24. class TestTimeout : public HHWheelTimer::Callback {
  25. public:
  26. TestTimeout() {}
  27. TestTimeout(HHWheelTimer* t, milliseconds timeout) {
  28. t->scheduleTimeout(this, timeout);
  29. }
  30. void timeoutExpired() noexcept override {
  31. timestamps.emplace_back();
  32. if (fn) {
  33. fn();
  34. }
  35. }
  36. void callbackCanceled() noexcept override {
  37. canceledTimestamps.emplace_back();
  38. if (fn) {
  39. fn();
  40. }
  41. }
  42. std::deque<TimePoint> timestamps;
  43. std::deque<TimePoint> canceledTimestamps;
  44. std::function<void()> fn;
  45. };
  46. class TestTimeoutDelayed : public TestTimeout {
  47. protected:
  48. std::chrono::steady_clock::time_point getCurTime() override {
  49. return std::chrono::steady_clock::now() - milliseconds(5);
  50. }
  51. };
  52. struct HHWheelTimerTest : public ::testing::Test {
  53. EventBase eventBase;
  54. };
  55. /*
  56. * Test firing some simple timeouts that are fired once and never rescheduled
  57. */
  58. TEST_F(HHWheelTimerTest, FireOnce) {
  59. StackWheelTimer t(&eventBase, milliseconds(1));
  60. TestTimeout t1;
  61. TestTimeout t2;
  62. TestTimeout t3;
  63. 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));
  70. ASSERT_EQ(t.count(), 3);
  71. TimePoint start;
  72. eventBase.loop();
  73. TimePoint end;
  74. ASSERT_EQ(t1.timestamps.size(), 1);
  75. ASSERT_EQ(t2.timestamps.size(), 1);
  76. ASSERT_EQ(t3.timestamps.size(), 1);
  77. ASSERT_EQ(t.count(), 0);
  78. T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
  79. T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
  80. T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
  81. T_CHECK_TIMEOUT(start, end, milliseconds(10));
  82. }
  83. /*
  84. * Test scheduling a timeout from another timeout callback.
  85. */
  86. TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
  87. HHWheelTimer& t = eventBase.timer();
  88. TestTimeout t1;
  89. // Delayed to simulate the steady_clock counter lagging
  90. TestTimeoutDelayed t2;
  91. t.scheduleTimeout(&t1, milliseconds(500));
  92. t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
  93. // If t is in an inconsistent state, detachEventBase should fail.
  94. t2.fn = [&] { t.detachEventBase(); };
  95. ASSERT_EQ(t.count(), 1);
  96. eventBase.loop();
  97. ASSERT_EQ(t.count(), 0);
  98. ASSERT_EQ(t1.timestamps.size(), 1);
  99. ASSERT_EQ(t2.timestamps.size(), 1);
  100. }
  101. /*
  102. * Test changing default-timeout in timer.
  103. */
  104. TEST_F(HHWheelTimerTest, TestSetDefaultTimeout) {
  105. HHWheelTimer& t = eventBase.timer();
  106. t.setDefaultTimeout(milliseconds(1000));
  107. // verify: default-time has been modified
  108. ASSERT_EQ(t.getDefaultTimeout(), milliseconds(1000));
  109. }
  110. /*
  111. * Test cancelling a timeout when it is scheduled to be fired right away.
  112. */
  113. TEST_F(HHWheelTimerTest, CancelTimeout) {
  114. StackWheelTimer t(&eventBase, milliseconds(1));
  115. // Create several timeouts that will all fire in 5ms.
  116. TestTimeout t5_1(&t, milliseconds(5));
  117. TestTimeout t5_2(&t, milliseconds(5));
  118. TestTimeout t5_3(&t, milliseconds(5));
  119. TestTimeout t5_4(&t, milliseconds(5));
  120. TestTimeout t5_5(&t, milliseconds(5));
  121. // Also create a few timeouts to fire in 10ms
  122. TestTimeout t10_1(&t, milliseconds(10));
  123. TestTimeout t10_2(&t, milliseconds(10));
  124. TestTimeout t10_3(&t, milliseconds(10));
  125. TestTimeout t20_1(&t, milliseconds(20));
  126. TestTimeout t20_2(&t, milliseconds(20));
  127. // Have t5_1 cancel t5_2 and t5_4.
  128. //
  129. // Cancelling t5_2 will test cancelling a timeout that is at the head of the
  130. // list and ready to be fired.
  131. //
  132. // Cancelling t5_4 will test cancelling a timeout in the middle of the list
  133. t5_1.fn = [&] {
  134. t5_2.cancelTimeout();
  135. t5_4.cancelTimeout();
  136. };
  137. // Have t5_3 cancel t5_5.
  138. // This will test cancelling the last remaining timeout.
  139. //
  140. // Then have t5_3 reschedule itself.
  141. t5_3.fn = [&] {
  142. t5_5.cancelTimeout();
  143. // Reset our function so we won't continually reschedule ourself
  144. std::function<void()> fnDtorGuard;
  145. t5_3.fn.swap(fnDtorGuard);
  146. t.scheduleTimeout(&t5_3, milliseconds(5));
  147. // Also test cancelling timeouts in another timeset that isn't ready to
  148. // fire yet.
  149. //
  150. // Cancel the middle timeout in ts10.
  151. t10_2.cancelTimeout();
  152. // Cancel both the timeouts in ts20.
  153. t20_1.cancelTimeout();
  154. t20_2.cancelTimeout();
  155. };
  156. TimePoint start;
  157. eventBase.loop();
  158. TimePoint end;
  159. ASSERT_EQ(t5_1.timestamps.size(), 1);
  160. T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
  161. ASSERT_EQ(t5_3.timestamps.size(), 2);
  162. T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
  163. T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
  164. ASSERT_EQ(t10_1.timestamps.size(), 1);
  165. T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
  166. ASSERT_EQ(t10_3.timestamps.size(), 1);
  167. T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
  168. // Cancelled timeouts
  169. ASSERT_EQ(t5_2.timestamps.size(), 0);
  170. ASSERT_EQ(t5_4.timestamps.size(), 0);
  171. ASSERT_EQ(t5_5.timestamps.size(), 0);
  172. ASSERT_EQ(t10_2.timestamps.size(), 0);
  173. ASSERT_EQ(t20_1.timestamps.size(), 0);
  174. ASSERT_EQ(t20_2.timestamps.size(), 0);
  175. T_CHECK_TIMEOUT(start, end, milliseconds(10));
  176. }
  177. /*
  178. * Test destroying a HHWheelTimer with timeouts outstanding
  179. */
  180. TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
  181. HHWheelTimer::UniquePtr t(
  182. HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
  183. TestTimeout t5_1(t.get(), milliseconds(5));
  184. TestTimeout t5_2(t.get(), milliseconds(5));
  185. TestTimeout t5_3(t.get(), milliseconds(5));
  186. TestTimeout t10_1(t.get(), milliseconds(10));
  187. TestTimeout t10_2(t.get(), milliseconds(10));
  188. // Have t5_2 destroy t
  189. // Note that this will call destroy() inside t's timeoutExpired()
  190. // method.
  191. t5_2.fn = [&] {
  192. t5_3.cancelTimeout();
  193. t5_1.cancelTimeout();
  194. t10_1.cancelTimeout();
  195. t10_2.cancelTimeout();
  196. t.reset();
  197. };
  198. TimePoint start;
  199. eventBase.loop();
  200. TimePoint end;
  201. ASSERT_EQ(t5_1.timestamps.size(), 1);
  202. T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
  203. ASSERT_EQ(t5_2.timestamps.size(), 1);
  204. T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
  205. ASSERT_EQ(t5_3.timestamps.size(), 0);
  206. ASSERT_EQ(t10_1.timestamps.size(), 0);
  207. ASSERT_EQ(t10_2.timestamps.size(), 0);
  208. T_CHECK_TIMEOUT(start, end, milliseconds(5));
  209. }
  210. /*
  211. * Test an event scheduled before the last event fires on time
  212. */
  213. TEST_F(HHWheelTimerTest, SlowFast) {
  214. StackWheelTimer t(&eventBase, milliseconds(1));
  215. TestTimeout t1;
  216. TestTimeout t2;
  217. ASSERT_EQ(t.count(), 0);
  218. t.scheduleTimeout(&t1, milliseconds(10));
  219. t.scheduleTimeout(&t2, milliseconds(5));
  220. ASSERT_EQ(t.count(), 2);
  221. TimePoint start;
  222. eventBase.loop();
  223. TimePoint end;
  224. ASSERT_EQ(t1.timestamps.size(), 1);
  225. ASSERT_EQ(t2.timestamps.size(), 1);
  226. ASSERT_EQ(t.count(), 0);
  227. // Check that the timeout was delayed by sleep
  228. T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(10), milliseconds(1));
  229. T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5), milliseconds(1));
  230. }
  231. TEST_F(HHWheelTimerTest, ReschedTest) {
  232. StackWheelTimer t(&eventBase, milliseconds(1));
  233. TestTimeout t1;
  234. TestTimeout t2;
  235. ASSERT_EQ(t.count(), 0);
  236. t.scheduleTimeout(&t1, milliseconds(128));
  237. TimePoint start2;
  238. t1.fn = [&]() {
  239. t.scheduleTimeout(&t2, milliseconds(255)); // WHEEL_SIZE - 1
  240. start2.reset();
  241. ASSERT_EQ(t.count(), 1);
  242. };
  243. ASSERT_EQ(t.count(), 1);
  244. TimePoint start;
  245. eventBase.loop();
  246. TimePoint end;
  247. ASSERT_EQ(t1.timestamps.size(), 1);
  248. ASSERT_EQ(t2.timestamps.size(), 1);
  249. ASSERT_EQ(t.count(), 0);
  250. T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
  251. T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(255), milliseconds(1));
  252. }
  253. TEST_F(HHWheelTimerTest, DeleteWheelInTimeout) {
  254. auto t = HHWheelTimer::newTimer(&eventBase, milliseconds(1));
  255. TestTimeout t1;
  256. TestTimeout t2;
  257. TestTimeout t3;
  258. ASSERT_EQ(t->count(), 0);
  259. t->scheduleTimeout(&t1, milliseconds(128));
  260. t->scheduleTimeout(&t2, milliseconds(128));
  261. t->scheduleTimeout(&t3, milliseconds(128));
  262. t1.fn = [&]() { t2.cancelTimeout(); };
  263. t3.fn = [&]() { t.reset(); };
  264. ASSERT_EQ(t->count(), 3);
  265. TimePoint start;
  266. eventBase.loop();
  267. TimePoint end;
  268. ASSERT_EQ(t1.timestamps.size(), 1);
  269. ASSERT_EQ(t2.timestamps.size(), 0);
  270. T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(128), milliseconds(1));
  271. }
  272. /*
  273. * Test scheduling a mix of timers with default timeout and variable timeout.
  274. */
  275. TEST_F(HHWheelTimerTest, DefaultTimeout) {
  276. milliseconds defaultTimeout(milliseconds(5));
  277. StackWheelTimer t(
  278. &eventBase,
  279. milliseconds(1),
  280. AsyncTimeout::InternalEnum::NORMAL,
  281. defaultTimeout);
  282. TestTimeout t1;
  283. TestTimeout t2;
  284. ASSERT_EQ(t.count(), 0);
  285. ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
  286. t.scheduleTimeout(&t1);
  287. t.scheduleTimeout(&t2, milliseconds(10));
  288. ASSERT_EQ(t.count(), 2);
  289. TimePoint start;
  290. eventBase.loop();
  291. TimePoint end;
  292. ASSERT_EQ(t1.timestamps.size(), 1);
  293. ASSERT_EQ(t2.timestamps.size(), 1);
  294. ASSERT_EQ(t.count(), 0);
  295. T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
  296. T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
  297. T_CHECK_TIMEOUT(start, end, milliseconds(10));
  298. }
  299. TEST_F(HHWheelTimerTest, lambda) {
  300. StackWheelTimer t(&eventBase, milliseconds(1));
  301. size_t count = 0;
  302. t.scheduleTimeoutFn([&] { count++; }, milliseconds(1));
  303. eventBase.loop();
  304. EXPECT_EQ(1, count);
  305. }
  306. // shouldn't crash because we swallow and log the error (you'll have to look
  307. // at the console to confirm logging)
  308. TEST_F(HHWheelTimerTest, lambdaThrows) {
  309. StackWheelTimer t(&eventBase, milliseconds(1));
  310. t.scheduleTimeoutFn(
  311. [&] { throw std::runtime_error("expected"); }, milliseconds(1));
  312. eventBase.loop();
  313. }
  314. TEST_F(HHWheelTimerTest, cancelAll) {
  315. StackWheelTimer t(&eventBase, milliseconds(1));
  316. TestTimeout tt;
  317. t.scheduleTimeout(&tt, std::chrono::minutes(1));
  318. EXPECT_EQ(1, t.cancelAll());
  319. EXPECT_EQ(1, tt.canceledTimestamps.size());
  320. }
  321. TEST_F(HHWheelTimerTest, IntrusivePtr) {
  322. HHWheelTimer::UniquePtr t(
  323. HHWheelTimer::newTimer(&eventBase, milliseconds(1)));
  324. TestTimeout t1;
  325. TestTimeout t2;
  326. TestTimeout t3;
  327. ASSERT_EQ(t->count(), 0);
  328. t->scheduleTimeout(&t1, milliseconds(5));
  329. t->scheduleTimeout(&t2, milliseconds(5));
  330. DelayedDestruction::IntrusivePtr<HHWheelTimer> s(t);
  331. s->scheduleTimeout(&t3, milliseconds(10));
  332. ASSERT_EQ(t->count(), 3);
  333. // Kill the UniquePtr, but the SharedPtr keeps it alive
  334. t.reset();
  335. TimePoint start;
  336. eventBase.loop();
  337. TimePoint end;
  338. ASSERT_EQ(t1.timestamps.size(), 1);
  339. ASSERT_EQ(t2.timestamps.size(), 1);
  340. ASSERT_EQ(t3.timestamps.size(), 1);
  341. ASSERT_EQ(s->count(), 0);
  342. T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
  343. T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
  344. T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
  345. T_CHECK_TIMEOUT(start, end, milliseconds(10));
  346. }
  347. TEST_F(HHWheelTimerTest, GetTimeRemaining) {
  348. StackWheelTimer t(&eventBase, milliseconds(1));
  349. TestTimeout t1;
  350. // Not scheduled yet, time remaining should be zero
  351. ASSERT_EQ(t1.getTimeRemaining(), milliseconds(0));
  352. ASSERT_EQ(t.count(), 0);
  353. // Scheduled, time remaining should be less than or equal to the scheduled
  354. // timeout
  355. t.scheduleTimeout(&t1, milliseconds(10));
  356. ASSERT_LE(t1.getTimeRemaining(), milliseconds(10));
  357. TimePoint start;
  358. eventBase.loop();
  359. TimePoint end;
  360. // Expired and time remaining should be zero
  361. ASSERT_EQ(t1.getTimeRemaining(), milliseconds(0));
  362. ASSERT_EQ(t.count(), 0);
  363. T_CHECK_TIMEOUT(start, end, milliseconds(10));
  364. }