Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_ASYNC_EVENT_HPP
11 : #define BOOST_CAPY_ASYNC_EVENT_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/concept/executor.hpp>
15 : #include <boost/capy/coro.hpp>
16 : #include <boost/capy/ex/executor_ref.hpp>
17 :
18 : #include <stop_token>
19 :
20 : #include <coroutine>
21 : #include <utility>
22 :
23 : namespace boost {
24 : namespace capy {
25 :
26 : /** An asynchronous event for coroutines.
27 :
28 : This event provides a way to notify multiple coroutines that some
29 : condition has occurred. When a coroutine awaits an unset event, it
30 : suspends and is added to an intrusive wait queue. When the event is
31 : set, all waiting coroutines are resumed.
32 :
33 : @par Zero Allocation
34 :
35 : The wait queue node is embedded in the wait_awaiter, which lives on
36 : the coroutine frame during suspension. No heap allocation occurs.
37 :
38 : @par Thread Safety
39 :
40 : This event is NOT thread-safe. It is designed for single-threaded
41 : use where multiple coroutines may wait for a condition.
42 :
43 : @par Example
44 : @code
45 : async_event event;
46 :
47 : task<> waiter() {
48 : co_await event.wait();
49 : // ... event was set ...
50 : }
51 :
52 : task<> notifier() {
53 : // ... do some work ...
54 : event.set(); // Wake all waiters
55 : }
56 : @endcode
57 : */
58 : class async_event
59 : {
60 : public:
61 : class wait_awaiter;
62 :
63 : /** Awaiter returned by wait().
64 :
65 : The awaiter contains the intrusive list node, which is stored
66 : on the coroutine frame during suspension.
67 : */
68 : class wait_awaiter
69 : {
70 : friend class async_event;
71 :
72 : async_event* e_;
73 : wait_awaiter* next_ = nullptr;
74 : std::coroutine_handle<> h_;
75 : executor_ref ex_;
76 :
77 : public:
78 24 : explicit wait_awaiter(async_event* e) noexcept
79 24 : : e_(e)
80 : {
81 24 : }
82 :
83 21 : bool await_ready() const noexcept
84 : {
85 21 : return e_->set_;
86 : }
87 :
88 : bool await_suspend(std::coroutine_handle<> h) noexcept
89 : {
90 : h_ = h;
91 : ex_ = {};
92 : if(e_->tail_)
93 : e_->tail_->next_ = this;
94 : else
95 : e_->head_ = this;
96 : e_->tail_ = this;
97 : return true;
98 : }
99 :
100 : /** IoAwaitable protocol overload. */
101 : template<Executor Ex>
102 17 : auto await_suspend(
103 : std::coroutine_handle<> h,
104 : Ex const& ex,
105 : std::stop_token = {}) noexcept -> std::coroutine_handle<>
106 : {
107 17 : h_ = h;
108 17 : ex_ = ex;
109 17 : if(e_->tail_)
110 6 : e_->tail_->next_ = this;
111 : else
112 11 : e_->head_ = this;
113 17 : e_->tail_ = this;
114 17 : return std::noop_coroutine();
115 : }
116 :
117 16 : void await_resume() const noexcept
118 : {
119 16 : }
120 : };
121 :
122 : async_event() = default;
123 :
124 : // Non-copyable, non-movable
125 : async_event(async_event const&) = delete;
126 : async_event& operator=(async_event const&) = delete;
127 :
128 : /** Returns an awaiter that waits until the event is set.
129 :
130 : If the event is already set, returns immediately.
131 : Otherwise suspends until set() is called.
132 :
133 : @return An awaiter that suspends if the event is not set,
134 : or completes immediately if set.
135 : */
136 24 : wait_awaiter wait() noexcept
137 : {
138 24 : return wait_awaiter{this};
139 : }
140 :
141 : /** Sets the event.
142 :
143 : All tasks waiting for the event will be immediately awakened.
144 : Subsequent calls to wait() will return immediately until
145 : clear() is called.
146 : */
147 24 : void set() noexcept
148 : {
149 24 : set_ = true;
150 41 : while(head_)
151 : {
152 17 : auto* waiter = head_;
153 17 : head_ = head_->next_;
154 17 : if(waiter->ex_)
155 4 : waiter->ex_.dispatch(coro{waiter->h_}).resume();
156 : else
157 13 : waiter->h_.resume();
158 : }
159 24 : tail_ = nullptr;
160 24 : }
161 :
162 : /** Clears the event.
163 :
164 : Subsequent calls to wait() will block until set() is called again.
165 : */
166 7 : void clear() noexcept
167 : {
168 7 : set_ = false;
169 7 : }
170 :
171 : /** Returns true if the event is currently set.
172 : */
173 13 : bool is_set() const noexcept
174 : {
175 13 : return set_;
176 : }
177 :
178 : private:
179 : bool set_ = false;
180 : wait_awaiter* head_ = nullptr;
181 : wait_awaiter* tail_ = nullptr;
182 : };
183 :
184 : } // namespace capy
185 : } // namespace boost
186 :
187 : #endif
|