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