userver: userver/utils/periodic_task.hpp Source File
Loading...
Searching...
No Matches
periodic_task.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/periodic_task.hpp
4/// @brief @copybrief utils::PeriodicTask
5
6#include <chrono>
7#include <functional>
8#include <optional>
9#include <random>
10#include <string>
11
12#include <userver/engine/single_consumer_event.hpp>
13#include <userver/engine/task/task_with_result.hpp>
14#include <userver/rcu/rcu.hpp>
15#include <userver/utils/assert.hpp>
16#include <userver/utils/flags.hpp>
17
18// TODO remove extra include
19#include <userver/engine/condition_variable.hpp>
20#include <userver/testsuite/periodic_task_control.hpp>
21#include <userver/tracing/span.hpp>
22
23USERVER_NAMESPACE_BEGIN
24
25namespace testsuite {
26class PeriodicTaskControl;
27} // namespace testsuite
28
29namespace utils {
30
31/// @ingroup userver_concurrency
32///
33/// @brief Task that periodically runs a user callback. Callback is started
34/// after the previous callback execution is finished every `period + A - B`,
35/// where:
36/// * `A` is `+/- distribution * rand(0.0, 1.0)` if Flags::kChaotic flag is set,
37/// otherwise is `0`;
38/// * `B` is the time of previous callback execution if Flags::kStrong flag is
39/// set, otherwise is `0`;
40///
41/// TaskProcessor to execute the callback and many other options are specified
42/// in PeriodicTask::Settings.
43class PeriodicTask final {
44public:
45 enum class Flags {
46 /// None of the below flags
47 kNone = 0,
48 /// Immediately call a function
49 kNow = 1 << 0,
50 /// Account function call time as a part of wait period
51 kStrong = 1 << 1,
52 /// Randomize wait period (+-25% by default)
53 kChaotic = 1 << 2,
54 /// @deprecated Does nothing, PeriodicTask is always spawned as
55 /// `engine::Task::Importance::kCritical`.
56 /// @note Although this periodic task cannot be cancelled due to
57 /// system overload, it's cancelled upon calling `Stop`.
58 /// Subtasks that may be spawned in the callback
59 /// are not critical by default and may be cancelled as usual.
60 kCritical = 1 << 4,
61 };
62
63 /// Configuration parameters for PeriodicTask.
64 struct Settings final {
65 static constexpr uint8_t kDistributionPercent = 25;
66
67 constexpr /*implicit*/ Settings(
68 std::chrono::milliseconds period,
69 utils::Flags<Flags> flags = {},
70 logging::Level span_level = logging::Level::kInfo
71 )
72 : Settings(period, kDistributionPercent, flags, span_level) {}
73
74 constexpr Settings(
75 std::chrono::milliseconds period,
76 std::chrono::milliseconds distribution,
77 utils::Flags<Flags> flags = {},
78 logging::Level span_level = logging::Level::kInfo
79 )
80 : period(period), distribution(distribution), flags(flags), span_level(span_level) {
81 UASSERT(distribution <= period);
82 }
83
84 constexpr Settings(
85 std::chrono::milliseconds period,
86 uint8_t distribution_percent,
87 utils::Flags<Flags> flags = {},
88 logging::Level span_level = logging::Level::kInfo
89 )
90 : Settings(period, period * distribution_percent / 100, flags, span_level) {
91 UASSERT(distribution_percent <= 100);
92 }
93
94 template <class Rep, class Period>
95 constexpr /*implicit*/ Settings(std::chrono::duration<Rep, Period> period)
96 : Settings(period, kDistributionPercent, {}, logging::Level::kInfo) {}
97
98 bool operator==(const Settings& other) const noexcept;
99 bool operator!=(const Settings& other) const noexcept;
100
101 // Note: Tidy requires us to explicitly initialize these fields, although
102 // the initializers are never used.
103
104 /// @brief Period for the task execution. Task is repeated every
105 /// `(period +/- distribution) - time of previous execution`
106 std::chrono::milliseconds period{};
107
108 /// @brief Jitter for task repetitions. If kChaotic is set in `flags`
109 /// the task is repeated every
110 /// `(period +/- distribution) - time of previous execution`
111 std::chrono::milliseconds distribution{};
112
113 /// @brief Used instead of `period` in case of exception, if set.
114 std::optional<std::chrono::milliseconds> exception_period;
115
116 /// @brief Flags that control the behavior of PeriodicTask.
117 utils::Flags<Flags> flags{};
118
119 /// @brief tracing::Span that measures each execution of the task
120 /// uses this logging level.
122
123 /// @brief TaskProcessor to execute the task. If nullptr then the
124 /// PeriodicTask::Start() calls engine::current_task::GetTaskProcessor()
125 /// to get the TaskProcessor.
126 engine::TaskProcessor* task_processor{nullptr};
127 };
128
129 /// Signature of the task to be executed each period.
130 using Callback = std::function<void()>;
131
132 /// Default constructor that does nothing.
134
135 PeriodicTask(PeriodicTask&&) = delete;
136 PeriodicTask(const PeriodicTask&) = delete;
137
138 /// Constructs the periodic task and calls Start()
139 PeriodicTask(std::string name, Settings settings, Callback callback);
140
141 /// Stops the periodic execution of previous task and starts the periodic
142 /// execution of the new task.
143 void Start(std::string name, Settings settings, Callback callback);
144
145 ~PeriodicTask();
146
147 /// @brief Stops the PeriodicTask. If a Step() is in progress, cancels it and
148 /// waits for its completion.
149 /// @warning PeriodicTask must be stopped before the callback becomes invalid.
150 /// E.g. if your class X stores PeriodicTask and the callback is class' X
151 /// method, you have to explicitly stop PeriodicTask in ~X() as after ~X()
152 /// exits the object is destroyed and using X's 'this' in callback is UB.
153 void Stop() noexcept;
154
155 /// Set all settings except flags. All flags must be set at the start.
156 void SetSettings(Settings settings);
157
158 /// @brief Non-blocking force next iteration.
159 ///
160 /// Returns immediately, without waiting for Step() to finish.
161 ///
162 /// - If PeriodicTask isn't running, then a Step() will be performed at the
163 /// start.
164 /// - If the PeriodicTask is waiting for the next iteration, then the wait is
165 /// interrupted and the next Step() is executed.
166 /// - If Step() is being executed, the current iteration will be completed and
167 /// only after that a new iteration will be called. Reason: the current
168 /// iteration is considered to be using stale data.
169 ///
170 /// @note If 'ForceStepAsync' is called multiple times while Step() is
171 /// being executed, all events will be conflated (one extra Step() call will
172 /// be executed).
174
175 /// Force next DoStep() iteration. It is guaranteed that there is at least one
176 /// call to DoStep() during SynchronizeDebug() execution. DoStep() is executed
177 /// as usual in the PeriodicTask's task (NOT in current task).
178 /// @param preserve_span run periodic task current span if true. It's here for
179 /// backward compatibility with existing tests. Will be removed in
180 /// TAXIDATA-1499.
181 /// @returns true if task was successfully executed.
182 /// @note On concurrent invocations, the task is guaranteed to be invoked
183 /// serially, one time after another.
184 bool SynchronizeDebug(bool preserve_span = false);
185
186 /// Skip Step() calls from loop until ResumeDebug() is called. If DoStep()
187 /// is executing, wait its completion, for a potentially long time.
188 /// The purpose is to control task execution from tests.
190
191 /// Stop skipping Step() calls from loop. Returns without waiting for
192 /// DoStep() call. The purpose is to control task execution from tests.
194
195 /// Checks if a periodic task (not a single iteration only) is running.
196 /// It may be in a callback execution or sleeping between callbacks.
197 bool IsRunning() const;
198
199 /// Make this periodic task available for testsuite. Testsuite provides a way
200 /// to call it directly from testcase.
201 void RegisterInTestsuite(USERVER_NAMESPACE::testsuite::PeriodicTaskControl& periodic_task_control);
202
203 /// Get current settings. Note that they might become stale very quickly.
204 Settings GetCurrentSettings() const;
205
206private:
207 enum class SuspendState { kRunning, kSuspended };
208
209 void DoStart();
210
211 void Run();
212
213 bool Step();
214
215 bool StepDebug(bool preserve_span);
216
217 bool DoStep();
218
219 std::chrono::milliseconds MutatePeriod(std::chrono::milliseconds period);
220
221 std::string_view GetName() const noexcept;
222
223 std::string name_;
224 std::atomic<bool> is_name_set_{false};
225 Callback callback_;
226 engine::TaskWithResult<void> task_;
227 rcu::Variable<Settings> settings_;
228 engine::SingleConsumerEvent changed_event_;
229 std::atomic<bool> should_force_step_{false};
230 std::optional<std::minstd_rand> mutate_period_random_;
231
232 // For kNow only
233 engine::Mutex step_mutex_;
234 std::atomic<SuspendState> suspend_state_;
235
236 std::optional<USERVER_NAMESPACE::testsuite::PeriodicTaskRegistrationHolder> registration_holder_;
237};
238
239} // namespace utils
240
241USERVER_NAMESPACE_END