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