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