Commit ac744ab3 authored by Dan Melnic's avatar Dan Melnic Committed by Facebook Github Bot

Add timerfd based timers

Summary: Add timerfd based timers

Reviewed By: djwatson

Differential Revision: D13809490

fbshipit-source-id: 9ae70d9cc4d481245623efbc3e74ce5ff8c8f3ca
parent 9f5c7e1e
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/STTimerFDTimeoutManager.h>
#include <folly/io/async/EventUtil.h>
namespace folly {
// STTimerFDTimeoutManager
STTimerFDTimeoutManager::STTimerFDTimeoutManager(folly::EventBase* eventBase)
: TimerFD(eventBase), eventBase_(eventBase) {}
STTimerFDTimeoutManager::~STTimerFDTimeoutManager() {
cancel();
close();
}
void STTimerFDTimeoutManager::setActive(AsyncTimeout* obj, bool active) {
if (obj) {
struct event* ev = obj->getEvent();
if (active) {
event_ref_flags(ev) |= EVLIST_ACTIVE;
} else {
event_ref_flags(ev) &= ~EVLIST_ACTIVE;
}
}
}
void STTimerFDTimeoutManager::attachTimeoutManager(
AsyncTimeout* /*unused*/,
InternalEnum /*unused*/) {}
void STTimerFDTimeoutManager::detachTimeoutManager(AsyncTimeout* obj) {
cancelTimeout(obj);
}
bool STTimerFDTimeoutManager::scheduleTimeout(
AsyncTimeout* obj,
timeout_type timeout) {
timeout_type_high_res high_res_timeout(timeout);
return scheduleTimeoutHighRes(obj, high_res_timeout);
}
bool STTimerFDTimeoutManager::scheduleTimeoutHighRes(
AsyncTimeout* obj,
timeout_type_high_res timeout) {
CHECK(obj_ == nullptr || obj_ == obj)
<< "Scheduling multiple timeouts on a single timeout manager is not allowed!";
// no need to cancel - just reschedule
obj_ = obj;
setActive(obj, true);
schedule(timeout);
return true;
}
void STTimerFDTimeoutManager::cancelTimeout(AsyncTimeout* obj) {
if (obj == obj_) {
setActive(obj, false);
obj_ = nullptr;
cancel();
}
}
void STTimerFDTimeoutManager::bumpHandlingTime() {}
void STTimerFDTimeoutManager::onTimeout() noexcept {
if (obj_) {
auto* obj = obj_;
obj_ = nullptr;
setActive(obj, false);
obj->timeoutExpired();
}
}
} // namespace folly
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/experimental/TimerFD.h>
#include <folly/io/async/TimeoutManager.h>
namespace folly {
// single timeout timerfd based TimeoutManager
class STTimerFDTimeoutManager : public TimeoutManager, TimerFD {
public:
explicit STTimerFDTimeoutManager(folly::EventBase* eventBase);
~STTimerFDTimeoutManager() override;
/**
* Attaches/detaches TimeoutManager to AsyncTimeout
*/
void attachTimeoutManager(AsyncTimeout* obj, InternalEnum internal) final;
void detachTimeoutManager(AsyncTimeout* obj) final;
/**
* Schedules AsyncTimeout to fire after `timeout` milliseconds
*/
bool scheduleTimeout(AsyncTimeout* obj, timeout_type timeout) final;
/**
* Schedules AsyncTimeout to fire after `timeout` microseconds
*/
bool scheduleTimeoutHighRes(AsyncTimeout* obj, timeout_type_high_res timeout)
final;
/**
* Cancels the AsyncTimeout, if scheduled
*/
void cancelTimeout(AsyncTimeout* obj) final;
/**
* This is used to mark the beginning of a new loop cycle by the
* first handler fired within that cycle.
*/
void bumpHandlingTime() final;
/**
* Helper method to know whether we are running in the timeout manager
* thread
*/
bool isInTimeoutManagerThread() final {
return eventBase_->isInEventBaseThread();
}
// from TimerFD
void onTimeout() noexcept final;
private:
static void setActive(AsyncTimeout* obj, bool active);
folly::EventBase* eventBase_{nullptr};
AsyncTimeout* obj_{nullptr};
};
} // namespace folly
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/TimerFD.h>
#ifdef FOLLY_HAVE_TIMERFD
#include <folly/FileUtil.h>
#include <sys/timerfd.h>
#endif
namespace folly {
#ifdef FOLLY_HAVE_TIMERFD
// TimerFD
TimerFD::TimerFD(folly::EventBase* eventBase)
: TimerFD(eventBase, createTimerFd()) {}
TimerFD::TimerFD(folly::EventBase* eventBase, int fd)
: folly::EventHandler(eventBase, fd), fd_(fd) {
if (fd_ > 0) {
registerHandler(folly::EventHandler::READ | folly::EventHandler::PERSIST);
}
}
TimerFD::~TimerFD() {
cancel();
close();
}
void TimerFD::close() {
unregisterHandler();
if (fd_ > 0) {
changeHandlerFD(NetworkSocket());
::close(fd_);
fd_ = -1;
}
}
void TimerFD::schedule(std::chrono::microseconds timeout) {
// schedule(0) will stop the timer otherwise
setTimer(timeout.count() ? timeout : std::chrono::microseconds(1));
}
void TimerFD::cancel() {
setTimer(std::chrono::microseconds(0));
}
bool TimerFD::setTimer(std::chrono::microseconds useconds) {
if (fd_ <= 0) {
return false;
}
struct itimerspec val;
val.it_interval = {0, 0};
val.it_value.tv_sec =
std::chrono::duration_cast<std::chrono::seconds>(useconds).count();
val.it_value.tv_nsec =
std::chrono::duration_cast<std::chrono::nanoseconds>(useconds).count() %
1000000000LL;
return (0 == ::timerfd_settime(fd_, 0, &val, nullptr));
}
void TimerFD::handlerReady(uint16_t events) noexcept {
DestructorGuard dg(this);
uint16_t relevantEvents = uint16_t(events & folly::EventHandler::READ_WRITE);
if (relevantEvents == folly::EventHandler::READ ||
relevantEvents == folly::EventHandler::READ_WRITE) {
uint64_t data = 0;
ssize_t num = folly::readNoInt(fd_, &data, sizeof(data));
if (num == sizeof(data)) {
onTimeout();
}
}
}
int TimerFD::createTimerFd() {
return ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
}
#else
TimerFD::TimerFD(folly::EventBase* eventBase) : timeout_(eventBase, this) {}
TimerFD::~TimerFD() {
// cancel has to be called from the derived classes !!!
}
void TimerFD::schedule(std::chrono::microseconds timeout) {
timeout_.scheduleTimeoutHighRes(timeout);
}
void TimerFD::cancel() {
timeout_.cancelTimeout();
}
TimerFD::TimerFDAsyncTimeout::TimerFDAsyncTimeout(
folly::EventBase* eventBase,
TimerFD* timerFd)
: folly::AsyncTimeout(eventBase), timerFd_(timerFd) {}
void TimerFD::TimerFDAsyncTimeout::timeoutExpired() noexcept {
timerFd_->onTimeout();
}
#endif
} // namespace folly
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#if __linux__ && !__ANDROID__
#define FOLLY_HAVE_TIMERFD
#endif
#include <folly/io/async/EventBase.h>
#ifdef FOLLY_HAVE_TIMERFD
#include <folly/io/async/EventHandler.h>
#else
#include <folly/io/async/AsyncTimeout.h>
#endif
#include <chrono>
namespace folly {
#ifdef FOLLY_HAVE_TIMERFD
// timerfd wrapper
class TimerFD : public folly::EventHandler, public DelayedDestruction {
public:
explicit TimerFD(folly::EventBase* eventBase);
~TimerFD() override;
virtual void onTimeout() noexcept = 0;
void schedule(std::chrono::microseconds timeout);
void cancel();
// from folly::EventHandler
void handlerReady(uint16_t events) noexcept override;
protected:
void close();
private:
TimerFD(folly::EventBase* eventBase, int fd);
static int createTimerFd();
// use 0 to stop the timer
bool setTimer(std::chrono::microseconds useconds);
int fd_{-1};
};
#else
// alternative implementation using a folly::AsyncTimeout
class TimerFD {
public:
explicit TimerFD(folly::EventBase* eventBase);
virtual ~TimerFD();
virtual void onTimeout() = 0;
void schedule(std::chrono::microseconds timeout);
void cancel();
protected:
void close() {}
private:
class TimerFDAsyncTimeout : public folly::AsyncTimeout {
public:
TimerFDAsyncTimeout(folly::EventBase* eventBase, TimerFD* timerFd);
~TimerFDAsyncTimeout() override = default;
// from folly::AsyncTimeout
void timeoutExpired() noexcept final;
private:
TimerFD* timerFd_;
};
TimerFDAsyncTimeout timeout_;
};
#endif
} // namespace folly
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/TimerFDTimeoutManager.h>
namespace folly {
// TimerFDTimeoutManager
TimerFDTimeoutManager::TimerFDTimeoutManager(folly::EventBase* eventBase)
: TimerFD(eventBase) {}
TimerFDTimeoutManager::~TimerFDTimeoutManager() {
cancelAll();
close();
}
void TimerFDTimeoutManager::onTimeout() noexcept {
processExpiredTimers();
scheduleNextTimer();
}
void TimerFDTimeoutManager::scheduleTimeout(
Callback* callback,
std::chrono::microseconds timeout) {
cancelTimeout(callback);
// we cannot schedule a timeout of 0 - this will stop the timer
if (FOLLY_UNLIKELY(!timeout.count())) {
timeout = std::chrono::microseconds(1);
}
auto expirationTime = getCurTime() + timeout;
auto expirationTimeUsec =
std::chrono::duration_cast<std::chrono::microseconds>(
expirationTime.time_since_epoch());
if (callbacks_.empty() || expirationTimeUsec < callbacks_.begin()->first) {
schedule(timeout);
}
// now add the callback
// handle entries that expire at the same time
auto iter = callbacks_.find(expirationTimeUsec);
if (iter != callbacks_.end()) {
iter->second.push_back(*callback);
} else {
CallbackList list;
list.push_back(*callback);
callbacks_.emplace(expirationTimeUsec, std::move(list));
}
callback->setExpirationTime(this, expirationTimeUsec);
}
bool TimerFDTimeoutManager::cancelTimeout(Callback* callback) {
if (!callback->is_linked()) {
return false;
}
callback->unlink();
callback->callbackCanceled();
auto expirationTime = callback->getExpirationTime();
auto iter = callbacks_.find(expirationTime);
if (iter == callbacks_.end()) {
return false;
}
bool removeFirst = (iter == callbacks_.begin());
if (iter->second.empty()) {
callbacks_.erase(iter);
}
// reschedule the timer if needed
if (!processingExpired_ && removeFirst && !callbacks_.empty()) {
auto now = std::chrono::duration_cast<std::chrono::microseconds>(
getCurTime().time_since_epoch());
if (now > callbacks_.begin()->first) {
auto timeout = now - callbacks_.begin()->first;
schedule(timeout);
}
}
if (callbacks_.empty()) {
cancel();
}
return true;
}
size_t TimerFDTimeoutManager::cancelAll() {
size_t ret = 0;
while (!callbacks_.empty()) {
auto iter = callbacks_.begin();
auto callbackList = std::move(iter->second);
callbacks_.erase(iter);
while (!callbackList.empty()) {
++ret;
auto* callback = &callbackList.front();
callbackList.pop_front();
callback->callbackCanceled();
}
}
// and now the in progress list
while (!inProgressList_.empty()) {
++ret;
auto* callback = &inProgressList_.front();
inProgressList_.pop_front();
callback->callbackCanceled();
}
if (ret) {
cancel();
}
return ret;
}
size_t TimerFDTimeoutManager::count() const {
size_t ret = 0;
for (const auto& c : callbacks_) {
ret += c.second.size();
}
return ret;
}
void TimerFDTimeoutManager::processExpiredTimers() {
processingExpired_ = true;
while (true) {
if (callbacks_.empty()) {
break;
}
auto iter = callbacks_.begin();
auto now = std::chrono::duration_cast<std::chrono::microseconds>(
getCurTime().time_since_epoch());
if (now >= iter->first) {
inProgressList_ = std::move(iter->second);
callbacks_.erase(iter);
CHECK(!inProgressList_.empty());
while (!inProgressList_.empty()) {
auto* callback = &inProgressList_.front();
inProgressList_.pop_front();
callback->timeoutExpired();
}
} else {
break;
}
}
processingExpired_ = false;
}
void TimerFDTimeoutManager::scheduleNextTimer() {
if (callbacks_.empty()) {
return;
}
auto iter = callbacks_.begin();
auto now = std::chrono::duration_cast<std::chrono::microseconds>(
getCurTime().time_since_epoch());
if (iter->first > now) {
schedule(iter->first - now);
} else {
// we schedule it here again to avoid the case
// where a timer can cause starvation
// by continuosly rescheduling itlsef
schedule(std::chrono::microseconds(1));
}
}
} // namespace folly
/*
* Copyright 2019-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/experimental/TimerFD.h>
#include <folly/io/async/DelayedDestruction.h>
#include <map>
namespace folly {
// generic TimerFD based timeout manager
class TimerFDTimeoutManager : public TimerFD {
public:
using UniquePtr =
std::unique_ptr<TimerFDTimeoutManager, DelayedDestruction::Destructor>;
using SharedPtr = std::shared_ptr<TimerFDTimeoutManager>;
public:
class Callback
: public boost::intrusive::list_base_hook<
boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
public:
Callback() = default;
explicit Callback(TimerFDTimeoutManager* mgr) : mgr_(mgr) {}
virtual ~Callback() = default;
virtual void timeoutExpired() noexcept = 0;
virtual void callbackCanceled() noexcept {
timeoutExpired();
}
const std::chrono::microseconds& getExpirationTime() const {
return expirationTime_;
}
void setExpirationTime(
TimerFDTimeoutManager* mgr,
const std::chrono::microseconds& expirationTime) {
mgr_ = mgr;
expirationTime_ = expirationTime;
}
std::chrono::microseconds getTimeRemaining() const {
return getTimeRemaining(std::chrono::steady_clock::now());
}
std::chrono::microseconds getTimeRemaining(
std::chrono::steady_clock::time_point now) const {
auto nowMs = std::chrono::duration_cast<std::chrono::microseconds>(
now.time_since_epoch());
if (expirationTime_ > nowMs) {
return std::chrono::duration_cast<std::chrono::microseconds>(
expirationTime_ - nowMs);
}
return std::chrono::microseconds(0);
}
void scheduleTimeout(std::chrono::microseconds timeout) {
if (mgr_) {
mgr_->scheduleTimeout(this, timeout);
}
}
bool cancelTimeout() {
return mgr_->cancelTimeout(this);
}
private:
TimerFDTimeoutManager* mgr_{nullptr};
std::chrono::microseconds expirationTime_{0};
};
explicit TimerFDTimeoutManager(folly::EventBase* eventBase);
~TimerFDTimeoutManager() override;
// from TimerFD
void onTimeout() noexcept final;
size_t cancelAll();
void scheduleTimeout(Callback* callback, std::chrono::microseconds timeout);
bool cancelTimeout(Callback* callback);
template <class F>
void scheduleTimeoutFn(F fn, std::chrono::microseconds timeout) {
struct Wrapper : Callback {
explicit Wrapper(F f) : fn_(std::move(f)) {}
void timeoutExpired() noexcept override {
try {
fn_();
} catch (std::exception const& e) {
LOG(ERROR) << "HHWheelTimerBase timeout callback threw an exception: "
<< e.what();
} catch (...) {
LOG(ERROR)
<< "HHWheelTimerBase timeout callback threw a non-exception.";
}
delete this;
}
F fn_;
};
Wrapper* w = new Wrapper(std::move(fn));
scheduleTimeout(w, timeout);
}
size_t count() const;
private:
void processExpiredTimers();
void scheduleNextTimer();
std::chrono::steady_clock::time_point getCurTime() {
return std::chrono::steady_clock::now();
}
// we can attempt to schedule new entries while in processExpiredTimers
// we want to reschedule the timers once we're done with the processing
bool processingExpired_{false};
typedef boost::intrusive::
list<Callback, boost::intrusive::constant_time_size<false>>
CallbackList;
std::map<std::chrono::microseconds, CallbackList> callbacks_;
CallbackList inProgressList_;
};
} // namespace folly
/*
* Copyright 2018-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Benchmark.h>
#include <folly/experimental/STTimerFDTimeoutManager.h>
#include <folly/experimental/TimerFDTimeoutManager.h>
#include <folly/io/async/test/UndelayedDestruction.h>
using namespace folly;
using std::chrono::microseconds;
using std::chrono::milliseconds;
class TestTimeoutMs : public HHWheelTimer::Callback {
public:
TestTimeoutMs() = default;
~TestTimeoutMs() override = default;
void timeoutExpired() noexcept override {}
void callbackCanceled() noexcept override {}
};
typedef UndelayedDestruction<HHWheelTimer> StackWheelTimerMs;
class TestTimeoutUs : public HHWheelTimerHighRes::Callback {
public:
TestTimeoutUs() = default;
~TestTimeoutUs() override = default;
void timeoutExpired() noexcept override {}
void callbackCanceled() noexcept override {}
};
typedef UndelayedDestruction<HHWheelTimerHighRes> StackWheelTimerUs;
class TestTimeoutDirectUs : public TimerFDTimeoutManager::Callback {
public:
TestTimeoutDirectUs() = default;
~TestTimeoutDirectUs() override = default;
void timeoutExpired() noexcept override {}
void callbackCanceled() noexcept override {}
};
unsigned int scheduleCancelTimersMs(unsigned int iters, unsigned int timers) {
BenchmarkSuspender susp;
EventBase evb;
StackWheelTimerMs t(&evb, milliseconds(1));
std::vector<TestTimeoutMs> timeouts(timers);
susp.dismiss();
for (unsigned int i = 0; i < iters; ++i) {
for (unsigned int j = 0; j < timers; ++j) {
t.scheduleTimeout(&timeouts[j], milliseconds(5 * (j + 1)));
}
for (unsigned int j = 0; j < timers; ++j) {
timeouts[j].cancelTimeout();
}
}
susp.rehire();
return iters;
}
unsigned int scheduleCancelTimersUs(unsigned int iters, unsigned int timers) {
BenchmarkSuspender susp;
EventBase evb;
STTimerFDTimeoutManager timeoutMgr(&evb);
StackWheelTimerUs t(&timeoutMgr, microseconds(20));
std::vector<TestTimeoutUs> timeouts(timers);
susp.dismiss();
for (unsigned int i = 0; i < iters; ++i) {
for (unsigned int j = 0; j < timers; ++j) {
t.scheduleTimeout(&timeouts[j], microseconds(5000 * (j + 1)));
}
for (unsigned int j = 0; j < timers; ++j) {
timeouts[j].cancelTimeout();
}
}
susp.rehire();
return iters;
}
unsigned int scheduleCancelTimersDirectUs(
unsigned int iters,
unsigned int timers) {
BenchmarkSuspender susp;
EventBase evb;
TimerFDTimeoutManager t(&evb);
std::vector<TestTimeoutDirectUs> timeouts(timers);
susp.dismiss();
for (unsigned int i = 0; i < iters; ++i) {
for (unsigned int j = 0; j < timers; ++j) {
t.scheduleTimeout(&timeouts[j], microseconds(5000 * (j + 1)));
}
for (unsigned int j = 0; j < timers; ++j) {
timeouts[j].cancelTimeout();
}
}
susp.rehire();
return iters;
}
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_1, 1)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_1, 1)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_1,
1)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_16, 16)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_16, 16)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_16,
16)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_64, 64)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_64, 64)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_64,
64)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_128, 128)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_128, 128)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_128,
128)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_512, 512)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_512, 512)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_512,
512)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_1024, 1024)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_1024, 1024)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_1024,
1024)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_4096, 4096)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_4096, 4096)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_4096,
4096)
BENCHMARK_DRAW_LINE();
BENCHMARK_NAMED_PARAM_MULTI(scheduleCancelTimersMs, ms_8192, 9182)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(scheduleCancelTimersUs, us_8192, 9182)
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(
scheduleCancelTimersDirectUs,
direct_us_8192,
9182)
BENCHMARK_DRAW_LINE();
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
folly::runBenchmarks();
return 0;
}
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/STTimerFDTimeoutManager.h>
#include <folly/io/async/HHWheelTimer.h>
#include <folly/io/async/test/UndelayedDestruction.h>
#include <folly/io/async/test/Util.h>
#include <folly/portability/GTest.h>
using namespace folly;
using std::chrono::microseconds;
typedef UndelayedDestruction<HHWheelTimerHighRes> StackWheelTimer;
class TestTimeout : public HHWheelTimerHighRes::Callback {
public:
TestTimeout() {}
TestTimeout(HHWheelTimerHighRes* t, microseconds timeout) {
t->scheduleTimeout(this, timeout);
}
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;
};
struct HHWheelTimerHighResTest : public ::testing::Test {
HHWheelTimerHighResTest() : timeoutMgr(&evb) {}
EventBase evb;
STTimerFDTimeoutManager timeoutMgr;
};
/*
* Test firing some simple timeouts that are fired once and never rescheduled
*/
TEST_F(HHWheelTimerHighResTest, FireOnce) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout t1;
TestTimeout t2;
TestTimeout t3;
ASSERT_EQ(t.count(), 0);
t.scheduleTimeout(&t1, microseconds(50));
t.scheduleTimeout(&t2, microseconds(50));
// Verify scheduling it twice cancels, then schedules.
// Should only get one callback.
t.scheduleTimeout(&t2, microseconds(50));
t.scheduleTimeout(&t3, microseconds(100));
ASSERT_EQ(t.count(), 3);
TestTimeout ts;
ts.fn = [&]() { evb.terminateLoopSoon(); };
t.scheduleTimeout(&ts, microseconds(1000));
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 1);
ASSERT_EQ(t3.timestamps.size(), 1);
ASSERT_EQ(t.count(), 0);
T_CHECK_TIMEOUT(start, t1.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t2.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t3.timestamps[0], microseconds(10));
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
/*
* Test scheduling a timeout from another timeout callback.
*/
TEST_F(HHWheelTimerHighResTest, TestSchedulingWithinCallback) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout t1, t2;
t.scheduleTimeout(&t1, microseconds(500 * 1000));
t1.fn = [&] {
t.scheduleTimeout(&t2, microseconds(1000));
std::this_thread::sleep_for(std::chrono::microseconds(5000));
};
t2.fn = [&] { evb.terminateLoopSoon(); };
ASSERT_EQ(t.count(), 1);
evb.loop();
ASSERT_EQ(t.count(), 0);
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 1);
}
/*
* Test changing default-timeout in timer.
*/
TEST_F(HHWheelTimerHighResTest, TestSetDefaultTimeout) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
t.setDefaultTimeout(microseconds(1000));
// verify: default-time has been modified
ASSERT_EQ(t.getDefaultTimeout(), microseconds(1000));
}
/*
* Test cancelling a timeout when it is scheduled to be fired right away.
*/
TEST_F(HHWheelTimerHighResTest, CancelTimeout) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
// Create several timeouts that will all fire in 5microseconds
TestTimeout t5_1(&t, microseconds(50));
TestTimeout t5_2(&t, microseconds(50));
TestTimeout t5_3(&t, microseconds(50));
TestTimeout t5_4(&t, microseconds(50));
TestTimeout t5_5(&t, microseconds(50));
// Also create a few timeouts to fire in 10microseconds
TestTimeout t10_1(&t, microseconds(100));
TestTimeout t10_2(&t, microseconds(100));
TestTimeout t10_3(&t, microseconds(100));
TestTimeout t20_1(&t, microseconds(200));
TestTimeout t20_2(&t, microseconds(200));
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
// Have t5_1 cancel t5_2 and t5_4.
//
// Cancelling t5_2 will test cancelling a timeout that is at the head of the
// list and ready to be fired.
//
// Cancelling t5_4 will test cancelling a timeout in the middle of the list
t5_1.fn = [&] {
t5_2.cancelTimeout();
t5_4.cancelTimeout();
};
// Have t5_3 cancel t5_5.
// This will test cancelling the last remaining timeout.
//
// Then have t5_3 reschedule itself.
t5_3.fn = [&] {
t5_5.cancelTimeout();
// Reset our function so we won't continually reschedule ourself
std::function<void()> fnDtorGuard;
t5_3.fn.swap(fnDtorGuard);
t.scheduleTimeout(&t5_3, microseconds(500));
// Also test cancelling timeouts in another timeset that isn't ready to
// fire yet.
//
// Cancel the middle timeout in ts10.
t10_2.cancelTimeout();
// Cancel both the timeouts in ts20.
t20_1.cancelTimeout();
t20_2.cancelTimeout();
};
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t5_1.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t5_1.timestamps[0], microseconds(500));
ASSERT_EQ(t5_3.timestamps.size(), 2);
T_CHECK_TIMEOUT(start, t5_3.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], microseconds(500));
ASSERT_EQ(t10_1.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t10_1.timestamps[0], microseconds(10));
ASSERT_EQ(t10_3.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t10_3.timestamps[0], microseconds(10));
// Cancelled timeouts
ASSERT_EQ(t5_2.timestamps.size(), 0);
ASSERT_EQ(t5_4.timestamps.size(), 0);
ASSERT_EQ(t5_5.timestamps.size(), 0);
ASSERT_EQ(t10_2.timestamps.size(), 0);
ASSERT_EQ(t20_1.timestamps.size(), 0);
ASSERT_EQ(t20_2.timestamps.size(), 0);
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
/*
* Test destroying a HHWheelTimerHighRes with timeouts outstanding
*/
TEST_F(HHWheelTimerHighResTest, DestroyTimeoutSet) {
HHWheelTimerHighRes::UniquePtr t(
HHWheelTimerHighRes::newTimer(&timeoutMgr, microseconds(100)));
TestTimeout t5_1(t.get(), microseconds(50));
TestTimeout t5_2(t.get(), microseconds(50));
TestTimeout t5_3(t.get(), microseconds(50));
TestTimeout t10_1(t.get(), microseconds(100));
TestTimeout t10_2(t.get(), microseconds(100));
// Have t5_2 destroy t
// Note that this will call destroy() inside t's timeoutExpired()
// method.
t5_2.fn = [&] {
t5_3.cancelTimeout();
t5_1.cancelTimeout();
t10_1.cancelTimeout();
t10_2.cancelTimeout();
t.reset();
evb.terminateLoopSoon();
};
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t5_1.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t5_1.timestamps[0], microseconds(500));
ASSERT_EQ(t5_2.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t5_2.timestamps[0], microseconds(500));
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, microseconds(500));
}
/*
* Test an event scheduled before the last event fires on time
*/
TEST_F(HHWheelTimerHighResTest, SlowFast) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout t1;
TestTimeout t2;
ASSERT_EQ(t.count(), 0);
t.scheduleTimeout(&t1, microseconds(100));
t.scheduleTimeout(&t2, microseconds(50));
ASSERT_EQ(t.count(), 2);
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.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], microseconds(100));
T_CHECK_TIMEOUT(start, t2.timestamps[0], microseconds(50));
}
TEST_F(HHWheelTimerHighResTest, ReschedTest) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout t1;
TestTimeout t2;
ASSERT_EQ(t.count(), 0);
t.scheduleTimeout(&t1, microseconds(12800));
TimePoint start2;
t1.fn = [&]() {
t.scheduleTimeout(&t2, microseconds(25500));
start2.reset();
ASSERT_EQ(t.count(), 2); // we scheduled et
};
ASSERT_EQ(t.count(), 1);
TestTimeout et(&t, microseconds(100000));
et.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.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], microseconds(12800));
T_CHECK_TIMEOUT(start2, t2.timestamps[0], microseconds(25500));
}
TEST_F(HHWheelTimerHighResTest, DeleteWheelInTimeout) {
auto t = HHWheelTimerHighRes::newTimer(&timeoutMgr, microseconds(100));
TestTimeout t1;
TestTimeout t2;
TestTimeout t3;
ASSERT_EQ(t->count(), 0);
t->scheduleTimeout(&t1, microseconds(128));
t->scheduleTimeout(&t2, microseconds(128));
t->scheduleTimeout(&t3, microseconds(128));
t1.fn = [&]() { t2.cancelTimeout(); };
t3.fn = [&]() {
t.reset();
evb.terminateLoopSoon();
};
ASSERT_EQ(t->count(), 3);
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 0);
T_CHECK_TIMEOUT(start, t1.timestamps[0], microseconds(128));
}
/*
* Test scheduling a mix of timers with default timeout and variable timeout.
*/
TEST_F(HHWheelTimerHighResTest, DefaultTimeout) {
microseconds defaultTimeout(microseconds(500));
StackWheelTimer t(
&timeoutMgr,
microseconds(100),
AsyncTimeout::InternalEnum::NORMAL,
defaultTimeout);
TestTimeout t1;
TestTimeout t2;
ASSERT_EQ(t.count(), 0);
ASSERT_EQ(t.getDefaultTimeout(), defaultTimeout);
t.scheduleTimeout(&t1);
t.scheduleTimeout(&t2, microseconds(10));
ASSERT_EQ(t.count(), 2);
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.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], microseconds(10));
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
TEST_F(HHWheelTimerHighResTest, lambda) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
size_t count = 0;
t.scheduleTimeoutFn([&] { count++; }, microseconds(100));
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
evb.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(HHWheelTimerHighResTest, lambdaThrows) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
size_t count = 0;
t.scheduleTimeoutFn(
[&] {
count++;
throw std::runtime_error("expected");
},
microseconds(100));
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
evb.loop();
// make sure the callback was invoked
EXPECT_EQ(1, count);
}
TEST_F(HHWheelTimerHighResTest, cancelAll) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout t1;
TestTimeout t2;
t.scheduleTimeout(&t1, std::chrono::microseconds(1000));
t.scheduleTimeout(&t2, std::chrono::microseconds(1000));
size_t canceled = 0;
t1.fn = [&] { canceled += t.cancelAll(); };
t2.fn = [&] { canceled += t.cancelAll(); };
// Sleep 20ms to ensure both timeouts will fire in a single event (in case
// they ended up in different slots)
::usleep(20000);
evb.scheduleAt(
[&]() { evb.terminateLoopSoon(); },
std::chrono::steady_clock::now() + std::chrono::milliseconds(100));
evb.loop();
EXPECT_EQ(1, t1.canceledTimestamps.size() + t2.canceledTimestamps.size());
EXPECT_EQ(1, canceled);
}
TEST_F(HHWheelTimerHighResTest, IntrusivePtr) {
HHWheelTimerHighRes::UniquePtr t(
HHWheelTimerHighRes::newTimer(&timeoutMgr, microseconds(100)));
TestTimeout t1;
TestTimeout t2;
TestTimeout t3;
ASSERT_EQ(t->count(), 0);
t->scheduleTimeout(&t1, microseconds(500));
t->scheduleTimeout(&t2, microseconds(500));
DelayedDestruction::IntrusivePtr<HHWheelTimerHighRes> s(t);
s->scheduleTimeout(&t3, microseconds(10));
ASSERT_EQ(t->count(), 3);
// Kill the UniquePtr, but the SharedPtr keeps it alive
t.reset();
evb.scheduleAt(
[&]() { evb.terminateLoopSoon(); },
std::chrono::steady_clock::now() + std::chrono::milliseconds(10));
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 1);
ASSERT_EQ(t3.timestamps.size(), 1);
ASSERT_EQ(s->count(), 0);
T_CHECK_TIMEOUT(start, t1.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t2.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t3.timestamps[0], microseconds(10));
T_CHECK_TIMEOUT(start, end, std::chrono::milliseconds(10)); // this is right
}
TEST_F(HHWheelTimerHighResTest, GetTimeRemaining) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout t1;
// Not scheduled yet, time remaining should be zero
ASSERT_EQ(t1.getTimeRemaining(), microseconds(0));
ASSERT_EQ(t.count(), 0);
// Scheduled, time remaining should be less than or equal to the scheduled
// timeout
t.scheduleTimeout(&t1, microseconds(10));
ASSERT_LE(t1.getTimeRemaining(), microseconds(10));
t1.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.loop();
TimePoint end;
// Expired and time remaining should be zero
ASSERT_EQ(t1.getTimeRemaining(), microseconds(0));
ASSERT_EQ(t.count(), 0);
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
TEST_F(HHWheelTimerHighResTest, prematureTimeout) {
StackWheelTimer t(&timeoutMgr, microseconds(10));
TestTimeout t1;
TestTimeout t2;
// Schedule the timeout for the nextTick of timer
t.scheduleTimeout(&t1, std::chrono::microseconds(100));
// Make sure that time is past that tick.
::usleep(10000);
// Schedule the timeout for the +255 tick, due to sleep above it will overlap
// with what would be ran on the next timeoutExpired of the timer.
auto timeout = std::chrono::microseconds(2555);
t.scheduleTimeout(&t2, std::chrono::microseconds(2555));
t2.fn = [&] { evb.terminateLoopSoon(); };
auto start = std::chrono::steady_clock::now();
evb.loop();
ASSERT_EQ(t2.timestamps.size(), 1);
auto elapsedUs = std::chrono::duration_cast<std::chrono::microseconds>(
t2.timestamps[0].getTime() - start);
EXPECT_GE(elapsedUs.count(), timeout.count());
}
TEST_F(HHWheelTimerHighResTest, Level1) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
TestTimeout tt;
// Schedule the timeout for the tick in a next epoch.
t.scheduleTimeout(&tt, std::chrono::microseconds(500));
tt.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(tt.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, end, microseconds(500));
}
// Test that we handle negative timeouts properly (i.e. treat them as 0)
TEST_F(HHWheelTimerHighResTest, NegativeTimeout) {
StackWheelTimer t(&timeoutMgr, microseconds(100));
std::this_thread::sleep_for(std::chrono::microseconds(10));
TestTimeout tt1;
TestTimeout tt2;
// Make sure we have event scheduled.
t.scheduleTimeout(&tt1, std::chrono::microseconds(100));
// Schedule another timeout that would appear to be earlier than
// the already scheduled one.
t.scheduleTimeout(&tt2, std::chrono::microseconds(-500000000));
evb.scheduleAt(
[&]() { evb.terminateLoopSoon(); },
std::chrono::steady_clock::now() + std::chrono::milliseconds(1));
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(tt2.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, end, std::chrono::milliseconds(10));
}
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/TimerFDTimeoutManager.h>
#include <folly/io/async/test/Util.h>
#include <folly/portability/GTest.h>
using namespace folly;
using std::chrono::microseconds;
class TestTimeout : public TimerFDTimeoutManager::Callback {
public:
TestTimeout() {}
TestTimeout(TimerFDTimeoutManager* t, microseconds timeout) {
t->scheduleTimeout(this, timeout);
}
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;
};
struct TimerFDTimeoutManagerTest : public ::testing::Test {
TimerFDTimeoutManagerTest() : timeoutMgr(&evb) {}
EventBase evb;
TimerFDTimeoutManager timeoutMgr;
};
/*
* Test firing some simple timeouts that are fired once and never rescheduled
*/
TEST_F(TimerFDTimeoutManagerTest, FireOnce) {
TimerFDTimeoutManager& t = timeoutMgr;
TestTimeout t1;
TestTimeout t2;
TestTimeout t3;
ASSERT_EQ(t.count(), 0);
t.scheduleTimeout(&t1, microseconds(50));
t.scheduleTimeout(&t2, microseconds(50));
// Verify scheduling it twice cancels, then schedules.
// Should only get one callback.
t.scheduleTimeout(&t2, microseconds(50));
t.scheduleTimeout(&t3, microseconds(100));
ASSERT_EQ(t.count(), 3);
TestTimeout ts;
ts.fn = [&]() { evb.terminateLoopSoon(); };
t.scheduleTimeout(&ts, microseconds(1000));
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 1);
ASSERT_EQ(t3.timestamps.size(), 1);
ASSERT_EQ(t.count(), 0);
T_CHECK_TIMEOUT(start, t1.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t2.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t3.timestamps[0], microseconds(10));
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
/*
* Test scheduling a timeout from another timeout callback.
*/
TEST_F(TimerFDTimeoutManagerTest, TestSchedulingWithinCallback) {
TimerFDTimeoutManager& t = timeoutMgr;
TestTimeout t1, t2;
t.scheduleTimeout(&t1, microseconds(500 * 1000));
t1.fn = [&] {
t.scheduleTimeout(&t2, microseconds(1000));
std::this_thread::sleep_for(std::chrono::microseconds(5000));
};
t2.fn = [&] { evb.terminateLoopSoon(); };
ASSERT_EQ(t.count(), 1);
evb.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_F(TimerFDTimeoutManagerTest, CancelTimeout) {
TimerFDTimeoutManager& t = timeoutMgr;
// Create several timeouts that will all fire in 5microseconds
TestTimeout t5_1(&t, microseconds(50));
TestTimeout t5_2(&t, microseconds(50));
TestTimeout t5_3(&t, microseconds(50));
TestTimeout t5_4(&t, microseconds(50));
TestTimeout t5_5(&t, microseconds(50));
// Also create a few timeouts to fire in 10microseconds
TestTimeout t10_1(&t, microseconds(100));
TestTimeout t10_2(&t, microseconds(100));
TestTimeout t10_3(&t, microseconds(100));
TestTimeout t20_1(&t, microseconds(200));
TestTimeout t20_2(&t, microseconds(200));
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
// Have t5_1 cancel t5_2 and t5_4.
//
// Cancelling t5_2 will test cancelling a timeout that is at the head of the
// list and ready to be fired.
//
// Cancelling t5_4 will test cancelling a timeout in the middle of the list
t5_1.fn = [&] {
t5_2.cancelTimeout();
t5_4.cancelTimeout();
};
// Have t5_3 cancel t5_5.
// This will test cancelling the last remaining timeout.
//
// Then have t5_3 reschedule itself.
t5_3.fn = [&] {
t5_5.cancelTimeout();
// Reset our function so we won't continually reschedule ourself
std::function<void()> fnDtorGuard;
t5_3.fn.swap(fnDtorGuard);
t.scheduleTimeout(&t5_3, microseconds(500));
// Also test cancelling timeouts in another timeset that isn't ready to
// fire yet.
//
// Cancel the middle timeout in ts10.
t10_2.cancelTimeout();
// Cancel both the timeouts in ts20.
t20_1.cancelTimeout();
t20_2.cancelTimeout();
};
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t5_1.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t5_1.timestamps[0], microseconds(500));
ASSERT_EQ(t5_3.timestamps.size(), 2);
T_CHECK_TIMEOUT(start, t5_3.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(t5_3.timestamps[0], t5_3.timestamps[1], microseconds(500));
ASSERT_EQ(t10_1.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t10_1.timestamps[0], microseconds(10));
ASSERT_EQ(t10_3.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t10_3.timestamps[0], microseconds(10));
// Cancelled timeouts
ASSERT_EQ(t5_2.timestamps.size(), 0);
ASSERT_EQ(t5_4.timestamps.size(), 0);
ASSERT_EQ(t5_5.timestamps.size(), 0);
ASSERT_EQ(t10_2.timestamps.size(), 0);
ASSERT_EQ(t20_1.timestamps.size(), 0);
ASSERT_EQ(t20_2.timestamps.size(), 0);
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
/*
* Test destroying a TimerFDTimeoutManager with timeouts outstanding
*/
TEST_F(TimerFDTimeoutManagerTest, DestroyTimeoutSet) {
TimerFDTimeoutManager::UniquePtr t(new TimerFDTimeoutManager(&evb));
TestTimeout t5_1(t.get(), microseconds(50));
TestTimeout t5_2(t.get(), microseconds(50));
TestTimeout t5_3(t.get(), microseconds(50));
TestTimeout t10_1(t.get(), microseconds(100));
TestTimeout t10_2(t.get(), microseconds(100));
// Have t5_2 destroy t
// Note that this will call destroy() inside t's timeoutExpired()
// method.
t5_2.fn = [&] {
t5_3.cancelTimeout();
t5_1.cancelTimeout();
t10_1.cancelTimeout();
t10_2.cancelTimeout();
t.reset();
evb.terminateLoopSoon();
};
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t5_1.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t5_1.timestamps[0], microseconds(500));
ASSERT_EQ(t5_2.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, t5_2.timestamps[0], microseconds(500));
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, microseconds(500));
}
/*
* Test an event scheduled before the last event fires on time
*/
TEST_F(TimerFDTimeoutManagerTest, SlowFast) {
TimerFDTimeoutManager& t = timeoutMgr;
TestTimeout t1;
TestTimeout t2;
ASSERT_EQ(t.count(), 0);
t.scheduleTimeout(&t1, microseconds(100));
t.scheduleTimeout(&t2, microseconds(50));
ASSERT_EQ(t.count(), 2);
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.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], microseconds(100));
T_CHECK_TIMEOUT(start, t2.timestamps[0], microseconds(50));
}
TEST_F(TimerFDTimeoutManagerTest, ReschedTest) {
TimerFDTimeoutManager& t = timeoutMgr;
TestTimeout t1;
TestTimeout t2;
ASSERT_EQ(t.count(), 0);
t.scheduleTimeout(&t1, microseconds(128));
TimePoint start2;
t1.fn = [&]() {
t.scheduleTimeout(&t2, microseconds(255)); // WHEEL_SIZE - 1
start2.reset();
ASSERT_EQ(t.count(), 2); // we scheduled et
};
ASSERT_EQ(t.count(), 1);
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.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], microseconds(128));
T_CHECK_TIMEOUT(start2, t2.timestamps[0], microseconds(255));
}
TEST_F(TimerFDTimeoutManagerTest, DeleteTimeoutManagerInTimeout) {
TimerFDTimeoutManager::UniquePtr t(new TimerFDTimeoutManager(&evb));
TestTimeout t1;
TestTimeout t2;
TestTimeout t3;
ASSERT_EQ(t->count(), 0);
t->scheduleTimeout(&t1, microseconds(128));
t->scheduleTimeout(&t2, microseconds(128));
t->scheduleTimeout(&t3, microseconds(128));
t1.fn = [&]() { t2.cancelTimeout(); };
t3.fn = [&]() {
t.reset();
evb.terminateLoopSoon();
};
ASSERT_EQ(t->count(), 3);
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 0);
T_CHECK_TIMEOUT(start, t1.timestamps[0], microseconds(128));
}
TEST_F(TimerFDTimeoutManagerTest, lambda) {
TimerFDTimeoutManager& t = timeoutMgr;
size_t count = 0;
t.scheduleTimeoutFn([&] { count++; }, microseconds(100));
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
evb.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(TimerFDTimeoutManagerTest, lambdaThrows) {
TimerFDTimeoutManager& t = timeoutMgr;
size_t count = 0;
t.scheduleTimeoutFn(
[&] {
count++;
throw std::runtime_error("expected");
},
microseconds(100));
TestTimeout et(&t, microseconds(1000));
et.fn = [&] { evb.terminateLoopSoon(); };
evb.loop();
// make sure the callback was invoked
EXPECT_EQ(1, count);
}
TEST_F(TimerFDTimeoutManagerTest, cancelAll) {
TimerFDTimeoutManager& t = timeoutMgr;
TestTimeout t1;
TestTimeout t2;
t.scheduleTimeout(&t1, std::chrono::microseconds(1000));
t.scheduleTimeout(&t2, std::chrono::microseconds(1000));
size_t canceled = 0;
t1.fn = [&] { canceled += t.cancelAll(); };
t2.fn = [&] { canceled += t.cancelAll(); };
// Sleep 20ms to ensure both timeouts will fire in a single event (in case
// they ended up in different slots)
::usleep(20000);
evb.scheduleAt(
[&]() { evb.terminateLoopSoon(); },
std::chrono::steady_clock::now() + std::chrono::milliseconds(100));
evb.loop();
EXPECT_EQ(1, t1.canceledTimestamps.size() + t2.canceledTimestamps.size());
EXPECT_EQ(1, canceled);
}
TEST_F(TimerFDTimeoutManagerTest, IntrusivePtr) {
TimerFDTimeoutManager::UniquePtr t(new TimerFDTimeoutManager(&evb));
TestTimeout t1;
TestTimeout t2;
TestTimeout t3;
ASSERT_EQ(t->count(), 0);
t->scheduleTimeout(&t1, microseconds(500));
t->scheduleTimeout(&t2, microseconds(500));
DelayedDestruction::IntrusivePtr<TimerFDTimeoutManager> s(t);
s->scheduleTimeout(&t3, microseconds(10));
ASSERT_EQ(t->count(), 3);
// Kill the UniquePtr, but the SharedPtr keeps it alive
t.reset();
evb.scheduleAt(
[&]() { evb.terminateLoopSoon(); },
std::chrono::steady_clock::now() + std::chrono::milliseconds(10));
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(t1.timestamps.size(), 1);
ASSERT_EQ(t2.timestamps.size(), 1);
ASSERT_EQ(t3.timestamps.size(), 1);
ASSERT_EQ(s->count(), 0);
T_CHECK_TIMEOUT(start, t1.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t2.timestamps[0], microseconds(500));
T_CHECK_TIMEOUT(start, t3.timestamps[0], microseconds(10));
T_CHECK_TIMEOUT(start, end, std::chrono::milliseconds(10)); // this is right
}
TEST_F(TimerFDTimeoutManagerTest, GetTimeRemaining) {
TimerFDTimeoutManager& t = timeoutMgr;
TestTimeout t1;
// Not scheduled yet, time remaining should be zero
ASSERT_EQ(t1.getTimeRemaining(), microseconds(0));
ASSERT_EQ(t.count(), 0);
// Scheduled, time remaining should be less than or equal to the scheduled
// timeout
t.scheduleTimeout(&t1, microseconds(10));
ASSERT_LE(t1.getTimeRemaining(), microseconds(10));
t1.fn = [&] { evb.terminateLoopSoon(); };
TimePoint start;
evb.loop();
TimePoint end;
// Expired and time remaining should be zero
ASSERT_EQ(t1.getTimeRemaining(), microseconds(0));
ASSERT_EQ(t.count(), 0);
T_CHECK_TIMEOUT(start, end, microseconds(10));
}
// Test that we handle negative timeouts properly (i.e. treat them as 0)
TEST_F(TimerFDTimeoutManagerTest, NegativeTimeout) {
TimerFDTimeoutManager& t = timeoutMgr;
std::this_thread::sleep_for(std::chrono::microseconds(10));
TestTimeout tt1;
TestTimeout tt2;
// Make sure we have event scheduled.
t.scheduleTimeout(&tt1, std::chrono::microseconds(100));
// Schedule another timeout that would appear to be earlier than
// the already scheduled one.
t.scheduleTimeout(&tt2, std::chrono::microseconds(-500000000));
evb.scheduleAt(
[&]() { evb.terminateLoopSoon(); },
std::chrono::steady_clock::now() + std::chrono::milliseconds(1));
TimePoint start;
evb.loop();
TimePoint end;
ASSERT_EQ(tt2.timestamps.size(), 1);
T_CHECK_TIMEOUT(start, end, std::chrono::milliseconds(10));
}
......@@ -21,4 +21,5 @@ namespace folly {
template <class Duration>
class HHWheelTimerBase;
using HHWheelTimer = HHWheelTimerBase<std::chrono::milliseconds>;
using HHWheelTimerHighRes = HHWheelTimerBase<std::chrono::microseconds>;
}
......@@ -375,6 +375,16 @@ int64_t HHWheelTimerBase<Duration>::calcNextTick(
return (curTime - startTime_) / interval_;
}
// std::chrono::microseconds
template <>
void HHWheelTimerBase<std::chrono::microseconds>::scheduleTimeoutInternal(
std::chrono::microseconds timeout) {
this->AsyncTimeout::scheduleTimeoutHighRes(timeout);
}
// std::chrono::milliseconds
template class HHWheelTimerBase<std::chrono::milliseconds>;
// std::chrono::microseconds
template class HHWheelTimerBase<std::chrono::microseconds>;
} // namespace folly
......@@ -346,4 +346,12 @@ class HHWheelTimerBase : private folly::AsyncTimeout,
using HHWheelTimer = HHWheelTimerBase<std::chrono::milliseconds>;
extern template class HHWheelTimerBase<std::chrono::milliseconds>;
// std::chrono::microseconds
template <>
void HHWheelTimerBase<std::chrono::microseconds>::scheduleTimeoutInternal(
std::chrono::microseconds timeout);
using HHWheelTimerHighRes = HHWheelTimerBase<std::chrono::microseconds>;
extern template class HHWheelTimerBase<std::chrono::microseconds>;
} // namespace folly
......@@ -20,6 +20,7 @@
#include <cstdint>
#include <folly/Function.h>
#include <folly/Optional.h>
namespace folly {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment