Skip to content

Commit 438cd16

Browse files
committed
support: remove mutex from timer manager timer creation
1 parent d5b684c commit 438cd16

File tree

2 files changed

+76
-50
lines changed

2 files changed

+76
-50
lines changed

include/srsran/support/timers.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ class timer_manager
122122
/// \param capacity Number of timers to pre-reserve and speed up timer construction.
123123
explicit timer_manager(size_t pre_reserve_capacity = 64);
124124

125+
~timer_manager();
126+
125127
/// Advances one tick and triggers any running timer that just expired.
126128
void tick();
127129

@@ -137,6 +139,8 @@ class timer_manager
137139
private:
138140
friend class unique_timer;
139141

142+
class unique_timer_pool;
143+
140144
/// \brief Create a new front-end context to be used by a newly created unique_timer.
141145
timer_frontend& create_frontend_timer(task_executor& exec);
142146

@@ -179,13 +183,12 @@ class timer_manager
179183
/// reused for the creation of new unique_timers in the front-end. The fact that the timer_handle is not deleted
180184
/// also avoids potential dangling pointer when the timer expiry callback is dispatched to the front-end execution
181185
/// context.
182-
/// Note: we use a deque to maintain reference validity.
183-
std::deque<timer_handle> timer_list;
186+
// Note: we use a deque to maintain reference validity.
187+
std::deque<timer_handle> timers;
184188

185-
/// List of timer_handle objects in timer_list that are currently not allocated.
186-
mutable std::mutex free_list_mutex;
187-
unsigned next_timer_id = 0;
188-
std::vector<timer_frontend*> free_list;
189+
// Pool of timers that can be reused.
190+
std::unique_ptr<unique_timer_pool> timer_pool;
191+
std::atomic<unsigned> next_timer_id{0};
189192

190193
/// \brief Timer wheel, which is circularly indexed via a running timer timeout. Collisions are resolved via an
191194
/// intrusive linked list stored in the timer_handle objects.

lib/support/timers.cpp

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
#include "srsran/support/timers.h"
12+
#include "cameron314/concurrentqueue.h"
1213
#include "srsran/srslog/srslog.h"
1314

1415
using namespace srsran;
@@ -62,22 +63,56 @@ void timer_manager::timer_frontend::stop()
6263
parent.push_timer_command(cmd_t{id, cmd_id.load(std::memory_order_relaxed), cmd_t::stop});
6364
}
6465

65-
timer_manager::timer_manager(size_t capacity) : logger(srslog::fetch_basic_logger("ALL")), time_wheel(WHEEL_SIZE)
66+
//
67+
68+
class timer_manager::unique_timer_pool
69+
{
70+
public:
71+
unique_timer_pool(timer_manager& parent, unsigned capacity) : free_list(capacity) {}
72+
73+
void push(timer_manager::timer_frontend* obj) { free_list.enqueue(obj); }
74+
75+
timer_manager::timer_frontend* pop()
76+
{
77+
timer_manager::timer_frontend* ret;
78+
if (free_list.try_dequeue(ret)) {
79+
return ret;
80+
}
81+
return nullptr;
82+
}
83+
84+
size_t size_approx() const { return free_list.size_approx(); }
85+
86+
private:
87+
// List of timer_handle objects in timer_list that are currently not allocated.
88+
moodycamel::ConcurrentQueue<timer_manager::timer_frontend*> free_list;
89+
};
90+
91+
//
92+
93+
timer_manager::timer_manager(size_t capacity) :
94+
logger(srslog::fetch_basic_logger("ALL")),
95+
timer_pool(std::make_unique<unique_timer_pool>(*this, capacity)),
96+
time_wheel(WHEEL_SIZE)
6697
{
6798
// Pre-reserve timers.
68-
while (timer_list.size() < capacity) {
69-
timer_list.emplace_back().frontend = std::make_unique<timer_frontend>(*this, (timer_id_t)next_timer_id++);
99+
while (timers.size() < capacity) {
100+
timers.emplace_back().frontend =
101+
std::make_unique<timer_frontend>(*this, (timer_id_t)next_timer_id.fetch_add(1, std::memory_order_relaxed));
70102
}
71103

72-
// Push to free list in reverse order to keep ascending ids.
73-
for (auto i = timer_list.rbegin(), e = timer_list.rend(); i != e; ++i) {
74-
free_list.emplace_back(i->frontend.get());
104+
// Push to free list in ascending id order.
105+
for (const auto& t : timers) {
106+
timer_pool->push(t.frontend.get());
75107
}
108+
76109
const uint16_t cmds_capacity = 16384;
77110
pending_cmds.reserve(cmds_capacity);
78111
cmds_to_process.reserve(cmds_capacity);
79112
}
80113

114+
timer_manager::~timer_manager() {}
115+
81116
void timer_manager::tick()
82117
{
83118
// Extract new commands from the timer front-ends to process in this tick.
@@ -97,7 +132,7 @@ void timer_manager::tick()
97132

98133
// Existing timer.
99134
const cmd_t& cmd = std::get<cmd_t>(event);
100-
timer_handle& timer = timer_list[static_cast<unsigned>(cmd.id)];
135+
timer_handle& timer = timers[static_cast<size_t>(cmd.id)];
101136

102137
// Update the timer backend cmd_id to match frontend.
103138
timer.backend.cmd_id = cmd.cmd_id;
@@ -125,7 +160,7 @@ void timer_manager::tick()
125160
// Iterate intrusive linked list of running timers with same wheel index.
126161
for (auto it = wheel_list.begin(); it != wheel_list.end();) {
127162
srsran_assert(it->frontend != nullptr, "invalid state of timer in timer wheel");
128-
timer_handle& timer = timer_list[static_cast<unsigned>(it->frontend->id)];
163+
timer_handle& timer = timers[static_cast<size_t>(it->frontend->id)];
129164
// We move iterator already, in case, the current timer gets removed from the linked list.
130165
++it;
131166

@@ -148,12 +183,11 @@ void timer_manager::push_timer_command(cmd_t cmd)
148183
void timer_manager::create_timer_handle(std::unique_ptr<timer_frontend> timer)
149184
{
150185
auto timer_idx = static_cast<unsigned>(timer->id);
151-
srsran_assert(timer_idx >= timer_list.size() or timer_list[timer_idx].frontend == nullptr,
152-
"Duplicate timer id detection");
153-
if (timer_idx >= timer_list.size()) {
154-
timer_list.resize(timer_idx + 1);
186+
srsran_assert(timer_idx >= timers.size() or timers[timer_idx].frontend == nullptr, "Duplicate timer id detection");
187+
if (timer_idx >= timers.size()) {
188+
timers.resize(timer_idx + 1);
155189
}
156-
timer_list[timer_idx].frontend = std::move(timer);
190+
timers[timer_idx].frontend = std::move(timer);
157191
}
158192

159193
void timer_manager::start_timer_backend(timer_handle& timer, unsigned duration)
@@ -222,7 +256,7 @@ bool timer_manager::try_stop_timer_backend(timer_handle& timer, bool expiry_reas
222256
void timer_manager::handle_postponed_timeouts()
223257
{
224258
while (not failed_to_trigger_timers.empty()) {
225-
timer_handle& timer = timer_list[static_cast<unsigned>(failed_to_trigger_timers.front().first)];
259+
timer_handle& timer = timers[(size_t)failed_to_trigger_timers.front().first];
226260
cmd_id_t prev_cmd_id = failed_to_trigger_timers.front().second;
227261

228262
if (timer.backend.cmd_id == prev_cmd_id and
@@ -256,41 +290,31 @@ void timer_manager::destroy_timer_backend(timer_handle& timer)
256290
timer.backend.state = state_t::stopped;
257291
timer.backend.timeout = 0;
258292
// Add timer handle in free list.
259-
std::lock_guard<std::mutex> lock(free_list_mutex);
260-
free_list.emplace_back(timer.frontend.get());
293+
timer_pool->push(timer.frontend.get());
261294
}
262295

263296
timer_manager::timer_frontend& timer_manager::create_frontend_timer(task_executor& exec)
264297
{
265-
// Allocate timer frontend with unique timer id.
266-
timer_id_t id = timer_id_t::invalid;
267-
timer_frontend* cached_timer = nullptr;
268-
{
269-
std::lock_guard<std::mutex> lock(free_list_mutex);
270-
if (!free_list.empty()) {
271-
cached_timer = free_list.back();
272-
free_list.pop_back();
273-
} else {
274-
// Need to allocate new timer.
275-
id = (timer_id_t)next_timer_id++;
276-
}
277-
}
278-
279-
// In case it fails to reuse a cached timer frontend object. Need to create a new one.
280-
if (cached_timer == nullptr) {
281-
auto new_handle = std::make_unique<timer_frontend>(*this, id);
282-
new_handle->exec = &exec;
283-
cached_timer = new_handle.get();
284-
285-
// Forward created timer handle to the backend.
286-
{
287-
std::lock_guard<std::mutex> lock(cmd_mutex);
288-
pending_cmds.emplace_back(std::move(new_handle));
289-
}
290-
} else {
298+
// Pop cached timer from pool.
299+
timer_frontend* cached_timer = timer_pool->pop();
300+
if (cached_timer != nullptr) {
291301
srsran_assert(cached_timer->exec == nullptr, "Reassignment of timer detected");
292302
// Assign new executor to created timer.
293303
cached_timer->exec = &exec;
304+
return *cached_timer;
305+
}
306+
307+
// In case it fails to reuse a cached timer frontend object. Need to create a new one.
308+
const auto id = (timer_id_t)next_timer_id.fetch_add(1, std::memory_order_relaxed);
309+
auto new_handle = std::make_unique<timer_frontend>(*this, id);
310+
new_handle->exec = &exec;
311+
cached_timer = new_handle.get();
312+
313+
// Forward created timer handle to the backend.
314+
// Note: This cannot fail, otherwise the created "id" cannot be reused.
315+
{
316+
std::lock_guard<std::mutex> lock(cmd_mutex);
317+
pending_cmds.emplace_back(std::move(new_handle));
294318
}
295319

296320
return *cached_timer;
@@ -303,6 +327,5 @@ unique_timer timer_manager::create_unique_timer(task_executor& exec)
303327

304328
size_t timer_manager::nof_timers() const
305329
{
306-
std::lock_guard<std::mutex> lock(free_list_mutex);
307-
return timer_list.size() - free_list.size();
330+
return timers.size() - std::min(timers.size(), timer_pool->size_approx());
308331
}

0 commit comments

Comments
 (0)