Files
scylladb/core/timer-set.hh
Takuya ASADA 991b77863d Use namespace on timer_set to prevent conflict with OSv's timer_set
Since we have same header on OSv too, we need to prevent conflict.

Signed-off-by: Takuya ASADA <syuu@cloudius-systems.com>
2015-04-19 10:33:36 +03:00

258 lines
6.9 KiB
C++

/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*/
/*
* Imported from OSv:
*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/
#ifndef __TIMER_SET_HH
#define __TIMER_SET_HH
#include <chrono>
#include <limits>
#include <bitset>
#include <array>
#include <boost/intrusive/list.hpp>
#include "bitset-iter.hh"
namespace bi = boost::intrusive;
namespace seastar {
/**
* A data structure designed for holding and expiring timers. It's
* optimized for timer non-delivery by deferring sorting cost until
* expiry time. The optimization is based on the observation that in
* many workloads timers are cancelled or rescheduled before they
* expire. That's especially the case for TCP timers.
*
* The template type "Timer" should have a method named
* get_timeout() which returns Timer::time_point which denotes
* timer's expiration.
*/
template<typename Timer, bi::list_member_hook<> Timer::*link>
class timer_set {
public:
using time_point = typename Timer::time_point;
using timer_list_t = bi::list<Timer, bi::member_hook<Timer, bi::list_member_hook<>, link>>;
private:
using duration = typename Timer::duration;
using timestamp_t = typename Timer::duration::rep;
static constexpr timestamp_t max_timestamp = std::numeric_limits<timestamp_t>::max();
static constexpr int timestamp_bits = std::numeric_limits<timestamp_t>::digits;
// The last bucket is reserved for active timers with timeout <= _last.
static constexpr int n_buckets = timestamp_bits + 1;
std::array<timer_list_t, n_buckets> _buckets;
timestamp_t _last;
timestamp_t _next;
std::bitset<n_buckets> _non_empty_buckets;
private:
static timestamp_t get_timestamp(time_point _time_point)
{
return _time_point.time_since_epoch().count();
}
static timestamp_t get_timestamp(Timer& timer)
{
return get_timestamp(timer.get_timeout());
}
int get_index(timestamp_t timestamp) const
{
if (timestamp <= _last) {
return n_buckets - 1;
}
auto index = bitsets::count_leading_zeros(timestamp ^ _last);
assert(index < n_buckets - 1);
return index;
}
int get_index(Timer& timer) const
{
return get_index(get_timestamp(timer));
}
int get_last_non_empty_bucket() const
{
return bitsets::get_last_set(_non_empty_buckets);
}
public:
timer_set()
: _last(0)
, _next(max_timestamp)
, _non_empty_buckets(0)
{
}
~timer_set() {
for (auto&& list : _buckets) {
while (!list.empty()) {
auto& timer = *list.begin();
timer.cancel();
}
}
}
/**
* Adds timer to the active set.
*
* The value returned by timer.get_timeout() is used as timer's expiry. The result
* of timer.get_timeout() must not change while the timer is in the active set.
*
* Preconditions:
* - this timer must not be currently in the active set or in the expired set.
*
* Postconditions:
* - this timer will be added to the active set until it is expired
* by a call to expire() or removed by a call to remove().
*
* Returns true if and only if this timer's timeout is less than get_next_timeout().
* When this function returns true the caller should reschedule expire() to be
* called at timer.get_timeout() to ensure timers are expired in a timely manner.
*/
bool insert(Timer& timer)
{
auto timestamp = get_timestamp(timer);
auto index = get_index(timestamp);
_buckets[index].push_back(timer);
_non_empty_buckets[index] = true;
if (timestamp < _next) {
_next = timestamp;
return true;
}
return false;
}
/**
* Removes timer from the active set.
*
* Preconditions:
* - timer must be currently in the active set. Note: it must not be in
* the expired set.
*
* Postconditions:
* - timer is no longer in the active set.
* - this object will no longer hold any references to this timer.
*/
void remove(Timer& timer)
{
auto index = get_index(timer);
auto& list = _buckets[index];
list.erase(list.iterator_to(timer));
if (list.empty()) {
_non_empty_buckets[index] = false;
}
}
/**
* Expires active timers.
*
* The time points passed to this function must be monotonically increasing.
* Use get_next_timeout() to query for the next time point.
*
* Preconditions:
* - the time_point passed to this function must not be lesser than
* the previous one passed to this function.
*
* Postconditons:
* - all timers from the active set with Timer::get_timeout() <= now are moved
* to the expired set.
*/
timer_list_t expire(time_point now)
{
timer_list_t exp;
auto timestamp = get_timestamp(now);
if (timestamp < _last) {
abort();
}
auto index = get_index(timestamp);
for (int i : bitsets::for_each_set(_non_empty_buckets, index + 1)) {
exp.splice(exp.end(), _buckets[i]);
_non_empty_buckets[i] = false;
}
_last = timestamp;
_next = max_timestamp;
auto& list = _buckets[index];
while (!list.empty()) {
auto& timer = *list.begin();
list.pop_front();
if (timer.get_timeout() <= now) {
exp.push_back(timer);
} else {
insert(timer);
}
}
_non_empty_buckets[index] = !list.empty();
if (_next == max_timestamp && _non_empty_buckets.any()) {
for (auto& timer : _buckets[get_last_non_empty_bucket()]) {
_next = std::min(_next, get_timestamp(timer));
}
}
return exp;
}
/**
* Returns a time point at which expire() should be called
* in order to ensure timers are expired in a timely manner.
*
* Returned values are monotonically increasing.
*/
time_point get_next_timeout() const
{
return time_point(duration(std::max(_last, _next)));
}
/**
* Clears both active and expired timer sets.
*/
void clear()
{
for (int i : bitsets::for_each_set(_non_empty_buckets)) {
_buckets[i].clear();
}
}
size_t size() const
{
size_t res = 0;
for (int i : bitsets::for_each_set(_non_empty_buckets)) {
res += _buckets[i].size();
}
return res;
}
/**
* Returns true if and only if there are no timers in the active set.
*/
bool empty() const
{
return _non_empty_buckets.none();
}
time_point now() {
return Timer::clock::now();
}
};
};
#endif