Rocket.Chat.ReactNative/ios/Pods/Flipper-Folly/folly/io/async/HHWheelTimer.h

377 lines
11 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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/Optional.h>
#include <folly/io/async/AsyncTimeout.h>
#include <folly/io/async/DelayedDestruction.h>
#include <folly/io/async/HHWheelTimer-fwd.h>
#include <boost/intrusive/list.hpp>
#include <glog/logging.h>
#include <array>
#include <chrono>
#include <cstddef>
#include <memory>
namespace folly {
namespace detail {
template <class Duration>
struct HHWheelTimerDurationConst;
template <>
struct HHWheelTimerDurationConst<std::chrono::milliseconds> {
static constexpr int DEFAULT_TICK_INTERVAL = 10;
};
template <>
struct HHWheelTimerDurationConst<std::chrono::microseconds> {
static constexpr int DEFAULT_TICK_INTERVAL = 200;
};
} // namespace detail
/**
* Hashed Hierarchical Wheel Timer
*
* We model timers as the number of ticks until the next
* due event. We allow 32-bits of space to track this
* due interval, and break that into 4 regions of 8 bits.
* Each region indexes into a bucket of 256 lists.
*
* Bucket 0 represents those events that are due the soonest.
* Each tick causes us to look at the next list in a bucket.
* The 0th list in a bucket is special; it means that it is time to
* flush the timers from the next higher bucket and schedule them
* into a different bucket.
*
* This technique results in a very cheap mechanism for
* maintaining time and timers.
*
* Unlike the original timer wheel paper, this implementation does
* *not* tick constantly, and instead calculates the exact next wakeup
* time.
*/
template <class Duration>
class HHWheelTimerBase : private folly::AsyncTimeout,
public folly::DelayedDestruction {
public:
using UniquePtr = std::unique_ptr<HHWheelTimerBase, Destructor>;
using SharedPtr = std::shared_ptr<HHWheelTimerBase>;
template <typename... Args>
static UniquePtr newTimer(Args&&... args) {
return UniquePtr(new HHWheelTimerBase(std::forward<Args>(args)...));
}
/**
* A callback to be notified when a timeout has expired.
*/
class Callback
: public boost::intrusive::list_base_hook<
boost::intrusive::link_mode<boost::intrusive::auto_unlink>> {
public:
Callback();
virtual ~Callback();
/**
* timeoutExpired() is invoked when the timeout has expired.
*/
virtual void timeoutExpired() noexcept = 0;
/// This callback was canceled. The default implementation is to just
/// proxy to `timeoutExpired` but if you care about the difference between
/// the timeout finishing or being canceled you can override this.
virtual void callbackCanceled() noexcept {
timeoutExpired();
}
/**
* Cancel the timeout, if it is running.
*
* If the timeout is not scheduled, cancelTimeout() does nothing.
*/
void cancelTimeout() {
if (wheel_ == nullptr) {
// We're not scheduled, so there's nothing to do.
return;
}
cancelTimeoutImpl();
}
/**
* Return true if this timeout is currently scheduled, and false otherwise.
*/
bool isScheduled() const {
return wheel_ != nullptr;
}
/**
* Get the time remaining until this timeout expires. Return 0 if this
* timeout is not scheduled or expired. Otherwise, return expiration
* time minus current time.
*/
Duration getTimeRemaining() const {
return getTimeRemaining(std::chrono::steady_clock::now());
}
private:
// Get the time remaining until this timeout expires
Duration getTimeRemaining(std::chrono::steady_clock::time_point now) const {
if (now >= expiration_) {
return Duration(0);
}
return std::chrono::duration_cast<Duration>(expiration_ - now);
}
void setScheduled(
HHWheelTimerBase* wheel,
std::chrono::steady_clock::time_point deadline);
void cancelTimeoutImpl();
HHWheelTimerBase* wheel_{nullptr};
std::chrono::steady_clock::time_point expiration_{};
int bucket_{-1};
typedef boost::intrusive::
list<Callback, boost::intrusive::constant_time_size<false>>
List;
std::shared_ptr<RequestContext> requestContext_;
// Give HHWheelTimerBase direct access to our members so it can take care
// of scheduling/cancelling.
friend class HHWheelTimerBase;
};
/**
* Create a new HHWheelTimerBase with the specified interval and the
* default timeout value set.
*
* Objects created using this version of constructor can be used
* to schedule both variable interval timeouts using
* scheduleTimeout(callback, timeout) method, and default
* interval timeouts using scheduleTimeout(callback) method.
*/
static int DEFAULT_TICK_INTERVAL;
explicit HHWheelTimerBase(
folly::TimeoutManager* timeoutMananger,
Duration intervalDuration = Duration(DEFAULT_TICK_INTERVAL),
AsyncTimeout::InternalEnum internal = AsyncTimeout::InternalEnum::NORMAL,
Duration defaultTimeoutDuration = Duration(-1));
/**
* Cancel all outstanding timeouts
*
* @returns the number of timeouts that were cancelled.
*/
size_t cancelAll();
/**
* Get the tick interval for this HHWheelTimerBase.
*
* Returns the tick interval in milliseconds.
*/
Duration getTickInterval() const {
return interval_;
}
/**
* Get the default timeout interval for this HHWheelTimerBase.
*
* Returns the timeout interval in milliseconds.
*/
Duration getDefaultTimeout() const {
return defaultTimeout_;
}
/**
* Set the default timeout interval for this HHWheelTimerBase.
*/
void setDefaultTimeout(Duration timeout) {
defaultTimeout_ = timeout;
}
/**
* Schedule the specified Callback to be invoked after the
* specified timeout interval.
*
* If the callback is already scheduled, this cancels the existing timeout
* before scheduling the new timeout.
*/
void scheduleTimeout(Callback* callback, Duration timeout);
/**
* Schedule the specified Callback to be invoked after the
* default timeout interval.
*
* If the callback is already scheduled, this cancels the existing timeout
* before scheduling the new timeout.
*
* This method uses CHECK() to make sure that the default timeout was
* specified on the object initialization.
*/
void scheduleTimeout(Callback* callback);
template <class F>
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) << "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);
}
/**
* Return the number of currently pending timeouts
*/
std::size_t count() const {
return count_;
}
bool isDetachable() const {
return !folly::AsyncTimeout::isScheduled();
}
using folly::AsyncTimeout::attachEventBase;
using folly::AsyncTimeout::detachEventBase;
using folly::AsyncTimeout::getTimeoutManager;
protected:
/**
* Protected destructor.
*
* Use destroy() instead. See the comments in DelayedDestruction for more
* details.
*/
~HHWheelTimerBase() override;
private:
// Forbidden copy constructor and assignment operator
HHWheelTimerBase(HHWheelTimerBase const&) = delete;
HHWheelTimerBase& operator=(HHWheelTimerBase const&) = delete;
// Methods inherited from AsyncTimeout
void timeoutExpired() noexcept override;
Duration interval_;
Duration defaultTimeout_;
static constexpr int WHEEL_BUCKETS = 4;
static constexpr int WHEEL_BITS = 8;
static constexpr unsigned int WHEEL_SIZE = (1 << WHEEL_BITS);
static constexpr unsigned int WHEEL_MASK = (WHEEL_SIZE - 1);
static constexpr uint32_t LARGEST_SLOT = 0xffffffffUL;
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(Duration t) {
return t.count() / interval_.count();
}
bool cascadeTimers(
int bucket,
int tick,
std::chrono::steady_clock::time_point curTime);
void scheduleTimeoutInternal(Duration timeout);
int64_t expireTick_;
std::size_t count_;
std::chrono::steady_clock::time_point startTime_;
int64_t calcNextTick();
int64_t calcNextTick(std::chrono::steady_clock::time_point curTime);
static bool inSameEpoch(int64_t tickA, int64_t tickB) {
return (tickA >> WHEEL_BITS) == (tickB >> WHEEL_BITS);
}
/**
* Schedule a given timeout by putting it into the appropriate bucket of the
* wheel.
*
* @param callback Callback to fire after `timeout`
* @param dueTick Tick at which the timer is due.
* @param nextTickToProcess next tick that was not processed by the timer
* yet. Can be less than nextTick if we're lagging.
* @param nextTick next tick based on the actual time
*/
void scheduleTimeoutImpl(
Callback* callback,
int64_t dueTick,
int64_t nextTickToProcess,
int64_t nextTick);
/**
* Compute next required wheel tick to fire and schedule the timeout for that
* tick.
*
* @param nextTick next tick based on the actual time
*/
void scheduleNextTimeout(int64_t nextTick);
/**
* Schedule next wheel timeout in a fixed number of wheel ticks.
*
* @param nextTick next tick based on the actual time
* @param ticks number of ticks in which the timer should fire
*/
void scheduleNextTimeout(int64_t nextTick, int64_t ticks);
size_t cancelTimeoutsFromList(CallbackList& timeouts);
bool* processingCallbacksGuard_;
// Timeouts that we're about to run. They're already extracted from their
// corresponding buckets, so we need this list for the `cancelAll` to be able
// to cancel them.
CallbackList timeoutsToRunNow_;
std::chrono::steady_clock::time_point getCurTime() {
return std::chrono::steady_clock::now();
}
};
// std::chrono::milliseconds
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