/** * * \section COPYRIGHT * * Copyright 2013-2021 Software Radio Systems Limited * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the distribution. * */ /****************************************************************************** * File: timers.h * Description: Manually incremented timers. Call a callback function upon * expiry. * Reference: *****************************************************************************/ #ifndef SRSRAN_TIMERS_H #define SRSRAN_TIMERS_H #include "srsran/adt/intrusive_list.h" #include "srsran/adt/move_callback.h" #include #include #include #include #include namespace srsran { class timer_callback { public: virtual void timer_expired(uint32_t timer_id) = 0; }; /** * Class that manages stack timers. It allows creation of unique_timers, with different ids. Each unique_timer duration, * and callback can be set via the set(...) method. A timer can be started/stopped via run()/stop() methods. * Internal Data structures: * - timer_list - std::deque that stores timer objects via push_back() to keep pointer/reference validity. * The timer index in the timer_list matches the timer object id field. * This deque will only grow in size. Erased timers are just tagged in the deque as empty, and can be reused for the * creation of new timers. To avoid unnecessary runtime allocations, the user can set an initial capacity. * - free_list - intrusive forward linked list to keep track of the empty timers and speed up new timer creation. * - A large circular vector of size WHEEL_SIZE which works as a time wheel, storing and circularly indexing the * currently running timers by their respective timeout value. * For a number of running timers N, and uniform distribution of timeout values, the step_all() complexity * should be O(N/WHEEL_SIZE). Thus, the performance should improve with a larger WHEEL_SIZE, at the expense of more * used memory. */ class timer_handler { using tic_diff_t = uint32_t; using tic_t = uint32_t; constexpr static uint32_t INVALID_ID = std::numeric_limits::max(); constexpr static tic_diff_t INVALID_TIME_DIFF = std::numeric_limits::max(); constexpr static size_t WHEEL_SHIFT = 16U; constexpr static size_t WHEEL_SIZE = 1U << WHEEL_SHIFT; constexpr static size_t WHEEL_MASK = WHEEL_SIZE - 1U; struct timer_impl : public intrusive_double_linked_list_element<>, public intrusive_forward_list_element<> { timer_handler& parent; const uint32_t id; tic_diff_t duration = INVALID_TIME_DIFF; tic_t timeout = 0; enum state_t : int8_t { empty, stopped, running, expired } state = empty; srsran::move_callback callback; explicit timer_impl(timer_handler& parent_, uint32_t id_) : parent(parent_), id(id_) {} timer_impl(const timer_impl&) = delete; timer_impl(timer_impl&&) = delete; timer_impl& operator=(const timer_impl&) = delete; timer_impl& operator=(timer_impl&&) = delete; bool is_empty() const { return state == empty; } bool is_running() const { return state == running; } bool is_expired() const { return state == expired; } tic_diff_t time_left() const { return is_running() ? timeout - parent.cur_time : (is_expired() ? 0 : duration); } uint32_t time_elapsed() const { return duration - time_left(); } bool set(uint32_t duration_) { duration = std::max(duration_, 1U); // the next step will be one place ahead of current one if (is_running()) { // if already running, just extends timer lifetime run(); } else { state = stopped; timeout = 0; } return true; } bool set(uint32_t duration_, srsran::move_callback callback_) { if (set(duration_)) { callback = std::move(callback_); return true; } return false; } void run() { std::lock_guard lock(parent.mutex); parent.start_run_(*this); } void stop() { std::lock_guard lock(parent.mutex); // does not call callback parent.stop_timer_(*this, false); } void deallocate() { parent.dealloc_timer(*this); } }; public: class unique_timer { public: unique_timer() = default; explicit unique_timer(timer_impl* handle_) : handle(handle_) {} unique_timer(const unique_timer&) = delete; unique_timer(unique_timer&& other) noexcept : handle(other.handle) { other.handle = nullptr; } ~unique_timer() { release(); } unique_timer& operator=(const unique_timer&) = delete; unique_timer& operator =(unique_timer&& other) noexcept { if (this != &other) { handle = other.handle; other.handle = nullptr; } return *this; } bool is_valid() const { return handle != nullptr; } void set(uint32_t duration_, move_callback callback_) { srsran_assert(is_valid(), "Trying to setup empty timer handle"); handle->set(duration_, std::move(callback_)); } void set(uint32_t duration_) { srsran_assert(is_valid(), "Trying to setup empty timer handle"); handle->set(duration_); } bool is_set() const { return is_valid() and handle->duration != INVALID_TIME_DIFF; } bool is_running() const { return is_valid() and handle->is_running(); } bool is_expired() const { return is_valid() and handle->is_expired(); } tic_diff_t time_elapsed() const { return is_valid() ? handle->time_elapsed() : INVALID_TIME_DIFF; } uint32_t id() const { return is_valid() ? handle->id : INVALID_ID; } tic_diff_t duration() const { return is_valid() ? handle->duration : INVALID_TIME_DIFF; } void run() { srsran_assert(is_valid(), "Starting invalid timer"); handle->run(); } void stop() { if (is_valid()) { handle->stop(); } } void release() { if (is_valid()) { handle->deallocate(); handle = nullptr; } } private: timer_impl* handle = nullptr; }; explicit timer_handler(uint32_t capacity = 64) { time_wheel.resize(WHEEL_SIZE); // Pre-reserve timers while (timer_list.size() < capacity) { timer_list.emplace_back(*this, timer_list.size()); } // push to free list in reverse order to keep ascending ids for (auto it = timer_list.rbegin(); it != timer_list.rend(); ++it) { free_list.push_front(&(*it)); } nof_free_timers = timer_list.size(); } void step_all() { std::unique_lock lock(mutex); cur_time++; auto& wheel_list = time_wheel[cur_time & WHEEL_MASK]; for (auto it = wheel_list.begin(); it != wheel_list.end();) { timer_impl& timer = timer_list[it->id]; ++it; if (timer.timeout == cur_time) { // stop timer (callback has to see the timer has already expired) stop_timer_(timer, true); // Call callback if configured if (not timer.callback.is_empty()) { // unlock mutex. It can happen that the callback tries to run a timer too lock.unlock(); timer.callback(timer.id); // Lock again to keep protecting the wheel lock.lock(); } } } } void stop_all() { std::lock_guard lock(mutex); // does not call callback for (timer_impl& timer : timer_list) { stop_timer_(timer, false); } } unique_timer get_unique_timer() { return unique_timer(&alloc_timer()); } uint32_t nof_timers() const { std::lock_guard lock(mutex); return timer_list.size() - nof_free_timers; } uint32_t nof_running_timers() const { std::lock_guard lock(mutex); return nof_timers_running_; } template void defer_callback(uint32_t duration, const F& func) { timer_impl& timer = alloc_timer(); srsran::move_callback c = [func, &timer](uint32_t tid) { func(); // auto-deletes timer timer.deallocate(); }; timer.set(duration, std::move(c)); timer.run(); } // useful for testing static size_t get_wheel_size() { return WHEEL_SIZE; } private: timer_impl& alloc_timer() { std::lock_guard lock(mutex); timer_impl* t; if (not free_list.empty()) { t = &free_list.front(); srsran_assert(t->is_empty(), "Invalid timer id=%d state", t->id); free_list.pop_front(); nof_free_timers--; } else { // Need to increase deque timer_list.emplace_back(*this, timer_list.size()); t = &timer_list.back(); } t->state = timer_impl::stopped; return *t; } void dealloc_timer(timer_impl& timer) { std::lock_guard lock(mutex); if (timer.is_empty()) { // already deallocated return; } stop_timer_(timer, false); timer.state = timer_impl::empty; timer.duration = INVALID_TIME_DIFF; timer.timeout = 0; timer.callback = srsran::move_callback(); free_list.push_front(&timer); nof_free_timers++; // leave id unchanged. } void start_run_(timer_impl& timer) { uint32_t timeout = cur_time + timer.duration; size_t new_wheel_pos = timeout & WHEEL_MASK; if (timer.is_running() and (timer.timeout & WHEEL_MASK) == new_wheel_pos) { // If no change in timer wheel position. Just update absolute timeout timer.timeout = timeout; return; } // Stop timer if it was running, removing it from wheel in the process stop_timer_(timer, false); // Insert timer in wheel time_wheel[new_wheel_pos].push_front(&timer); timer.timeout = timeout; timer.state = timer_impl::running; nof_timers_running_++; } /// called when user manually stops timer (as an alternative to expiry) void stop_timer_(timer_impl& timer, bool expiry) { if (not timer.is_running()) { return; } // If already running, need to disconnect it from previous wheel time_wheel[timer.timeout & WHEEL_MASK].pop(&timer); timer.state = expiry ? timer_impl::expired : timer_impl::stopped; nof_timers_running_--; } tic_t cur_time = 0; size_t nof_timers_running_ = 0, nof_free_timers = 0; // using a deque to maintain reference validity on emplace_back. Also, this deque will only grow. std::deque timer_list; srsran::intrusive_forward_list free_list; std::vector > time_wheel; mutable std::mutex mutex; // Protect priority queue }; using unique_timer = timer_handler::unique_timer; } // namespace srsran #endif // SRSRAN_TIMERS_H