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

Add support for microsecond timers

Summary: Add support for microsecond timers

Reviewed By: spalamarchuk

Differential Revision: D13765262

fbshipit-source-id: ab5e2d876195a726dc654aee28cb40f695cf2277
parent 241ea7bd
......@@ -18,10 +18,9 @@
#include <chrono>
#include <functional>
namespace folly {
class HHWheelTimer;
#include <folly/io/async/HHWheelTimer-fwd.h>
namespace folly {
namespace fibers {
class FiberManager;
......
......@@ -77,6 +77,13 @@ bool AsyncTimeout::scheduleTimeout(TimeoutManager::timeout_type timeout) {
return timeoutManager_->scheduleTimeout(this, timeout);
}
bool AsyncTimeout::scheduleTimeoutHighRes(
TimeoutManager::timeout_type_high_res timeout) {
assert(timeoutManager_ != nullptr);
context_ = RequestContext::saveContext();
return timeoutManager_->scheduleTimeoutHighRes(this, timeout);
}
bool AsyncTimeout::scheduleTimeout(uint32_t milliseconds) {
return scheduleTimeout(TimeoutManager::timeout_type(milliseconds));
}
......
......@@ -28,7 +28,6 @@ namespace folly {
class EventBase;
class RequestContext;
class TimeoutManager;
/**
* AsyncTimeout is used to asynchronously wait for a timeout to occur.
......@@ -97,6 +96,7 @@ class AsyncTimeout : private boost::noncopyable {
*/
bool scheduleTimeout(uint32_t milliseconds);
bool scheduleTimeout(TimeoutManager::timeout_type timeout);
bool scheduleTimeoutHighRes(TimeoutManager::timeout_type_high_res timeout);
/**
* Cancel the timeout, if it is running.
......
......@@ -15,7 +15,10 @@
*/
#pragma once
#include <chrono>
namespace folly {
class HHWheelTimer;
template <class Duration>
class HHWheelTimerBase;
using HHWheelTimer = HHWheelTimerBase<std::chrono::milliseconds>;
}
......@@ -25,7 +25,6 @@
#include <folly/io/async/Request.h>
#include <folly/lang/Bits.h>
using std::chrono::milliseconds;
namespace folly {
......@@ -41,16 +40,22 @@ namespace folly {
* start showing up in cpu perf. Also, it might not be possible to set
* tick interval less than 10ms on older kernels.
*/
int HHWheelTimer::DEFAULT_TICK_INTERVAL = 10;
template <class Duration>
int HHWheelTimerBase<Duration>::DEFAULT_TICK_INTERVAL = 10;
HHWheelTimer::Callback::~Callback() {
template <class Duration>
HHWheelTimerBase<Duration>::Callback::Callback() {}
template <class Duration>
HHWheelTimerBase<Duration>::Callback::~Callback() {
if (isScheduled()) {
cancelTimeout();
}
}
void HHWheelTimer::Callback::setScheduled(
HHWheelTimer* wheel,
template <class Duration>
void HHWheelTimerBase<Duration>::Callback::setScheduled(
HHWheelTimerBase* wheel,
std::chrono::steady_clock::time_point deadline) {
assert(wheel_ == nullptr);
assert(expiration_ == decltype(expiration_){});
......@@ -59,7 +64,8 @@ void HHWheelTimer::Callback::setScheduled(
expiration_ = deadline;
}
void HHWheelTimer::Callback::cancelTimeoutImpl() {
template <class Duration>
void HHWheelTimerBase<Duration>::Callback::cancelTimeoutImpl() {
if (--wheel_->count_ <= 0) {
assert(wheel_->count_ == 0);
wheel_->AsyncTimeout::cancelTimeout();
......@@ -74,14 +80,15 @@ void HHWheelTimer::Callback::cancelTimeoutImpl() {
expiration_ = {};
}
HHWheelTimer::HHWheelTimer(
template <class Duration>
HHWheelTimerBase<Duration>::HHWheelTimerBase(
folly::TimeoutManager* timeoutMananger,
std::chrono::milliseconds intervalMS,
Duration intervalDuration,
AsyncTimeout::InternalEnum internal,
std::chrono::milliseconds defaultTimeoutMS)
Duration defaultTimeoutDuration)
: AsyncTimeout(timeoutMananger, internal),
interval_(intervalMS),
defaultTimeout_(defaultTimeoutMS),
interval_(intervalDuration),
defaultTimeout_(defaultTimeoutDuration),
expireTick_(1),
count_(0),
startTime_(getCurTime()),
......@@ -89,7 +96,8 @@ HHWheelTimer::HHWheelTimer(
bitmap_.fill(0);
}
HHWheelTimer::~HHWheelTimer() {
template <class Duration>
HHWheelTimerBase<Duration>::~HHWheelTimerBase() {
// Ensure this gets done, but right before destruction finishes.
auto destructionPublisherGuard = folly::makeGuard([&] {
// Inform the subscriber that this instance is doomed.
......@@ -100,7 +108,8 @@ HHWheelTimer::~HHWheelTimer() {
cancelAll();
}
void HHWheelTimer::scheduleTimeoutImpl(
template <class Duration>
void HHWheelTimerBase<Duration>::scheduleTimeoutImpl(
Callback* callback,
int64_t dueTick,
int64_t nextTickToProcess,
......@@ -133,11 +142,12 @@ void HHWheelTimer::scheduleTimeoutImpl(
list->push_back(*callback);
}
void HHWheelTimer::scheduleTimeout(
template <class Duration>
void HHWheelTimerBase<Duration>::scheduleTimeout(
Callback* callback,
std::chrono::milliseconds timeout) {
Duration timeout) {
// Make sure that the timeout is not negative.
timeout = std::max(timeout, std::chrono::milliseconds::zero());
timeout = std::max(timeout, Duration::zero());
// Cancel the callback if it happens to be scheduled already.
callback->cancelTimeout();
callback->requestContext_ = RequestContext::saveContext();
......@@ -149,9 +159,10 @@ void HHWheelTimer::scheduleTimeout(
callback->setScheduled(this, now + timeout);
// There are three possible scenarios:
// - we are currently inside of HHWheelTimer::timeoutExpired. In this case,
// - we are currently inside of HHWheelTimerBase<Duration>::timeoutExpired.
// In this case,
// we need to use its last tick as a base for computations
// - HHWheelTimer tick timeout is already scheduled. In this case,
// - HHWheelTimerBase tick timeout is already scheduled. In this case,
// we need to use its scheduled tick as a base.
// - none of the above are true. In this case, it's safe to use the nextTick
// as a base.
......@@ -180,13 +191,15 @@ void HHWheelTimer::scheduleTimeout(
}
}
void HHWheelTimer::scheduleTimeout(Callback* callback) {
CHECK(std::chrono::milliseconds(-1) != defaultTimeout_)
template <class Duration>
void HHWheelTimerBase<Duration>::scheduleTimeout(Callback* callback) {
CHECK(Duration(-1) != defaultTimeout_)
<< "Default timeout was not initialized";
scheduleTimeout(callback, defaultTimeout_);
}
bool HHWheelTimer::cascadeTimers(int bucket, int tick) {
template <class Duration>
bool HHWheelTimerBase<Duration>::cascadeTimers(int bucket, int tick) {
CallbackList cbs;
cbs.swap(buckets_[bucket][tick]);
auto now = getCurTime();
......@@ -205,7 +218,13 @@ bool HHWheelTimer::cascadeTimers(int bucket, int tick) {
return tick == 0;
}
void HHWheelTimer::timeoutExpired() noexcept {
template <class Duration>
void HHWheelTimerBase<Duration>::scheduleTimeoutInternal(Duration timeout) {
this->AsyncTimeout::scheduleTimeout(timeout);
}
template <class Duration>
void HHWheelTimerBase<Duration>::timeoutExpired() noexcept {
auto nextTick = calcNextTick();
// If the last smart pointer for "this" is reset inside the callback's
......@@ -258,7 +277,7 @@ void HHWheelTimer::timeoutExpired() noexcept {
RequestContextScopeGuard rctx(cb->requestContext_);
cb->timeoutExpired();
if (isDestroyed) {
// The HHWheelTimer itself has been destroyed. The other callbacks
// The HHWheelTimerBase itself has been destroyed. The other callbacks
// will have been cancelled from the destructor. Bail before causing
// damage.
return;
......@@ -271,7 +290,8 @@ void HHWheelTimer::timeoutExpired() noexcept {
}
}
size_t HHWheelTimer::cancelAll() {
template <class Duration>
size_t HHWheelTimerBase<Duration>::cancelAll() {
size_t count = 0;
if (count_ != 0) {
......@@ -305,7 +325,8 @@ size_t HHWheelTimer::cancelAll() {
return count;
}
void HHWheelTimer::scheduleNextTimeout(int64_t nextTick) {
template <class Duration>
void HHWheelTimerBase<Duration>::scheduleNextTimeout(int64_t nextTick) {
int64_t tick = 1;
if (nextTick & WHEEL_MASK) {
......@@ -322,12 +343,17 @@ void HHWheelTimer::scheduleNextTimeout(int64_t nextTick) {
scheduleNextTimeout(nextTick, tick);
}
void HHWheelTimer::scheduleNextTimeout(int64_t nextTick, int64_t ticks) {
this->AsyncTimeout::scheduleTimeout(interval_ * ticks);
template <class Duration>
void HHWheelTimerBase<Duration>::scheduleNextTimeout(
int64_t nextTick,
int64_t ticks) {
scheduleTimeoutInternal(interval_ * ticks);
expireTick_ = ticks + nextTick - 1;
}
size_t HHWheelTimer::cancelTimeoutsFromList(CallbackList& timeouts) {
template <class Duration>
size_t HHWheelTimerBase<Duration>::cancelTimeoutsFromList(
CallbackList& timeouts) {
size_t count = 0;
while (!timeouts.empty()) {
++count;
......@@ -338,13 +364,17 @@ size_t HHWheelTimer::cancelTimeoutsFromList(CallbackList& timeouts) {
return count;
}
int64_t HHWheelTimer::calcNextTick() {
template <class Duration>
int64_t HHWheelTimerBase<Duration>::calcNextTick() {
return calcNextTick(getCurTime());
}
int64_t HHWheelTimer::calcNextTick(
template <class Duration>
int64_t HHWheelTimerBase<Duration>::calcNextTick(
std::chrono::steady_clock::time_point curTime) {
return (curTime - startTime_) / interval_;
}
template class HHWheelTimerBase<std::chrono::milliseconds>;
} // namespace folly
......@@ -52,15 +52,16 @@ namespace folly {
* *not* tick constantly, and instead calculates the exact next wakeup
* time.
*/
class HHWheelTimer : private folly::AsyncTimeout,
public folly::DelayedDestruction {
template <class Duration>
class HHWheelTimerBase : private folly::AsyncTimeout,
public folly::DelayedDestruction {
public:
using UniquePtr = std::unique_ptr<HHWheelTimer, Destructor>;
using SharedPtr = std::shared_ptr<HHWheelTimer>;
using UniquePtr = std::unique_ptr<HHWheelTimerBase, Destructor>;
using SharedPtr = std::shared_ptr<HHWheelTimerBase>;
template <typename... Args>
static UniquePtr newTimer(Args&&... args) {
return UniquePtr(new HHWheelTimer(std::forward<Args>(args)...));
return UniquePtr(new HHWheelTimerBase(std::forward<Args>(args)...));
}
/**
......@@ -70,7 +71,7 @@ class HHWheelTimer : private folly::AsyncTimeout,
: public boost::intrusive::list_base_hook<
boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
public:
Callback() = default;
Callback();
virtual ~Callback();
/**
......@@ -110,27 +111,25 @@ class HHWheelTimer : private folly::AsyncTimeout,
* timeout is not scheduled or expired. Otherwise, return expiration
* time minus current time.
*/
std::chrono::milliseconds getTimeRemaining() {
Duration getTimeRemaining() {
return getTimeRemaining(std::chrono::steady_clock::now());
}
private:
// Get the time remaining until this timeout expires
std::chrono::milliseconds getTimeRemaining(
std::chrono::steady_clock::time_point now) const {
Duration getTimeRemaining(std::chrono::steady_clock::time_point now) const {
if (now >= expiration_) {
return std::chrono::milliseconds(0);
return Duration(0);
}
return std::chrono::duration_cast<std::chrono::milliseconds>(
expiration_ - now);
return std::chrono::duration_cast<Duration>(expiration_ - now);
}
void setScheduled(
HHWheelTimer* wheel,
HHWheelTimerBase* wheel,
std::chrono::steady_clock::time_point deadline);
void cancelTimeoutImpl();
HHWheelTimer* wheel_{nullptr};
HHWheelTimerBase* wheel_{nullptr};
std::chrono::steady_clock::time_point expiration_{};
int bucket_{-1};
......@@ -140,13 +139,13 @@ class HHWheelTimer : private folly::AsyncTimeout,
std::shared_ptr<RequestContext> requestContext_;
// Give HHWheelTimer direct access to our members so it can take care
// Give HHWheelTimerBase direct access to our members so it can take care
// of scheduling/cancelling.
friend class HHWheelTimer;
friend class HHWheelTimerBase;
};
/**
* Create a new HHWheelTimer with the specified interval and the
* Create a new HHWheelTimerBase with the specified interval and the
* default timeout value set.
*
* Objects created using this version of constructor can be used
......@@ -155,13 +154,11 @@ class HHWheelTimer : private folly::AsyncTimeout,
* interval timeouts using scheduleTimeout(callback) method.
*/
static int DEFAULT_TICK_INTERVAL;
explicit HHWheelTimer(
explicit HHWheelTimerBase(
folly::TimeoutManager* timeoutManager,
std::chrono::milliseconds intervalMS =
std::chrono::milliseconds(DEFAULT_TICK_INTERVAL),
Duration intervalDuration = Duration(DEFAULT_TICK_INTERVAL),
AsyncTimeout::InternalEnum internal = AsyncTimeout::InternalEnum::NORMAL,
std::chrono::milliseconds defaultTimeoutMS =
std::chrono::milliseconds(-1));
Duration defaultTimeoutDuration = Duration(-1));
/**
* Cancel all outstanding timeouts
......@@ -171,27 +168,27 @@ class HHWheelTimer : private folly::AsyncTimeout,
size_t cancelAll();
/**
* Get the tick interval for this HHWheelTimer.
* Get the tick interval for this HHWheelTimerBase.
*
* Returns the tick interval in milliseconds.
*/
std::chrono::milliseconds getTickInterval() const {
Duration getTickInterval() const {
return interval_;
}
/**
* Get the default timeout interval for this HHWheelTimer.
* Get the default timeout interval for this HHWheelTimerBase.
*
* Returns the timeout interval in milliseconds.
*/
std::chrono::milliseconds getDefaultTimeout() const {
Duration getDefaultTimeout() const {
return defaultTimeout_;
}
/**
* Set the default timeout interval for this HHWheelTimer.
* Set the default timeout interval for this HHWheelTimerBase.
*/
void setDefaultTimeout(std::chrono::milliseconds timeout) {
void setDefaultTimeout(Duration timeout) {
defaultTimeout_ = timeout;
}
......@@ -202,7 +199,7 @@ class HHWheelTimer : private folly::AsyncTimeout,
* If the callback is already scheduled, this cancels the existing timeout
* before scheduling the new timeout.
*/
void scheduleTimeout(Callback* callback, std::chrono::milliseconds timeout);
void scheduleTimeout(Callback* callback, Duration timeout);
/**
* Schedule the specified Callback to be invoked after the
......@@ -217,17 +214,18 @@ class HHWheelTimer : private folly::AsyncTimeout,
void scheduleTimeout(Callback* callback);
template <class F>
void scheduleTimeoutFn(F fn, std::chrono::milliseconds timeout) {
void scheduleTimeoutFn(F fn, Duration timeout) {
struct Wrapper : Callback {
Wrapper(F f) : fn_(std::move(f)) {}
void timeoutExpired() noexcept override {
try {
fn_();
} catch (std::exception const& e) {
LOG(ERROR) << "HHWheelTimer timeout callback threw an exception: "
LOG(ERROR) << "HHWheelTimerBase timeout callback threw an exception: "
<< e.what();
} catch (...) {
LOG(ERROR) << "HHWheelTimer timeout callback threw a non-exception.";
LOG(ERROR)
<< "HHWheelTimerBase timeout callback threw a non-exception.";
}
delete this;
}
......@@ -259,18 +257,18 @@ class HHWheelTimer : private folly::AsyncTimeout,
* Use destroy() instead. See the comments in DelayedDestruction for more
* details.
*/
~HHWheelTimer() override;
~HHWheelTimerBase() override;
private:
// Forbidden copy constructor and assignment operator
HHWheelTimer(HHWheelTimer const&) = delete;
HHWheelTimer& operator=(HHWheelTimer const&) = delete;
HHWheelTimerBase(HHWheelTimerBase const&) = delete;
HHWheelTimerBase& operator=(HHWheelTimerBase const&) = delete;
// Methods inherited from AsyncTimeout
void timeoutExpired() noexcept override;
std::chrono::milliseconds interval_;
std::chrono::milliseconds defaultTimeout_;
Duration interval_;
Duration defaultTimeout_;
static constexpr int WHEEL_BUCKETS = 4;
static constexpr int WHEEL_BITS = 8;
......@@ -278,15 +276,17 @@ class HHWheelTimer : private folly::AsyncTimeout,
static constexpr unsigned int WHEEL_MASK = (WHEEL_SIZE - 1);
static constexpr uint32_t LARGEST_SLOT = 0xffffffffUL;
typedef Callback::List CallbackList;
typedef typename Callback::List CallbackList;
CallbackList buckets_[WHEEL_BUCKETS][WHEEL_SIZE];
std::array<std::size_t, (WHEEL_SIZE / sizeof(std::size_t)) / 8> bitmap_;
int64_t timeToWheelTicks(std::chrono::milliseconds t) {
int64_t timeToWheelTicks(Duration t) {
return t.count() / interval_.count();
}
bool cascadeTimers(int bucket, int tick);
void scheduleTimeoutInternal(Duration timeout);
int64_t expireTick_;
std::size_t count_;
std::chrono::steady_clock::time_point startTime_;
......@@ -343,4 +343,7 @@ class HHWheelTimer : private folly::AsyncTimeout,
}
};
using HHWheelTimer = HHWheelTimerBase<std::chrono::milliseconds>;
extern template class HHWheelTimerBase<std::chrono::milliseconds>;
} // namespace folly
......@@ -18,6 +18,7 @@
#include <boost/intrusive/list.hpp>
#include <folly/Chrono.h>
#include <folly/Exception.h>
#include <folly/Memory.h>
#include <folly/io/async/AsyncTimeout.h>
......@@ -70,6 +71,13 @@ struct TimeoutManager::CobTimeouts {
TimeoutManager::TimeoutManager()
: cobTimeouts_(std::make_unique<CobTimeouts>()) {}
bool TimeoutManager::scheduleTimeoutHighRes(
AsyncTimeout* obj,
timeout_type_high_res timeout) {
timeout_type timeout_ms = folly::chrono::round<timeout_type>(timeout);
return scheduleTimeout(obj, timeout_ms);
}
void TimeoutManager::runAfterDelay(
Func cob,
uint32_t milliseconds,
......
......@@ -33,6 +33,8 @@ class AsyncTimeout;
class TimeoutManager {
public:
typedef std::chrono::milliseconds timeout_type;
typedef std::chrono::microseconds timeout_type_high_res;
using Func = folly::Function<void()>;
enum class InternalEnum { INTERNAL, NORMAL };
......@@ -54,6 +56,13 @@ class TimeoutManager {
*/
virtual bool scheduleTimeout(AsyncTimeout* obj, timeout_type timeout) = 0;
/**
* Schedules AsyncTimeout to fire after `timeout` microseconds
*/
virtual bool scheduleTimeoutHighRes(
AsyncTimeout* obj,
timeout_type_high_res timeout);
/**
* Cancels the AsyncTimeout, if scheduled
*/
......
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