userver: userver/engine/single_consumer_event.hpp Source File
Loading...
Searching...
No Matches
single_consumer_event.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/engine/single_consumer_event.hpp
4/// @brief @copybrief engine::SingleConsumerEvent
5
6#include <chrono>
7
8#include <userver/engine/awaitable.hpp>
9#include <userver/engine/deadline.hpp>
10#include <userver/engine/future_status.hpp>
11#include <userver/utils/fast_pimpl.hpp>
12
13USERVER_NAMESPACE_BEGIN
14
15namespace engine {
16
17/// @ingroup userver_concurrency
18///
19/// @brief A multiple-producers, single-consumer event
20class SingleConsumerEvent final {
21public:
22 struct NoAutoReset final {};
23
24 /// Creates an event that resets automatically on retrieval.
26
27 /// Creates an event that does not reset automatically.
28 explicit SingleConsumerEvent(NoAutoReset) noexcept;
29
30 SingleConsumerEvent(const SingleConsumerEvent&) = delete;
31 SingleConsumerEvent(SingleConsumerEvent&&) = delete;
32 SingleConsumerEvent& operator=(const SingleConsumerEvent&) = delete;
33 SingleConsumerEvent& operator=(SingleConsumerEvent&&) = delete;
34 ~SingleConsumerEvent();
35
36 /// @return whether this event resets automatically on retrieval
37 bool IsAutoReset() const noexcept;
38
39 /// @brief Waits until the event is in a signaled state.
40 ///
41 /// If the event is auto-resetting, clears the signal flag upon waking up. If
42 /// already in a signaled state, does the same without sleeping.
43 ///
44 /// If the waiting failed (the event did not signal), because the optional
45 /// deadline is expired or the current task is cancelled, returns `false`.
46 ///
47 /// @return whether the event signaled
48 [[nodiscard]] bool WaitForEvent();
49
50 /// @overload bool WaitForEvent()
51 template <typename Clock, typename Duration>
52 [[nodiscard]] bool WaitForEventFor(std::chrono::duration<Clock, Duration>);
53
54 /// @overload bool WaitForEvent()
55 template <typename Clock, typename Duration>
56 [[nodiscard]] bool WaitForEventUntil(std::chrono::time_point<Clock, Duration>);
57
58 /// @brief Waits until the event is signaled, the deadline is reached, or the task is cancelled
59 [[nodiscard]] bool WaitForEventUntil(Deadline);
60
61 /// @brief Waits until the event is in a signaled state, same as
62 /// #WaitForEventUntil, but gives the precise reason of a failure instead of just
63 /// `false`.
64 ///
65 /// If the event is auto-resetting, clears the signal flag upon waking up. If already in a signaled state,
66 /// does the same without sleeping.
67 ///
68 /// @return `FutureStatus::kReady` if the event signaled, `FutureStatus::kCancelled` if the current task was
69 /// cancelled, `FutureStatus::kTimeout` if the deadline was reached.
70 [[nodiscard]] FutureStatus WaitUntil(Deadline deadline);
71
72 /// @brief Works like `std::condition_variable::wait_until`. Waits until
73 /// @a stop_waiting becomes `true`, and we are notified via `Send`.
74 ///
75 /// If @a stop_waiting is already `true`, returns right away.
76 ///
77 /// Unlike `std::condition_variable` and engine::ConditionVariable, there are
78 /// no locks around the state watched by @a stop_waiting, so that state must
79 /// be atomic. `std::memory_order_relaxed` is OK inside @a stop_waiting and
80 /// inside the notifiers as long as it does not mess up their logic.
81 ///
82 /// **Example.** Suppose we want to wait until a counter is even, then grab
83 /// it.
84 ///
85 /// Initialization:
86 /// @snippet core/src/engine/single_consumer_event_test.cpp CV init
87 ///
88 /// Notifier side:
89 /// @snippet core/src/engine/single_consumer_event_test.cpp CV notifier
90 ///
91 /// Waiter side:
92 /// @snippet core/src/engine/single_consumer_event_test.cpp CV waiter
93 ///
94 /// @return `FutureStatus::kReady` if @a stop_waiting became `true`, `FutureStatus::kCancelled` if the current
95 /// task was cancelled, `FutureStatus::kTimeout` if the deadline was reached.
96 template <typename Predicate>
97 [[nodiscard]] FutureStatus WaitUntil(Deadline, Predicate stop_waiting);
98
99 /// Resets the signal flag, if there is any existing event. Guarantees at least 'acquire' and 'release'
100 /// memory ordering. Must only be called by the waiting task.
101 void Reset() noexcept;
102
103 /// Sets the signal flag and wakes a task that waits on it (if any).
104 /// If the signal flag is already set, does nothing.
105 ///
106 /// The waiter is allowed to destroy the SingleConsumerEvent immediately
107 /// after exiting WaitForEvent, ONLY IF the wait succeeded. Otherwise
108 /// a concurrent task may call Send on a destroyed SingleConsumerEvent.
109 /// Here is an example of this situation:
110 /// @snippet core/src/engine/single_consumer_event_test.cpp Wait and destroy
111 ///
112 /// You can safely invoke Send from outside a coroutine.
113 void Send();
114
115 /// Returns `true` iff already signaled. Never resets the signal.
116 [[nodiscard]] bool IsReady() const noexcept;
117
118 /// @brief Satisfies @ref engine::Awaitable, for use with @ref engine::WaitAnyContext and friends.
119 ///
120 /// @note When using `SingleConsumerEvent` as a condition variable, beware of spurious wakeups.
121 /// The awaitable signals completion as soon as @ref Send is called regardless of possible semantic restrictions
122 /// of the predicate in @ref WaitUntil.
123 ///
124 /// @warning Only available for @ref NoAutoReset case.
125 AwaitableToken GetAwaitableToken();
126
127private:
128 struct Impl;
129
130 bool GetIsSignaled() noexcept;
131
132 utils::FastPimpl<Impl, 32, 16> impl_;
133};
134
135template <typename Clock, typename Duration>
136bool SingleConsumerEvent::WaitForEventFor(std::chrono::duration<Clock, Duration> duration) {
137 return WaitForEventUntil(Deadline::FromDuration(duration));
138}
139
140template <typename Clock, typename Duration>
141bool SingleConsumerEvent::WaitForEventUntil(std::chrono::time_point<Clock, Duration> time_point) {
142 return WaitForEventUntil(Deadline::FromTimePoint(time_point));
143}
144
145template <typename Predicate>
146FutureStatus SingleConsumerEvent::WaitUntil(Deadline deadline, Predicate stop_waiting) {
147 // If the state, according to what we've been previously notified of via
148 // 'Send', is OK, then return right away. Fresh state updates can also
149 // leak to us here, but we should not rely on it.
150 while (!stop_waiting()) {
151 // Wait until we are allowed to make progress.
152 // On the first such wait, we may discover a signal from the state that
153 // has already leaked to us previously (as described above).
154 //
155 // We may also receive false signals from cases when we are allowed
156 // and unallowed to make progress in a rapid sequence, or when the notifier
157 // thinks that we might be happy with the state, but we aren't.
158 if (const auto status = WaitUntil(deadline); status != FutureStatus::kReady) {
159 return status;
160 }
161
162 if (!IsAutoReset()) {
163 // Reset guarantees `std::memory_order_acquire` on the signal, so
164 // if we reset any additional signals here, then the predicate will
165 // see the associated data updates.
166 Reset();
167 }
168 }
169
171}
172
173} // namespace engine
174
175USERVER_NAMESPACE_END