userver: userver/engine/wait_any.hpp Source File
Loading...
Searching...
No Matches
wait_any.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/engine/wait_any.hpp
4/// @brief Provides engine::WaitAny, engine::WaitAnyFor, engine::WaitAnyUntil, engine::WaitAnyContext and
5/// engine::MakeWaitAny
6
7#include <chrono>
8#include <cstdint>
9#include <optional>
10#include <vector>
11
12#include <boost/smart_ptr/intrusive_ptr.hpp>
13
14#include <userver/engine/awaitable.hpp>
15#include <userver/engine/deadline.hpp>
16#include <userver/utils/expected.hpp>
17#include <userver/utils/fast_pimpl.hpp>
18#include <userver/utils/meta.hpp>
19#include <userver/utils/span.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23namespace engine {
24
25/// @ingroup userver_concurrency
26///
27/// @deprecated `WaitAny` is deprecated. Please, prefer @ref MakeWaitAny + @ref WaitAnyContext::Wait.
28/// @brief Waits for the completion of any of the specified tasks or the
29/// cancellation of the caller.
30///
31/// Could be used to get the ready HTTP requests ASAP:
32/// @snippet src/clients/http/client_wait_test.cpp HTTP Client - waitany
33///
34/// Works with different types of tasks and futures:
35/// @snippet src/engine/wait_any_test.cpp sample waitany
36///
37/// @param tasks either a single container, or a pack of future-like elements.
38/// @returns the index of the completed task, or `std::nullopt` if there are no
39/// completed tasks (possible if current task was cancelled).
40template <typename... Tasks>
41std::optional<std::size_t> WaitAny(Tasks&... tasks);
42
43/// @ingroup userver_concurrency
44///
45/// @deprecated `WaitAnyFor` is deprecated. Please, prefer @ref MakeWaitAny + @ref WaitAnyContext::WaitFor.
46/// @overload std::optional<std::size_t> WaitAny(Tasks&... tasks)
47template <typename... Tasks, typename Rep, typename Period>
48std::optional<std::size_t> WaitAnyFor(const std::chrono::duration<Rep, Period>& duration, Tasks&... tasks);
49
50/// @ingroup userver_concurrency
51///
52/// @deprecated `WaitAnyUntil` is deprecated. Please, prefer @ref MakeWaitAny + @ref WaitAnyContext::WaitUntil.
53/// @overload std::optional<std::size_t> WaitAny(Tasks&... tasks)
54template <typename... Tasks, typename Clock, typename Duration>
55std::optional<std::size_t> WaitAnyUntil(const std::chrono::time_point<Clock, Duration>& until, Tasks&... tasks);
56
57/// @ingroup userver_concurrency
58///
59/// @deprecated `WaitAnyUntil` is deprecated. Please, prefer @ref MakeWaitAny + @ref WaitAnyContext::WaitUntil.
60/// @overload std::optional<std::size_t> WaitAny(Tasks&... tasks)
61template <typename... Tasks>
62std::optional<std::size_t> WaitAnyUntil(Deadline, Tasks&... tasks);
63
64template <typename... Tasks>
65std::optional<std::size_t> WaitAny(Tasks&... tasks) {
66 return engine::WaitAnyUntil(Deadline{}, tasks...);
67}
68
69template <typename... Tasks, typename Rep, typename Period>
70std::optional<std::size_t> WaitAnyFor(const std::chrono::duration<Rep, Period>& duration, Tasks&... tasks) {
71 return engine::WaitAnyUntil(Deadline::FromDuration(duration), tasks...);
72}
73
74template <typename... Tasks, typename Clock, typename Duration>
75std::optional<std::size_t> WaitAnyUntil(const std::chrono::time_point<Clock, Duration>& until, Tasks&... tasks) {
76 return engine::WaitAnyUntil(Deadline::FromTimePoint(until), tasks...);
77}
78
79namespace impl {
80
81std::optional<std::size_t> DoWaitAny(utils::span<AwaitableToken> target_tokens, Deadline deadline);
82
83template <typename Container>
84std::optional<std::size_t> WaitAnyFromContainer(Deadline deadline, Container& tasks) {
85 const auto size = std::size(tasks);
86 std::vector<AwaitableToken> targets;
87 targets.reserve(size);
88
89 for (auto& task : tasks) {
90 static_assert(engine::Awaitable<decltype(task)>, "Tasks must be awaitable");
91 targets.push_back(task.GetAwaitableToken());
92 }
93
94 return DoWaitAny(targets, deadline);
95}
96
97template <typename... Tasks>
98std::optional<std::size_t> WaitAnyFromTasks(Deadline deadline, Tasks&... tasks) {
99 static_assert((true && ... && engine::Awaitable<Tasks>), "Tasks must be awaitable");
100 AwaitableToken wa_elements[]{tasks.GetAwaitableToken()...};
101 return DoWaitAny(wa_elements, deadline);
102}
103
104inline std::optional<std::size_t> WaitAnyFromTasks(Deadline) { return {}; }
105
106} // namespace impl
107
108template <typename... Tasks>
109std::optional<std::size_t> WaitAnyUntil(Deadline deadline, Tasks&... tasks) {
110 if constexpr (meta::impl::IsSingleRange<Tasks...>) {
111 return impl::WaitAnyFromContainer(deadline, tasks...);
112 } else {
113 return impl::WaitAnyFromTasks(deadline, tasks...);
114 }
115}
116
117/// @ingroup userver_concurrency
118///
119/// @brief The reason why @ref WaitAnyContext::Wait and friends did not return the index of a completed awaitable.
120enum class WaitAnyError : std::uint8_t {
121 kEmpty, ///< there were no awaitables to wait for
122 kCancelled, ///< the wait operation was interrupted by task cancellation
123 kTimeout, ///< the wait operation timed out (see also @ref FutureStatus)
124};
125
126/// @ingroup userver_concurrency
127///
128/// @brief Stores a set of awaitables and allows waiting for completion of any of the stored awaitables.
129///
130/// Works with different types of awaitables:
131/// @snippet src/engine/wait_any_test.cpp sample MakeWaitAny
132///
133/// No methods (except .dtor) should be called on a moved-out instance.
134class WaitAnyContext final {
135public:
136 WaitAnyContext();
137 ~WaitAnyContext() noexcept;
138 WaitAnyContext(WaitAnyContext&&) noexcept;
139 WaitAnyContext& operator=(WaitAnyContext&& other) noexcept;
140 WaitAnyContext(const WaitAnyContext&) = delete;
141 WaitAnyContext& operator=(const WaitAnyContext&) = delete;
142
143 /// @brief Appends a single awaitable to the context.
144 ///
145 /// The appended awaitable will be assigned the next id from the auto-incrementing counter;
146 /// see also @ref GetNextId.
147 void Append(engine::Awaitable auto& awaitable);
148
149 /// @brief Appends the given awaitable to the context with an explicit id.
150 ///
151 /// The id is an arbitrary number that will be returned by @ref Wait.
152 /// It does not have to be sequential or starting from 0. Duplicate ids are allowed.
153 ///
154 /// @ref GetNextId will be unaffected by this call. When mixing the id-less @ref Append
155 /// and this overload, the id-less `Append` maintains a sequence only within its own calls.
156 ///
157 /// Works well together with @ref utils::SlotMap to process and erase tasks in completion order:
158 /// @snippet src/engine/wait_any_test.cpp sample WaitAnyContext SlotMap
159 ///
160 /// @param id the id that will be returned by @ref Wait.
161 /// @param awaitable the awaitable to append.
163
164 /// @brief Waits either for the completion of any of the awaitables stored in the context
165 /// or for the cancellation of the caller.
166 ///
167 /// The completed awaitable is dropped out of the context.
168 ///
169 /// @returns the index of the completed awaitable, or a @ref WaitAnyError if there are no
170 /// completed awaitables (possible if current task was cancelled or the context is empty).
172
173 /// @brief Waits for the completion of any of the awaitables stored in the context
174 /// or cancellation of the caller or deadline expiration.
175 ///
176 /// The completed awaitable is dropped from the context.
177 ///
178 /// @returns the index of the completed awaitable, or a @ref WaitAnyError if there are no
179 /// completed awaitables (possible if current task was cancelled, the deadline was reached,
180 /// or the context is empty).
181 utils::expected<std::uint64_t, WaitAnyError> WaitUntil(Deadline deadline);
182
183 /// @overload
184 template <typename Rep, typename Period>
185 utils::expected<std::uint64_t, WaitAnyError> WaitFor(const std::chrono::duration<Rep, Period>& duration) {
186 return WaitUntil(Deadline::FromDuration(duration));
187 }
188
189 /// @overload
190 template <typename Clock, typename Duration>
191 utils::expected<std::uint64_t, WaitAnyError> WaitUntil(const std::chrono::time_point<Clock, Duration>& until) {
192 return WaitUntil(Deadline::FromTimePoint(until));
193 }
194
195 /// @brief Returns the number of awaitables stored in the context.
196 ///
197 /// These are awaitables which have been `Append`ed, but not yet retrieved by `Wait` or `WaitUntil`.
198 /// The awaitables that have already been reported using `Wait*` are dropped out.
199 std::size_t GetSize() const noexcept;
200
201 /// @brief Returns the next id that will be assigned by the next @ref Append call.
202 ///
203 /// It could be used to calculate ids of awaitables appended via @ref Append call.
204 std::uint64_t GetNextId() const noexcept;
205
206private:
207 class Impl;
208
209 void AppendToken(engine::AwaitableToken awaitable);
210
211 void AppendToken(std::uint64_t id, engine::AwaitableToken awaitable);
212
213 boost::intrusive_ptr<Impl> impl_;
214};
215
216void WaitAnyContext::Append(engine::Awaitable auto& awaitable) { AppendToken(awaitable.GetAwaitableToken()); }
217
218void WaitAnyContext::Append(std::uint64_t id, engine::Awaitable auto& awaitable) {
219 AppendToken(id, awaitable.GetAwaitableToken());
220}
221
222/// @ingroup userver_concurrency
223///
224/// @brief Produces a WaitAnyContext for the given awaitables and sequences of awaitables.
225///
226/// Each passed awaitable could be either a single awaitable or a container of awaitables.
227/// In the latter case all awaitables from the container are appended to the context.
228/// The stored awaitables will have ids [0, GetNextId() - 1].
229template <typename... Awaitables>
230WaitAnyContext MakeWaitAny(Awaitables&... awaitables) {
231 auto context = WaitAnyContext();
232 [[maybe_unused]] const auto append_one = [&context](auto& arg) {
233 if constexpr (meta::IsRange<decltype(arg)>) {
234 for (auto& awaitable : arg) {
235 context.Append(awaitable);
236 }
237 } else {
238 context.Append(arg);
239 }
240 };
241 (append_one(awaitables), ...);
242 return context;
243}
244
245} // namespace engine
246
247USERVER_NAMESPACE_END