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