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/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