Skip to content

Commit 07fef67

Browse files
committed
feat engine: return failure reason from WaitAnyContext::Wait
* Changed @ref engine::WaitAnyContext::Wait, `WaitUntil` and `WaitFor` to return `utils::expected<std::uint64_t, engine::WaitAnyError>` instead of `std::optional<std::uint64_t>`, distinguishing cancellation, timeout and empty-context cases. Migration: `result.has_value()` and `*result` keep working as before; replace comparisons with `std::nullopt` with `result.has_value()`, and use `result.error()` to inspect the specific @ref engine::WaitAnyError reason. * Added @ref engine::SingleConsumerEvent::WaitUntil(Deadline), same as `WaitForEventUntil`, but returning the precise @ref engine::FutureStatus reason (`kReady`/`kTimeout`/`kCancelled`) instead of just a `bool` commit_hash:143825523261cba67af573b743f92f7e8f62ddd5
1 parent 9661907 commit 07fef67

7 files changed

Lines changed: 108 additions & 53 deletions

File tree

core/include/userver/engine/single_consumer_event.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <userver/engine/awaitable.hpp>
99
#include <userver/engine/deadline.hpp>
10+
#include <userver/engine/future_status.hpp>
1011
#include <userver/utils/fast_pimpl.hpp>
1112

1213
USERVER_NAMESPACE_BEGIN
@@ -57,6 +58,16 @@ class SingleConsumerEvent final {
5758
/// @overload bool WaitForEvent()
5859
[[nodiscard]] bool WaitForEventUntil(Deadline);
5960

61+
/// @brief Waits until the event is in a signaled state, same as @ref WaitForEventUntil, but gives the precise
62+
/// reason of a failure instead of just `false`.
63+
///
64+
/// If the event is auto-resetting, clears the signal flag upon waking up. If already in a signaled state,
65+
/// does the same without sleeping.
66+
///
67+
/// @return `FutureStatus::kReady` if the event signaled, `FutureStatus::kCancelled` if the current task was
68+
/// cancelled, `FutureStatus::kTimeout` if the deadline was reached.
69+
[[nodiscard]] FutureStatus WaitUntil(Deadline deadline);
70+
6071
/// @brief Works like `std::condition_variable::wait_until`. Waits until
6172
/// @a stop_waiting becomes `true`, and we are notified via `Send`.
6273
///

core/include/userver/engine/wait_any.hpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <userver/engine/awaitable.hpp>
1515
#include <userver/engine/deadline.hpp>
16+
#include <userver/utils/expected.hpp>
1617
#include <userver/utils/fast_pimpl.hpp>
1718
#include <userver/utils/meta.hpp>
1819
#include <userver/utils/span.hpp>
@@ -113,6 +114,15 @@ std::optional<std::size_t> WaitAnyUntil(Deadline deadline, Tasks&... tasks) {
113114
}
114115
}
115116

117+
/// @ingroup userver_concurrency
118+
///
119+
/// @brief The reason why @ref WaitAnyContext::Wait and friends did not return the index of a completed awaitable.
120+
enum 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+
116126
/// @ingroup userver_concurrency
117127
///
118128
/// @brief Stores a set of awaitables and allows waiting for completion of any of the stored awaitables.
@@ -154,30 +164,32 @@ class WaitAnyContext final {
154164
/// @brief Waits either for the completion of any of the awaitables stored in the context
155165
/// or for the cancellation of the caller.
156166
///
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();
167+
/// @returns the index of the completed awaitable, or a @ref WaitAnyError if there are no
168+
/// completed awaitables (possible if current task was cancelled or the context is empty).
169+
utils::expected<std::uint64_t, WaitAnyError> Wait();
160170

161171
/// @brief Waits for the completion of any of the awaitables stored in the context
162172
/// or cancellation of the caller or deadline expiration.
163173
///
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);
174+
/// @returns the index of the completed awaitable, or a @ref WaitAnyError if there are no
175+
/// completed awaitables (possible if current task was cancelled, the deadline was reached,
176+
/// or the context is empty).
177+
utils::expected<std::uint64_t, WaitAnyError> WaitUntil(Deadline deadline);
167178

168179
/// @brief Waits for the completion of any of the awaitables stored in the context
169180
/// or cancellation of the caller or expiration of the given duration.
170181
///
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).
182+
/// @returns the index of the completed awaitable, or a @ref WaitAnyError if there are no
183+
/// completed awaitables (possible if current task was cancelled, the given duration has passed,
184+
/// or the context is empty).
173185
template <typename Rep, typename Period>
174-
std::optional<std::uint64_t> WaitFor(const std::chrono::duration<Rep, Period>& duration) {
186+
utils::expected<std::uint64_t, WaitAnyError> WaitFor(const std::chrono::duration<Rep, Period>& duration) {
175187
return WaitUntil(Deadline::FromDuration(duration));
176188
}
177189

178-
/// @overload std::optional<std::size_t> Wait()
190+
/// @overload utils::expected<std::uint64_t, WaitAnyError> Wait()
179191
template <typename Clock, typename Duration>
180-
std::optional<std::uint64_t> WaitUntil(const std::chrono::time_point<Clock, Duration>& until) {
192+
utils::expected<std::uint64_t, WaitAnyError> WaitUntil(const std::chrono::time_point<Clock, Duration>& until) {
181193
return WaitUntil(Deadline::FromTimePoint(until));
182194
}
183195

core/src/engine/single_consumer_event.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <userver/engine/single_consumer_event.hpp>
22

3+
#include <engine/impl/future_utils.hpp>
34
#include <engine/impl/wait_list_light.hpp>
45
#include <engine/task/task_context.hpp>
56
#include <userver/engine/awaitable.hpp>
@@ -42,26 +43,29 @@ bool SingleConsumerEvent::IsAutoReset() const noexcept { return impl_->is_auto_r
4243

4344
bool SingleConsumerEvent::WaitForEvent() { return WaitForEventUntil(Deadline{}); }
4445

45-
bool SingleConsumerEvent::WaitForEventUntil(Deadline deadline) {
46+
bool SingleConsumerEvent::WaitForEventUntil(Deadline deadline) { return WaitUntil(deadline) == FutureStatus::kReady; }
47+
48+
FutureStatus SingleConsumerEvent::WaitUntil(Deadline deadline) {
4649
// optimistic path
4750
if (IsReady()) {
4851
if (IsAutoReset()) {
4952
Reset();
5053
}
51-
return true;
54+
return FutureStatus::kReady;
5255
}
5356

5457
auto& awaiter = current_task::GetCurrentTaskContext();
5558
const auto wakeup_source = awaiter.Sleep(*impl_, deadline);
56-
if (!impl::HasWaitSucceeded(wakeup_source)) {
57-
return false;
59+
const auto status = impl::ToFutureStatus(wakeup_source);
60+
if (status != FutureStatus::kReady) {
61+
return status;
5862
}
5963

6064
UASSERT(IsReady());
6165
if (IsAutoReset()) {
6266
Reset();
6367
}
64-
return true;
68+
return FutureStatus::kReady;
6569
}
6670

6771
void SingleConsumerEvent::Reset() noexcept {

core/src/engine/single_consumer_event_test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ UTEST(SingleConsumerEvent, WaitFailed) {
9292
EXPECT_FALSE(event.WaitForEventUntil(engine::Deadline::Passed()));
9393
}
9494

95+
UTEST(SingleConsumerEvent, WaitUntilAllStatuses) {
96+
engine::SingleConsumerEvent event;
97+
98+
// kReady: the event is already signaled.
99+
event.Send();
100+
EXPECT_EQ(event.WaitUntil(engine::Deadline{}), engine::FutureStatus::kReady);
101+
102+
// kTimeout: the event is not signaled and the deadline is reached.
103+
EXPECT_EQ(event.WaitUntil(engine::Deadline::Passed()), engine::FutureStatus::kTimeout);
104+
105+
// kCancelled: the waiting task is cancelled before the event is signaled.
106+
auto waiter = engine::CriticalAsyncNoTracing([&event] {
107+
return event.WaitUntil(engine::Deadline::FromDuration(utest::kMaxTestWaitTime));
108+
});
109+
waiter.SyncCancel();
110+
UEXPECT_NO_THROW(EXPECT_EQ(waiter.Get(), engine::FutureStatus::kCancelled));
111+
}
112+
95113
UTEST(SingleConsumerEvent, SendAndWait2) {
96114
engine::SingleConsumerEvent event;
97115
auto task = engine::AsyncNoTracing([&event]() {

core/src/engine/wait_any.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class WaitAnyContext::Impl final : public utils::impl::IntrusiveRefCounterOne<Im
7676

7777
void Append(std::uint64_t id, engine::AwaitableToken awaitable);
7878

79-
std::optional<std::uint64_t> WaitUntil(Deadline deadline);
79+
utils::expected<std::uint64_t, WaitAnyError> WaitUntil(Deadline deadline);
8080

8181
std::size_t GetSize() const noexcept { return subscribed_count_ + pending_subscription_.size(); }
8282

@@ -170,27 +170,32 @@ void WaitAnyContext::Impl::Append(std::uint64_t id, engine::AwaitableToken await
170170
pending_subscription_.push_front(item);
171171
}
172172

173-
std::optional<std::uint64_t> WaitAnyContext::Impl::WaitUntil(Deadline deadline) {
173+
utils::expected<std::uint64_t, WaitAnyError> WaitAnyContext::Impl::WaitUntil(Deadline deadline) {
174174
for (;;) {
175175
queue_non_empty_.Reset();
176176
if (subscribed_count_ != 0) {
177177
auto result = TryProcessQueue();
178178
if (result.has_value()) {
179-
return result;
179+
return *result;
180180
}
181181
}
182182

183183
auto result = TrySubscribe();
184184
if (result.has_value()) {
185-
return result;
185+
return *result;
186186
}
187187

188188
if (subscribed_count_ == 0) {
189-
return std::nullopt;
189+
return utils::unexpected(WaitAnyError::kEmpty);
190190
}
191191

192-
if (!queue_non_empty_.WaitForEventUntil(deadline)) {
193-
return std::nullopt;
192+
switch (queue_non_empty_.WaitUntil(deadline)) {
193+
case FutureStatus::kReady:
194+
break;
195+
case FutureStatus::kTimeout:
196+
return utils::unexpected(WaitAnyError::kTimeout);
197+
case FutureStatus::kCancelled:
198+
return utils::unexpected(WaitAnyError::kCancelled);
194199
}
195200
}
196201
}
@@ -302,9 +307,9 @@ WaitAnyContext& WaitAnyContext::operator=(WaitAnyContext&& other) noexcept {
302307
return *this;
303308
}
304309

305-
std::optional<std::uint64_t> WaitAnyContext::Wait() { return WaitUntil(Deadline{}); }
310+
utils::expected<std::uint64_t, WaitAnyError> WaitAnyContext::Wait() { return WaitUntil(Deadline{}); }
306311

307-
std::optional<std::uint64_t> WaitAnyContext::WaitUntil(Deadline deadline) {
312+
utils::expected<std::uint64_t, WaitAnyError> WaitAnyContext::WaitUntil(Deadline deadline) {
308313
UASSERT(impl_ != nullptr);
309314
return impl_->WaitUntil(deadline);
310315
}

0 commit comments

Comments
 (0)