TestTimeout(HHWheelTimer* t, milliseconds timeout) {
t->scheduleTimeout(this, timeout);
}
- virtual void timeoutExpired() noexcept {
- timestamps.push_back(TimePoint());
+
+ void timeoutExpired() noexcept override {
+ timestamps.emplace_back();
+ if (fn) {
+ fn();
+ }
+ }
+
+ void callbackCanceled() noexcept override {
+ canceledTimestamps.emplace_back();
if (fn) {
fn();
}
}
std::deque<TimePoint> timestamps;
+ std::deque<TimePoint> canceledTimestamps;
std::function<void()> fn;
};
+
+class TestTimeoutDelayed : public TestTimeout {
+ protected:
+ std::chrono::milliseconds getCurTime() override {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now().time_since_epoch()) -
+ milliseconds(5);
+ }
+};
+
+struct HHWheelTimerTest : public ::testing::Test {
+ EventBase eventBase;
+};
+
/*
* Test firing some simple timeouts that are fired once and never rescheduled
*/
-TEST(HHWheelTimerTest, FireOnce) {
- EventBase eventBase;
+TEST_F(HHWheelTimerTest, FireOnce) {
StackWheelTimer t(&eventBase, milliseconds(1));
const HHWheelTimer::Callback* nullCallback = nullptr;
ASSERT_EQ(t.count(), 0);
- T_CHECK_TIMEOUT(start, t1.timestamps[0], 5);
- T_CHECK_TIMEOUT(start, t2.timestamps[0], 5);
- T_CHECK_TIMEOUT(start, t3.timestamps[0], 10);
- T_CHECK_TIMEOUT(start, end, 10);
+ T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(5));
+ T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(5));
+ T_CHECK_TIMEOUT(start, t3.timestamps[0], milliseconds(10));
+ T_CHECK_TIMEOUT(start, end, milliseconds(10));
+}
+
+/*
+ * Test scheduling a timeout from another timeout callback.
+ */
+TEST_F(HHWheelTimerTest, TestSchedulingWithinCallback) {
+ StackWheelTimer t(&eventBase, milliseconds(10));
+ const HHWheelTimer::Callback* nullCallback = nullptr;
+
+ TestTimeout t1;
+ // Delayed to simulate the steady_clock counter lagging
+ TestTimeoutDelayed t2;
+
+ t.scheduleTimeout(&t1, milliseconds(500));
+ t1.fn = [&] { t.scheduleTimeout(&t2, milliseconds(1)); };
+ // If t is in an inconsistent state, detachEventBase should fail.
+ t2.fn = [&] { t.detachEventBase(); };
+
+ ASSERT_EQ(t.count(), 1);
+
+ eventBase.loop();
+
+ ASSERT_EQ(t.count(), 0);
+ ASSERT_EQ(t1.timestamps.size(), 1);
+ ASSERT_EQ(t2.timestamps.size(), 1);
}
/*
* Test cancelling a timeout when it is scheduled to be fired right away.
*/
-TEST(HHWheelTimerTest, CancelTimeout) {
- EventBase eventBase;
+TEST_F(HHWheelTimerTest, CancelTimeout) {
StackWheelTimer t(&eventBase, milliseconds(1));
// Create several timeouts that will all fire in 5ms.
TimePoint end;
ASSERT_EQ(t5_1.timestamps.size(), 1);
- T_CHECK_TIMEOUT(start, t5_1.timestamps[0], 5);
+ T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
ASSERT_EQ(t5_3.timestamps.size(), 2);
- T_CHECK_TIMEOUT(start, t5_3.timestamps[0], 5);
- T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], 5);
+ T_CHECK_TIMEOUT(start, t5_3.timestamps[0], milliseconds(5));
+ T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], milliseconds(5));
ASSERT_EQ(t10_1.timestamps.size(), 1);
- T_CHECK_TIMEOUT(start, t10_1.timestamps[0], 10);
+ T_CHECK_TIMEOUT(start, t10_1.timestamps[0], milliseconds(10));
ASSERT_EQ(t10_3.timestamps.size(), 1);
- T_CHECK_TIMEOUT(start, t10_3.timestamps[0], 10);
+ T_CHECK_TIMEOUT(start, t10_3.timestamps[0], milliseconds(10));
// Cancelled timeouts
ASSERT_EQ(t5_2.timestamps.size(), 0);
ASSERT_EQ(t20_1.timestamps.size(), 0);
ASSERT_EQ(t20_2.timestamps.size(), 0);
- T_CHECK_TIMEOUT(start, end, 10);
+ T_CHECK_TIMEOUT(start, end, milliseconds(10));
}
/*
* Test destroying a HHWheelTimer with timeouts outstanding
*/
-TEST(HHWheelTimerTest, DestroyTimeoutSet) {
- EventBase eventBase;
+TEST_F(HHWheelTimerTest, DestroyTimeoutSet) {
HHWheelTimer::UniquePtr t(
new HHWheelTimer(&eventBase, milliseconds(1)));
TimePoint end;
ASSERT_EQ(t5_1.timestamps.size(), 1);
- T_CHECK_TIMEOUT(start, t5_1.timestamps[0], 5);
+ T_CHECK_TIMEOUT(start, t5_1.timestamps[0], milliseconds(5));
ASSERT_EQ(t5_2.timestamps.size(), 1);
- T_CHECK_TIMEOUT(start, t5_2.timestamps[0], 5);
+ T_CHECK_TIMEOUT(start, t5_2.timestamps[0], milliseconds(5));
ASSERT_EQ(t5_3.timestamps.size(), 0);
ASSERT_EQ(t10_1.timestamps.size(), 0);
ASSERT_EQ(t10_2.timestamps.size(), 0);
- T_CHECK_TIMEOUT(start, end, 5);
+ T_CHECK_TIMEOUT(start, end, milliseconds(5));
}
/*
* Test the tick interval parameter
*/
-TEST(HHWheelTimerTest, AtMostEveryN) {
- EventBase eventBase;
+TEST_F(HHWheelTimerTest, AtMostEveryN) {
// Create a timeout set with a 10ms interval, to fire no more than once
// every 3ms.
// T_CHECK_TIMEOUT() normally has a tolerance of 5ms. Allow an additional
// atMostEveryN.
milliseconds tolerance = milliseconds(5) + interval;
- T_CHECK_TIMEOUT(scheduledTime, firedTime, atMostEveryN.count(),
- tolerance.count());
+ T_CHECK_TIMEOUT(scheduledTime, firedTime, atMostEveryN, tolerance);
// Assert that the difference between the previous timeout and now was
// either very small (fired in the same event loop), or larger than
}
TimePoint prev(timeouts[idx - 1].timestamps[1]);
- milliseconds delta((firedTime.getTimeStart() - prev.getTimeEnd()) -
- (firedTime.getTimeWaiting() - prev.getTimeWaiting()));
+ auto delta = (firedTime.getTimeStart() - prev.getTimeEnd()) -
+ (firedTime.getTimeWaiting() - prev.getTimeWaiting());
if (delta > milliseconds(1)) {
- T_CHECK_TIMEOUT(prev, firedTime, atMostEveryN.count()); }
+ T_CHECK_TIMEOUT(prev, firedTime, atMostEveryN);
+ }
}
}
* Test an event loop that is blocking
*/
-TEST(HHWheelTimerTest, SlowLoop) {
- EventBase eventBase;
+TEST_F(HHWheelTimerTest, SlowLoop) {
StackWheelTimer t(&eventBase, milliseconds(1));
TestTimeout t1;
ASSERT_EQ(t.count(), 0);
// Check that the timeout was delayed by sleep
- T_CHECK_TIMEOUT(start, t1.timestamps[0], 15, 1);
- T_CHECK_TIMEOUT(start, end, 15, 1);
+ T_CHECK_TIMEOUT(start, t1.timestamps[0], milliseconds(15), milliseconds(1));
+ T_CHECK_TIMEOUT(start, end, milliseconds(15), milliseconds(1));
// Try it again, this time with catchup timing every loop
t.setCatchupEveryN(1);
ASSERT_EQ(t.count(), 0);
// Check that the timeout was NOT delayed by sleep
- T_CHECK_TIMEOUT(start2, t2.timestamps[0], 10, 1);
- T_CHECK_TIMEOUT(start2, end2, 10, 1);
+ T_CHECK_TIMEOUT(start2, t2.timestamps[0], milliseconds(10), milliseconds(1));
+ T_CHECK_TIMEOUT(start2, end2, milliseconds(10), milliseconds(1));
+}
+
+/*
+ * Test scheduling a mix of timers with default timeout and variable timeout.
+ */
+TEST_F(HHWheelTimerTest, DefaultTimeout) {
+ milliseconds defaultTimeout(milliseconds(5));
+ StackWheelTimer t(&eventBase,
+ milliseconds(1),
+ AsyncTimeout::InternalEnum::NORMAL,
+ defaultTimeout);
+
+ TestTimeout t1;
+ TestTimeout t2;
+
+ ASSERT_EQ(t.count(), 0);
+ ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
+
+ t.scheduleTimeout(&t1);
+ t.scheduleTimeout(&t2, milliseconds(10));
+
+ ASSERT_EQ(t.count(), 2);
+
+ TimePoint start;
+ eventBase.loop();
+ TimePoint end;
+
+ ASSERT_EQ(t1.timestamps.size(), 1);
+ ASSERT_EQ(t2.timestamps.size(), 1);
+
+ ASSERT_EQ(t.count(), 0);
+
+ T_CHECK_TIMEOUT(start, t1.timestamps[0], defaultTimeout);
+ T_CHECK_TIMEOUT(start, t2.timestamps[0], milliseconds(10));
+ T_CHECK_TIMEOUT(start, end, milliseconds(10));
+}
+
+TEST_F(HHWheelTimerTest, lambda) {
+ StackWheelTimer t(&eventBase, milliseconds(1));
+ size_t count = 0;
+ t.scheduleTimeoutFn([&]{ count++; }, milliseconds(1));
+ eventBase.loop();
+ EXPECT_EQ(1, count);
+}
+
+// shouldn't crash because we swallow and log the error (you'll have to look
+// at the console to confirm logging)
+TEST_F(HHWheelTimerTest, lambdaThrows) {
+ StackWheelTimer t(&eventBase, milliseconds(1));
+ t.scheduleTimeoutFn([&]{ throw std::runtime_error("expected"); },
+ milliseconds(1));
+ eventBase.loop();
+}
+
+TEST_F(HHWheelTimerTest, cancelAll) {
+ StackWheelTimer t(&eventBase);
+ TestTimeout tt;
+ t.scheduleTimeout(&tt, std::chrono::minutes(1));
+ EXPECT_EQ(1, t.cancelAll());
+ EXPECT_EQ(1, tt.canceledTimestamps.size());
}