userver: userver/engine/pulse_event.hpp Source File
Loading...
Searching...
No Matches
pulse_event.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/engine/pulse_event.hpp
4/// @brief @copybrief engine::PulseEvent
5
6#include <atomic>
7#include <cstdint>
8
9#include <userver/compiler/impl/lifetime.hpp>
10#include <userver/engine/awaitable.hpp>
11#include <userver/engine/deadline.hpp>
12#include <userver/engine/future_status.hpp>
13#include <userver/engine/impl/context_accessor.hpp>
14#include <userver/engine/impl/wait_list_fwd.hpp>
15#include <userver/utils/fast_pimpl.hpp>
16
17USERVER_NAMESPACE_BEGIN
18
19namespace engine {
20
21class PulseEvent;
22
23/// @brief A one-shot subscription from @ref PulseEvent::Subscribe.
24///
25/// Satisfies @ref engine::Awaitable, for use with @ref engine::WaitAnyContext and friends.
26///
27/// @note For simple "subscribe to updates" cases a callback API is often more convenient:
28/// @ref concurrent::AsyncEventChannel.
29///
30/// **Example.** A parent task publishes two atomic config fields and wakes all consumers. Each consumer
31/// assembles a `Config` and passes it to `ApplyConfig`.
32///
33/// Initialization:
34/// @snippet core/src/engine/pulse_event_test.cpp WaitUntil subscription init
35///
36/// Publisher side:
37/// @snippet core/src/engine/pulse_event_test.cpp WaitUntil subscription notifier
38///
39/// Consumer side:
40/// @snippet core/src/engine/pulse_event_test.cpp WaitUntil subscription waiter
41class PulseEventSubscription final : private impl::AwaitableBase {
42public:
43 PulseEventSubscription() noexcept;
44 PulseEventSubscription(const PulseEventSubscription&) = delete;
45 PulseEventSubscription(PulseEventSubscription&& other) noexcept;
46 PulseEventSubscription& operator=(const PulseEventSubscription&) = delete;
47 PulseEventSubscription& operator=(PulseEventSubscription&& other) noexcept;
48 ~PulseEventSubscription();
49
50 /// @return `true` if this object was issued by @ref PulseEvent::Subscribe
51 [[nodiscard]] bool IsValid() const noexcept;
52
53 /// @return `true` if a @ref PulseEvent::Send after this subscription has already happened
54 [[nodiscard]] bool IsReady() const noexcept override;
55
56 /// @brief Waits until a concurrent @ref PulseEvent::Send satisfies this subscription, or the deadline expires, or
57 /// the current task is cancelled.
58 [[nodiscard]] FutureStatus WaitUntil(Deadline);
59
60 /// Satisfies @ref engine::Awaitable, for use with @ref engine::WaitAnyContext and friends.
61 AwaitableToken GetAwaitableToken() noexcept USERVER_IMPL_LIFETIME_BOUND;
62
63private:
64 friend class PulseEvent;
65
66 using Ticket = std::uint64_t;
67
68 PulseEventSubscription(PulseEvent* event, Ticket ticket) noexcept;
69
70 void TryAppendAwaiter(impl::AwaiterPtr& awaiter, std::uintptr_t context) override;
71 impl::AwaiterPtr RemoveAwaiter(impl::Awaiter& awaiter, std::uintptr_t context) noexcept override;
72
73 PulseEvent* event_{nullptr};
74 Ticket ticket_{};
75};
76
77/// @ingroup userver_concurrency
78///
79/// @brief A multiple-producers, multiple-consumers notification.
80///
81/// @ref Subscribe issues a one-shot @ref PulseEventSubscription. Any @ref Send after that satisfies it; a @ref Send
82/// that happened before @ref Subscribe does not count. After that first @ref Send, the subscription stays ready;
83/// call @ref Subscribe again to wait for later signals.
84///
85/// This is unlike @ref engine::MultiConsumerEvent, which latches a single global signal forever after the first
86/// @ref engine::MultiConsumerEvent::Send "Send" (equivalent to @ref engine::SingleConsumerEvent::NoAutoReset).
87///
88/// @ref PulseEventSubscription is compatible with @ref engine::WaitAny and friends. `PulseEvent` itself is not
89/// an awaitable: wait on a subscription from @ref Subscribe.
90///
91/// @see @ref scripts/docs/en/userver/synchronization.md "Synchronization primitives (PulseEvent overview and example)"
92class PulseEvent final {
93 using Ticket = std::uint64_t;
94
95public:
96 PulseEvent() noexcept;
97
98 PulseEvent(const PulseEvent&) = delete;
99 PulseEvent(PulseEvent&&) = delete;
100 PulseEvent& operator=(const PulseEvent&) = delete;
101 PulseEvent& operator=(PulseEvent&&) = delete;
102 ~PulseEvent();
103
104 /// @brief Issues a one-shot subscription that later @ref Send calls can satisfy.
105 [[nodiscard]] PulseEventSubscription Subscribe() noexcept;
106
107 /// @brief Works like `std::condition_variable::wait_until`. Waits until @a stop_waiting becomes `true`, and we are
108 /// notified via `Send`.
109 ///
110 /// If @a stop_waiting is already `true`, returns right away.
111 ///
112 /// Unlike `std::condition_variable` and engine::ConditionVariable, there are no locks around the state watched by
113 /// @a stop_waiting, so that state must be atomic. `std::memory_order_relaxed` is OK inside @a stop_waiting and
114 /// inside the notifiers as long as it does not mess up their logic.
115 ///
116 /// For a full example, see @ref engine_pulse_event.
117 ///
118 /// @return `FutureStatus::kReady` if @a stop_waiting became `true`, `FutureStatus::kCancelled` if the current task
119 /// was cancelled, `FutureStatus::kTimeout` if the deadline was reached.
120 template <typename Predicate>
121 [[nodiscard]] FutureStatus WaitUntil(Deadline, Predicate stop_waiting);
122
123 /// Wakes all tasks that currently wait on this event, if any. Also satisfies every subscription already issued, so
124 /// a @ref Send that races with @ref Subscribe is not lost.
125 ///
126 /// You can safely invoke Send from outside a coroutine.
127 void Send() noexcept;
128
129private:
130 friend class PulseEventSubscription;
131
132 std::atomic<Ticket> generation_{0};
133 impl::FastPimplWaitList awaiters_;
134};
135
136template <typename Predicate>
137FutureStatus PulseEvent::WaitUntil(Deadline deadline, Predicate stop_waiting) {
138 while (true) {
139 auto subscription = Subscribe();
140
141 if (stop_waiting()) {
143 }
144
145 if (const auto status = subscription.WaitUntil(deadline); status != FutureStatus::kReady) {
146 return status;
147 }
148 }
149}
150
151} // namespace engine
152
153USERVER_NAMESPACE_END