/** * Copyright 2013-2022 Software Radio Systems Limited * * This file is part of srsRAN. * * srsRAN is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * srsRAN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * A copy of the GNU Affero General Public License can be found in * the LICENSE file in the top-level directory of this distribution * and at http://www.gnu.org/licenses/. * */ /****************************************************************************** * 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 #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. * The timers access/alteration is thread-safe. Just beware non-atomic uses of its getters. * 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 size_t WHEEL_SHIFT = 16U; constexpr static size_t WHEEL_SIZE = 1U << WHEEL_SHIFT; constexpr static size_t WHEEL_MASK = WHEEL_SIZE - 1U; constexpr static uint64_t STOPPED_FLAG = 0U; constexpr static uint64_t RUNNING_FLAG = static_cast(1U) << 63U; constexpr static uint64_t EXPIRED_FLAG = static_cast(1U) << 62U; constexpr static tic_diff_t MAX_TIMER_DURATION = 0x3FFFFFFFU; static bool decode_is_running(uint64_t value) { return (value & RUNNING_FLAG) != 0; } static bool decode_is_expired(uint64_t value) { return (value & EXPIRED_FLAG) != 0; } static tic_diff_t decode_duration(uint64_t value) { return (value >> 32U) & MAX_TIMER_DURATION; } static tic_t decode_timeout(uint64_t value) { return static_cast(value & 0xFFFFFFFFU); } static uint64_t encode_state(uint64_t mode_flag, uint32_t duration, uint32_t timeout) { return mode_flag + (static_cast(duration) << 32U) + timeout; } struct timer_impl : public intrusive_double_linked_list_element<>, public intrusive_forward_list_element<> { // const const uint32_t id; timer_handler& parent; // writes protected by backend lock bool allocated = false; std::atomic state{0}; ///< read can be without lock, thus writes must be atomic 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; // unprotected bool is_running_() const { return decode_is_running(state.load(std::memory_order_relaxed)); } bool is_expired_() const { return decode_is_expired(state.load(std::memory_order_relaxed)); } uint32_t duration_() const { return decode_duration(state.load(std::memory_order_relaxed)); } bool is_set_() const { return duration_() > 0; } tic_diff_t time_elapsed_() const { uint64_t state_snapshot = state.load(std::memory_order_relaxed); bool running = decode_is_running(state_snapshot), expired = decode_is_expired(state_snapshot); uint32_t duration = decode_duration(state_snapshot), timeout = decode_timeout(state_snapshot); return running ? duration - (timeout - parent.cur_time) : (expired ? duration : 0); } void set(uint32_t duration_) { srsran_assert(duration_ <= MAX_TIMER_DURATION, "Invalid timer duration=%" PRIu32 ">%" PRIu32, duration_, MAX_TIMER_DURATION); std::lock_guard lock(parent.mutex); set_(duration_); } void set(uint32_t duration_, srsran::move_callback callback_) { srsran_assert(duration_ <= MAX_TIMER_DURATION, "Invalid timer duration=%" PRIu32 ">%" PRIu32, duration_, MAX_TIMER_DURATION); std::lock_guard lock(parent.mutex); set_(duration_); callback = std::move(callback_); } 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() { std::lock_guard lock(parent.mutex); parent.dealloc_timer_(*this); } private: void set_(uint32_t duration_) { duration_ = std::max(duration_, 1U); // the next step will be one place ahead of current one // called in locked context uint64_t old_state = state.load(std::memory_order_relaxed); if (decode_is_running(old_state)) { // if already running, just extends timer lifetime parent.start_run_(*this, duration_); } else { state.store(encode_state(STOPPED_FLAG, duration_, 0), std::memory_order_relaxed); } } }; 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_); } uint32_t id() const { return is_valid() ? handle->id : INVALID_ID; } bool is_set() const { return is_valid() and handle->is_set_(); } 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_() : 0; } tic_diff_t duration() const { return is_valid() ? handle->duration_() : 0; } 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); uint32_t cur_time_local = cur_time.load(std::memory_order_relaxed) + 1; auto& wheel_list = time_wheel[cur_time_local & WHEEL_MASK]; for (auto it = wheel_list.begin(); it != wheel_list.end();) { timer_impl& timer = timer_list[it->id]; ++it; if (decode_timeout(timer.state.load(std::memory_order_relaxed)) == cur_time_local) { // 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(); } } } cur_time.fetch_add(1, std::memory_order_relaxed); } 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_; } constexpr static uint32_t max_timer_duration() { return MAX_TIMER_DURATION; } 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(not t->allocated, "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->allocated = true; return *t; } void dealloc_timer_(timer_impl& timer) { if (not timer.allocated) { // already deallocated return; } stop_timer_(timer, false); timer.allocated = false; timer.state.store(encode_state(STOPPED_FLAG, 0, 0), std::memory_order_relaxed); timer.callback = srsran::move_callback(); free_list.push_front(&timer); nof_free_timers++; // leave id unchanged. } void start_run_(timer_impl& timer, uint32_t duration_ = 0) { uint64_t timer_old_state = timer.state.load(std::memory_order_relaxed); duration_ = duration_ == 0 ? decode_duration(timer_old_state) : duration_; uint32_t new_timeout = cur_time.load(std::memory_order_relaxed) + duration_; size_t new_wheel_pos = new_timeout & WHEEL_MASK; uint32_t old_timeout = decode_timeout(timer_old_state); bool was_running = decode_is_running(timer_old_state); if (was_running and (old_timeout & WHEEL_MASK) == new_wheel_pos) { // If no change in timer wheel position. Just update absolute timeout timer.state.store(encode_state(RUNNING_FLAG, duration_, new_timeout), std::memory_order_relaxed); return; } // Stop timer if it was running, removing it from wheel in the process if (was_running) { time_wheel[old_timeout & WHEEL_MASK].pop(&timer); nof_timers_running_--; } // Insert timer in wheel time_wheel[new_wheel_pos].push_front(&timer); timer.state.store(encode_state(RUNNING_FLAG, duration_, new_timeout), std::memory_order_relaxed); nof_timers_running_++; } /// called when user manually stops timer (as an alternative to expiry) void stop_timer_(timer_impl& timer, bool expiry) { uint64_t timer_old_state = timer.state.load(std::memory_order_relaxed); if (not decode_is_running(timer_old_state)) { return; } // If already running, need to disconnect it from previous wheel uint32_t old_timeout = decode_timeout(timer_old_state); time_wheel[old_timeout & WHEEL_MASK].pop(&timer); uint64_t new_state = encode_state(expiry ? EXPIRED_FLAG : STOPPED_FLAG, decode_duration(timer_old_state), old_timeout); timer.state.store(new_state, std::memory_order_relaxed); nof_timers_running_--; } std::atomic 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