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