Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/envoy/event/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ class Timer {
virtual void enableTimer(const std::chrono::milliseconds& ms,
const ScopeTrackedObject* object = nullptr) PURE;

/**
* Enable a pending high resolution timeout. If a timeout is already pending, it will be reset to
* the new timeout.
*
* @param us supplies the duration of the alarm in microseconds.
* @param object supplies an optional scope for the duration of the alarm.
*/
virtual void enableHRTimer(const std::chrono::microseconds& us,
const ScopeTrackedObject* object = nullptr) PURE;
/**
* Return whether the timer is currently armed.
*/
Expand Down
9 changes: 7 additions & 2 deletions source/common/event/timer_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Envoy {
namespace Event {

void TimerUtils::millisecondsToTimeval(const std::chrono::milliseconds& d, timeval& tv) {
void TimerUtils::microsecondsToTimeval(const std::chrono::microseconds& d, timeval& tv) {
std::chrono::seconds secs = std::chrono::duration_cast<std::chrono::seconds>(d);
std::chrono::microseconds usecs = std::chrono::duration_cast<std::chrono::microseconds>(d - secs);

Expand Down Expand Up @@ -38,12 +38,17 @@ TimerImpl::TimerImpl(Libevent::BasePtr& libevent, TimerCb cb, Dispatcher& dispat
void TimerImpl::disableTimer() { event_del(&raw_event_); }

void TimerImpl::enableTimer(const std::chrono::milliseconds& d, const ScopeTrackedObject* object) {
enableHRTimer(d, object);
}

void TimerImpl::enableHRTimer(const std::chrono::microseconds& d,
const ScopeTrackedObject* object = nullptr) {
object_ = object;
if (d.count() == 0) {
event_active(&raw_event_, EV_TIMEOUT, 0);
} else {
timeval tv;
TimerUtils::millisecondsToTimeval(d, tv);
TimerUtils::microsecondsToTimeval(d, tv);
event_add(&raw_event_, &tv);
}
}
Expand Down
5 changes: 4 additions & 1 deletion source/common/event/timer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Event {
*/
class TimerUtils {
public:
static void millisecondsToTimeval(const std::chrono::milliseconds& d, timeval& tv);
static void microsecondsToTimeval(const std::chrono::microseconds& d, timeval& tv);
};

/**
Expand All @@ -29,6 +29,9 @@ class TimerImpl : public Timer, ImplBase {
// Timer
void disableTimer() override;
void enableTimer(const std::chrono::milliseconds& d, const ScopeTrackedObject* scope) override;
void enableHRTimer(const std::chrono::microseconds& us,
const ScopeTrackedObject* object) override;

bool enabled() override;

private:
Expand Down
1 change: 1 addition & 0 deletions test/common/event/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ envoy_cc_test(
"//source/common/stats:isolated_store_lib",
"//test/mocks:common_lib",
"//test/mocks/stats:stats_mocks",
"//test/test_common:simulated_time_system_lib",
"//test/test_common:utility_lib",
],
)
Expand Down
117 changes: 86 additions & 31 deletions test/common/event/dispatcher_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "test/mocks/common.h"
#include "test/mocks/stats/mocks.h"
#include "test/test_common/simulated_time_system.h"
#include "test/test_common/utility.h"

#include "gmock/gmock.h"
Expand Down Expand Up @@ -82,6 +83,33 @@ class DispatcherImplTest : public testing::Test {
dispatcher_thread_->join();
}

void timerTest(std::function<void(Timer&)> enable_timer_delegate) {
TimerPtr timer;
dispatcher_->post([this, &timer]() {
{
Thread::LockGuard lock(mu_);
timer = dispatcher_->createTimer([this]() {
{
Thread::LockGuard lock(mu_);
work_finished_ = true;
}
cv_.notifyOne();
});
EXPECT_FALSE(timer->enabled());
}
cv_.notifyOne();
});

Thread::LockGuard lock(mu_);
while (timer == nullptr) {
cv_.wait(mu_);
}
enable_timer_delegate(*timer);
while (!work_finished_) {
cv_.wait(mu_);
}
}

NiceMock<Stats::MockStore> scope_; // Used in InitializeStats, must outlive dispatcher_->exit().
Api::ApiPtr api_;
Thread::ThreadPtr dispatcher_thread_;
Expand Down Expand Up @@ -158,31 +186,8 @@ TEST_F(DispatcherImplTest, RunPostCallbacksLocking) {
}

TEST_F(DispatcherImplTest, Timer) {
TimerPtr timer;
dispatcher_->post([this, &timer]() {
{
Thread::LockGuard lock(mu_);
timer = dispatcher_->createTimer([this]() {
{
Thread::LockGuard lock(mu_);
work_finished_ = true;
}
cv_.notifyOne();
});
EXPECT_FALSE(timer->enabled());
}
cv_.notifyOne();
});

Thread::LockGuard lock(mu_);
while (timer == nullptr) {
cv_.wait(mu_);
}
timer->enableTimer(std::chrono::milliseconds(50));

while (!work_finished_) {
cv_.wait(mu_);
}
timerTest([](Timer& timer) { timer.enableTimer(std::chrono::milliseconds(50)); });
timerTest([](Timer& timer) { timer.enableHRTimer(std::chrono::microseconds(50)); });
}

TEST_F(DispatcherImplTest, TimerWithScope) {
Expand Down Expand Up @@ -305,6 +310,56 @@ TEST(TimerImplTest, TimerEnabledDisabled) {
EXPECT_TRUE(timer->enabled());
dispatcher->run(Dispatcher::RunType::NonBlock);
EXPECT_FALSE(timer->enabled());
timer->enableHRTimer(std::chrono::milliseconds(0));
EXPECT_TRUE(timer->enabled());
dispatcher->run(Dispatcher::RunType::NonBlock);
EXPECT_FALSE(timer->enabled());
}

class TimerImplTimingTest : public testing::Test {
public:
std::chrono::nanoseconds getTimerTiming(Event::SimulatedTimeSystem& time_system,
Dispatcher& dispatcher, Event::Timer& timer) {
const auto start = time_system.monotonicTime();
EXPECT_TRUE(timer.enabled());
while (true) {
dispatcher.run(Dispatcher::RunType::NonBlock);
if (timer.enabled()) {
time_system.sleep(std::chrono::microseconds(1));
} else {
break;
}
}
return time_system.monotonicTime() - start;
}
};

// Test the timer with a series of timings and measure they fire accurately
// using simulated time. enableTimer() should be precise at the millisecond
// level, whereas enableHRTimer should be precise at the microsecond level.
// For good measure, also check that '0'/immediate does what it says on the tin.
TEST_F(TimerImplTimingTest, TheoreticalTimerTiming) {
Event::SimulatedTimeSystem time_system;
Api::ApiPtr api = Api::createApiForTest(time_system);
DispatcherPtr dispatcher(api->allocateDispatcher());
Event::TimerPtr timer = dispatcher->createTimer([&dispatcher] { dispatcher->exit(); });

const uint64_t timings[] = {0, 10, 50, 1234};
for (const uint64_t timing : timings) {
std::chrono::milliseconds ms(timing);
timer->enableTimer(ms);
EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(
getTimerTiming(time_system, *dispatcher, *timer))
.count(),
timing);

std::chrono::microseconds us(timing);
timer->enableHRTimer(us);
EXPECT_EQ(std::chrono::duration_cast<std::chrono::microseconds>(
getTimerTiming(time_system, *dispatcher, *timer))
.count(),
timing);
}
}

TEST(TimerImplTest, TimerValueConversion) {
Expand All @@ -313,21 +368,21 @@ TEST(TimerImplTest, TimerValueConversion) {

// Basic test with zero milliseconds.
msecs = std::chrono::milliseconds(0);
TimerUtils::millisecondsToTimeval(msecs, tv);
TimerUtils::microsecondsToTimeval(msecs, tv);
EXPECT_EQ(tv.tv_sec, 0);
EXPECT_EQ(tv.tv_usec, 0);

// 2050 milliseconds is 2 seconds and 50000 microseconds.
msecs = std::chrono::milliseconds(2050);
TimerUtils::millisecondsToTimeval(msecs, tv);
TimerUtils::microsecondsToTimeval(msecs, tv);
EXPECT_EQ(tv.tv_sec, 2);
EXPECT_EQ(tv.tv_usec, 50000);

// Check maximum value conversion.
msecs = std::chrono::milliseconds::duration::max();
TimerUtils::millisecondsToTimeval(msecs, tv);
EXPECT_EQ(tv.tv_sec, msecs.count() / 1000);
EXPECT_EQ(tv.tv_usec, (msecs.count() % tv.tv_sec) * 1000);
const auto usecs = std::chrono::microseconds::duration::max();
TimerUtils::microsecondsToTimeval(usecs, tv);
EXPECT_EQ(tv.tv_sec, usecs.count() / 1000000);
EXPECT_EQ(tv.tv_usec, usecs.count() % tv.tv_sec);
}

} // namespace
Expand Down
2 changes: 2 additions & 0 deletions test/mocks/event/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ class MockTimer : public Timer {
MOCK_METHOD0(disableTimer, void());
MOCK_METHOD2(enableTimer,
void(const std::chrono::milliseconds&, const ScopeTrackedObject* scope));
MOCK_METHOD2(enableHRTimer,
void(const std::chrono::microseconds&, const ScopeTrackedObject* scope));
MOCK_METHOD0(enabled, bool());

MockDispatcher* dispatcher_{};
Expand Down
12 changes: 8 additions & 4 deletions test/test_common/simulated_time_system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ class SimulatedTimeSystemHelper::Alarm : public Timer {
// Timer
void disableTimer() override;
void enableTimer(const std::chrono::milliseconds& duration,
const ScopeTrackedObject* scope) override;
const ScopeTrackedObject* scope) override {
enableHRTimer(duration, scope);
};
void enableHRTimer(const std::chrono::microseconds& duration,
const ScopeTrackedObject* scope) override;
bool enabled() override {
Thread::LockGuard lock(time_system_.mutex_);
return armed_ || base_timer_->enabled();
Expand Down Expand Up @@ -177,8 +181,8 @@ void SimulatedTimeSystemHelper::Alarm::Alarm::disableTimerLockHeld() {
}
}

void SimulatedTimeSystemHelper::Alarm::Alarm::enableTimer(const std::chrono::milliseconds& duration,
const ScopeTrackedObject* scope) {
void SimulatedTimeSystemHelper::Alarm::Alarm::enableHRTimer(
const std::chrono::microseconds& duration, const ScopeTrackedObject* scope) {
Thread::LockGuard lock(time_system_.mutex_);
disableTimerLockHeld();
armed_ = true;
Expand Down Expand Up @@ -287,7 +291,7 @@ int64_t SimulatedTimeSystemHelper::nextIndex() {
}

void SimulatedTimeSystemHelper::addAlarmLockHeld(
Alarm* alarm, const std::chrono::milliseconds& duration) NO_THREAD_SAFETY_ANALYSIS {
Alarm* alarm, const std::chrono::microseconds& duration) NO_THREAD_SAFETY_ANALYSIS {
ASSERT(&(alarm->timeSystem()) == this);
alarm->setTimeLockHeld(monotonic_time_ + duration);
alarms_.insert(alarm);
Expand Down
2 changes: 1 addition & 1 deletion test/test_common/simulated_time_system.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class SimulatedTimeSystemHelper : public TestTimeSystem {
int64_t nextIndex();

// Adds/removes an alarm.
void addAlarmLockHeld(Alarm*, const std::chrono::milliseconds& duration)
void addAlarmLockHeld(Alarm*, const std::chrono::microseconds& duration)
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void removeAlarmLockHeld(Alarm*) EXCLUSIVE_LOCKS_REQUIRED(mutex_);

Expand Down