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