libdisplaydevice latest
C++ library to modify display devices.
retry_scheduler.h
Go to the documentation of this file.
1
5#pragma once
6
7// system includes
8#include <algorithm>
9#include <condition_variable>
10#include <functional>
11#include <memory>
12#include <thread>
13
14// local includes
15#include "logging.h"
16
17namespace display_device {
25 class SchedulerStopToken final {
26 public:
31 explicit SchedulerStopToken(std::function<void()> cleanup);
32
37
42
47
51 void requestStop();
52
57 [[nodiscard]] bool stopRequested() const;
58
59 private:
60 bool m_stop_requested {false};
61 std::function<void()> m_cleanup;
62 };
63
64 namespace detail {
69 template<class FunctionT>
70 concept OptionalFunction = requires(FunctionT exec_fn) {
71 static_cast<bool>(exec_fn);
72 };
73
77 template<class T, bool AddConst>
78 struct AutoConst;
79
80 template<class T>
81 struct AutoConst<T, false> {
82 using type = T;
83 };
84
85 template<class T>
86 struct AutoConst<T, true> {
87 using type = std::add_const_t<T>;
88 };
89
93 template<class T, bool AddConst>
95
100 template<class T, class FunctionT>
101 concept ExecuteWithoutStopToken = requires(FunctionT exec_fn, T &value) { exec_fn(value); };
102
107 template<class T, class FunctionT>
108 concept ExecuteWithStopToken = requires(FunctionT exec_fn, T &value, SchedulerStopToken &token) {
109 exec_fn(value, token);
110 };
111
115 template<class T, class FunctionT>
117 } // namespace detail
118
126 enum class Execution {
127 Immediate,
130 };
131
132 std::vector<std::chrono::milliseconds> m_sleep_durations;
134 };
135
142 template<class T>
143 class RetryScheduler final {
144 public:
149 explicit RetryScheduler(std::unique_ptr<T> iface):
150 m_iface {iface ? std::move(iface) : throw std::logic_error {"Nullptr interface provided in RetryScheduler!"}},
151 m_thread {[this]() {
152 std::unique_lock lock {m_mutex};
153 while (m_keep_alive) {
154 m_syncing_thread = false;
155 if (auto duration {takeNextDuration(m_sleep_durations)}; duration > std::chrono::milliseconds::zero()) {
156 // We're going to sleep until manually woken up or the time elapses.
157 m_sleep_cv.wait_for(lock, duration, [this]() {
158 return m_syncing_thread;
159 });
160 } else {
161 // We're going to sleep until manually woken up.
162 m_sleep_cv.wait(lock, [this]() {
163 return m_syncing_thread;
164 });
165 }
166
167 if (m_syncing_thread) {
168 // Thread was waken up to sync sleep time or to be stopped.
169 continue;
170 }
171
172 try {
173 SchedulerStopToken scheduler_stop_token {[&]() {
174 clearThreadLoopUnlocked();
175 }};
176 m_retry_function(*m_iface, scheduler_stop_token);
177 continue;
178 } catch (const std::exception &error) {
179 DD_LOG(error) << "Exception thrown in the RetryScheduler thread. Stopping scheduler. Error:\n"
180 << error.what();
181 }
182
183 clearThreadLoopUnlocked();
184 }
185 }} {
186 }
187
192 {
193 std::lock_guard lock {m_mutex};
194 m_keep_alive = false;
195 syncThreadUnlocked();
196 }
197
198 m_thread.join();
199 }
200
219 void schedule(std::function<void(T &, SchedulerStopToken &stop_token)> exec_fn, const SchedulerOptions &options) {
220 if (!exec_fn) {
221 throw std::logic_error {"Empty callback function provided in RetryScheduler::schedule!"};
222 }
223
224 if (options.m_sleep_durations.empty()) {
225 throw std::logic_error {"At least 1 sleep duration must be specified in RetryScheduler::schedule!"};
226 }
227
228 if (std::ranges::any_of(options.m_sleep_durations, [&](const auto &duration) {
229 return duration == std::chrono::milliseconds::zero();
230 })) {
231 throw std::logic_error {"All of the durations specified in RetryScheduler::schedule must be larger than a 0!"};
232 }
233
234 std::lock_guard lock {m_mutex};
235 SchedulerStopToken stop_token {[&]() {
236 stopUnlocked();
237 }};
238
239 // We are catching the exception here instead of propagating to have
240 // similar try...catch login as in the scheduler thread.
241 try {
242 auto sleep_durations = options.m_sleep_durations;
245 std::this_thread::sleep_for(takeNextDuration(sleep_durations));
246 }
247
248 exec_fn(*m_iface, stop_token);
249 }
250
251 if (!stop_token.stopRequested()) {
252 m_retry_function = std::move(exec_fn);
253 m_sleep_durations = std::move(sleep_durations);
254 syncThreadUnlocked();
255 }
256 } catch (const std::exception &error) {
257 stop_token.requestStop();
258 DD_LOG(error) << "Exception thrown in the RetryScheduler::schedule. Stopping scheduler. Error:\n"
259 << error.what();
260 }
261 }
262
266 template<class FunctionT>
267 auto execute(FunctionT &&exec_fn) {
268 return executeImpl(*this, std::forward<FunctionT>(exec_fn));
269 }
270
274 template<class FunctionT>
275 auto execute(FunctionT &&exec_fn) const {
276 return executeImpl(*this, std::forward<FunctionT>(exec_fn));
277 }
278
283 [[nodiscard]] bool isScheduled() const {
284 return static_cast<bool>(m_retry_function);
285 }
286
290 void stop() {
291 std::lock_guard lock {m_mutex};
292 stopUnlocked();
293 }
294
295 private:
296 static std::chrono::milliseconds takeNextDuration(std::vector<std::chrono::milliseconds> &durations) {
297 if (durations.size() > 1) {
298 const auto front_it {std::begin(durations)};
299 const auto front_value {*front_it};
300 durations.erase(front_it);
301 return front_value;
302 }
303
304 return durations.empty() ? std::chrono::milliseconds::zero() : durations.back();
305 }
306
343 static auto executeImpl(auto &self, auto &&exec_fn)
344 requires detail::ExecuteCallbackLike<T, decltype(exec_fn)>
345 {
346 using FunctionT = decltype(exec_fn);
347 constexpr bool IsConst = std::is_const_v<std::remove_reference_t<decltype(self)>>;
348
349 if constexpr (detail::OptionalFunction<FunctionT>) {
350 if (!exec_fn) {
351 throw std::logic_error {"Empty callback function provided in RetryScheduler::execute!"};
352 }
353 }
354
355 std::lock_guard lock {self.m_mutex};
356 detail::auto_const_t<std::decay_t<T>, IsConst> &iface_ref {*self.m_iface};
357 if constexpr (detail::ExecuteWithStopToken<T, FunctionT>) {
358 detail::auto_const_t<SchedulerStopToken, IsConst> stop_token {[&self]() {
359 if constexpr (!IsConst) {
360 self.stopUnlocked();
361 }
362 }};
363 return std::forward<FunctionT>(exec_fn)(iface_ref, stop_token);
364 } else {
365 return std::forward<FunctionT>(exec_fn)(iface_ref);
366 }
367 }
368
372 void clearThreadLoopUnlocked() {
373 m_sleep_durations = {};
374 m_retry_function = nullptr;
375 }
376
380 void syncThreadUnlocked() {
381 m_syncing_thread = true;
382 m_sleep_cv.notify_one();
383 }
384
388 void stopUnlocked() {
389 if (isScheduled()) {
390 clearThreadLoopUnlocked();
391 syncThreadUnlocked();
392 }
393 }
394
395 std::unique_ptr<T> m_iface;
396 std::vector<std::chrono::milliseconds> m_sleep_durations;
397 std::function<void(T &, SchedulerStopToken &)> m_retry_function {nullptr};
399 mutable std::mutex m_mutex {};
400 std::condition_variable m_sleep_cv {};
401 bool m_syncing_thread {false};
402 bool m_keep_alive {true};
404 // Always the last in the list so that all the members are already initialized!
405 std::thread m_thread;
406 };
407} // namespace display_device
A wrapper class around an interface that provides a thread-safe access to the interface and allows to...
Definition retry_scheduler.h:143
auto execute(FunctionT &&exec_fn)
A non-const variant of the executeImpl method. See it for details.
Definition retry_scheduler.h:267
RetryScheduler(std::unique_ptr< T > iface)
Default constructor.
Definition retry_scheduler.h:149
void schedule(std::function< void(T &, SchedulerStopToken &stop_token)> exec_fn, const SchedulerOptions &options)
Schedule an interface executor function to be executed at specified intervals.
Definition retry_scheduler.h:219
bool isScheduled() const
Check whether anything is scheduled for execution.
Definition retry_scheduler.h:283
~RetryScheduler()
A destructor that gracefully shuts down the thread.
Definition retry_scheduler.h:191
auto execute(FunctionT &&exec_fn) const
A const variant of the executeImpl method. See it for details.
Definition retry_scheduler.h:275
void stop()
Stop the scheduled function - will no longer be execute once THIS method returns.
Definition retry_scheduler.h:290
A convenience class for stopping the RetryScheduler.
Definition retry_scheduler.h:25
SchedulerStopToken(std::function< void()> cleanup)
Default constructor.
Definition retry_scheduler.cpp:9
SchedulerStopToken & operator=(const SchedulerStopToken &)=delete
Deleted copy operator.
SchedulerStopToken(const SchedulerStopToken &)=delete
Deleted copy constructor.
bool stopRequested() const
Check if stop was requested.
Definition retry_scheduler.cpp:23
void requestStop()
Request the scheduler to be stopped.
Definition retry_scheduler.cpp:19
~SchedulerStopToken()
Executes cleanup logic if scheduler stop was requested.
Definition retry_scheduler.cpp:13
Check if the function signature matches the acceptable signature for RetryScheduler::execute.
Definition retry_scheduler.h:116
Check if the function signature matches the acceptable signature for RetryScheduler::execute with a s...
Definition retry_scheduler.h:108
Check if the function signature matches the acceptable signature for RetryScheduler::execute without ...
Definition retry_scheduler.h:101
Given that we know that we are dealing with a function, check if it is an optional function (like std...
Definition retry_scheduler.h:70
Declarations for the logging utility.
#define DD_LOG(level)
Helper MACRO that disables output string computation if log level is not enabled.
Definition logging.h:153
typename AutoConst< T, AddConst >::type auto_const_t
A convenience template helper for adding const to the type.
Definition retry_scheduler.h:94
Scheduler options to be used when scheduling executor function.
Definition retry_scheduler.h:122
Execution m_execution
Executor's execution logic.
Definition retry_scheduler.h:133
std::vector< std::chrono::milliseconds > m_sleep_durations
Specifies for long the scheduled thread sleeps before invoking executor. Last duration is reused inde...
Definition retry_scheduler.h:132
Execution
Defines the executor's execution logic when it is scheduled.
Definition retry_scheduler.h:126
@ ImmediateWithSleep
The first sleep duration is TAKEN from m_sleep_durations and the calling thread is put to sleep....
@ Immediate
Executor is executed in the calling thread immediately and scheduled afterward.
@ ScheduledOnly
Executor is executed in the thread only.
A convenience template struct helper for adding const to the type.
Definition retry_scheduler.h:78