1 | // |
2 | // coroutine.hpp |
3 | // ~~~~~~~~~~~~~ |
4 | // |
5 | // Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
6 | // |
7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
9 | // |
10 | |
11 | #ifndef BOOST_ASIO_COROUTINE_HPP |
12 | #define BOOST_ASIO_COROUTINE_HPP |
13 | |
14 | namespace boost { |
15 | namespace asio { |
16 | namespace detail { |
17 | |
18 | class coroutine_ref; |
19 | |
20 | } // namespace detail |
21 | |
22 | /// Provides support for implementing stackless coroutines. |
23 | /** |
24 | * The @c coroutine class may be used to implement stackless coroutines. The |
25 | * class itself is used to store the current state of the coroutine. |
26 | * |
27 | * Coroutines are copy-constructible and assignable, and the space overhead is |
28 | * a single int. They can be used as a base class: |
29 | * |
30 | * @code class session : coroutine |
31 | * { |
32 | * ... |
33 | * }; @endcode |
34 | * |
35 | * or as a data member: |
36 | * |
37 | * @code class session |
38 | * { |
39 | * ... |
40 | * coroutine coro_; |
41 | * }; @endcode |
42 | * |
43 | * or even bound in as a function argument using lambdas or @c bind(). The |
44 | * important thing is that as the application maintains a copy of the object |
45 | * for as long as the coroutine must be kept alive. |
46 | * |
47 | * @par Pseudo-keywords |
48 | * |
49 | * A coroutine is used in conjunction with certain "pseudo-keywords", which |
50 | * are implemented as macros. These macros are defined by a header file: |
51 | * |
52 | * @code #include <boost/asio/yield.hpp>@endcode |
53 | * |
54 | * and may conversely be undefined as follows: |
55 | * |
56 | * @code #include <boost/asio/unyield.hpp>@endcode |
57 | * |
58 | * <b>reenter</b> |
59 | * |
60 | * The @c reenter macro is used to define the body of a coroutine. It takes a |
61 | * single argument: a pointer or reference to a coroutine object. For example, |
62 | * if the base class is a coroutine object you may write: |
63 | * |
64 | * @code reenter (this) |
65 | * { |
66 | * ... coroutine body ... |
67 | * } @endcode |
68 | * |
69 | * and if a data member or other variable you can write: |
70 | * |
71 | * @code reenter (coro_) |
72 | * { |
73 | * ... coroutine body ... |
74 | * } @endcode |
75 | * |
76 | * When @c reenter is executed at runtime, control jumps to the location of the |
77 | * last @c yield or @c fork. |
78 | * |
79 | * The coroutine body may also be a single statement, such as: |
80 | * |
81 | * @code reenter (this) for (;;) |
82 | * { |
83 | * ... |
84 | * } @endcode |
85 | * |
86 | * @b Limitation: The @c reenter macro is implemented using a switch. This |
87 | * means that you must take care when using local variables within the |
88 | * coroutine body. The local variable is not allowed in a position where |
89 | * reentering the coroutine could bypass the variable definition. |
90 | * |
91 | * <b>yield <em>statement</em></b> |
92 | * |
93 | * This form of the @c yield keyword is often used with asynchronous operations: |
94 | * |
95 | * @code yield socket_->async_read_some(buffer(*buffer_), *this); @endcode |
96 | * |
97 | * This divides into four logical steps: |
98 | * |
99 | * @li @c yield saves the current state of the coroutine. |
100 | * @li The statement initiates the asynchronous operation. |
101 | * @li The resume point is defined immediately following the statement. |
102 | * @li Control is transferred to the end of the coroutine body. |
103 | * |
104 | * When the asynchronous operation completes, the function object is invoked |
105 | * and @c reenter causes control to transfer to the resume point. It is |
106 | * important to remember to carry the coroutine state forward with the |
107 | * asynchronous operation. In the above snippet, the current class is a |
108 | * function object object with a coroutine object as base class or data member. |
109 | * |
110 | * The statement may also be a compound statement, and this permits us to |
111 | * define local variables with limited scope: |
112 | * |
113 | * @code yield |
114 | * { |
115 | * mutable_buffers_1 b = buffer(*buffer_); |
116 | * socket_->async_read_some(b, *this); |
117 | * } @endcode |
118 | * |
119 | * <b>yield return <em>expression</em> ;</b> |
120 | * |
121 | * This form of @c yield is often used in generators or coroutine-based parsers. |
122 | * For example, the function object: |
123 | * |
124 | * @code struct interleave : coroutine |
125 | * { |
126 | * istream& is1; |
127 | * istream& is2; |
128 | * char operator()(char c) |
129 | * { |
130 | * reenter (this) for (;;) |
131 | * { |
132 | * yield return is1.get(); |
133 | * yield return is2.get(); |
134 | * } |
135 | * } |
136 | * }; @endcode |
137 | * |
138 | * defines a trivial coroutine that interleaves the characters from two input |
139 | * streams. |
140 | * |
141 | * This type of @c yield divides into three logical steps: |
142 | * |
143 | * @li @c yield saves the current state of the coroutine. |
144 | * @li The resume point is defined immediately following the semicolon. |
145 | * @li The value of the expression is returned from the function. |
146 | * |
147 | * <b>yield ;</b> |
148 | * |
149 | * This form of @c yield is equivalent to the following steps: |
150 | * |
151 | * @li @c yield saves the current state of the coroutine. |
152 | * @li The resume point is defined immediately following the semicolon. |
153 | * @li Control is transferred to the end of the coroutine body. |
154 | * |
155 | * This form might be applied when coroutines are used for cooperative |
156 | * threading and scheduling is explicitly managed. For example: |
157 | * |
158 | * @code struct task : coroutine |
159 | * { |
160 | * ... |
161 | * void operator()() |
162 | * { |
163 | * reenter (this) |
164 | * { |
165 | * while (... not finished ...) |
166 | * { |
167 | * ... do something ... |
168 | * yield; |
169 | * ... do some more ... |
170 | * yield; |
171 | * } |
172 | * } |
173 | * } |
174 | * ... |
175 | * }; |
176 | * ... |
177 | * task t1, t2; |
178 | * for (;;) |
179 | * { |
180 | * t1(); |
181 | * t2(); |
182 | * } @endcode |
183 | * |
184 | * <b>yield break ;</b> |
185 | * |
186 | * The final form of @c yield is used to explicitly terminate the coroutine. |
187 | * This form is comprised of two steps: |
188 | * |
189 | * @li @c yield sets the coroutine state to indicate termination. |
190 | * @li Control is transferred to the end of the coroutine body. |
191 | * |
192 | * Once terminated, calls to is_complete() return true and the coroutine cannot |
193 | * be reentered. |
194 | * |
195 | * Note that a coroutine may also be implicitly terminated if the coroutine |
196 | * body is exited without a yield, e.g. by return, throw or by running to the |
197 | * end of the body. |
198 | * |
199 | * <b>fork <em>statement</em></b> |
200 | * |
201 | * The @c fork pseudo-keyword is used when "forking" a coroutine, i.e. splitting |
202 | * it into two (or more) copies. One use of @c fork is in a server, where a new |
203 | * coroutine is created to handle each client connection: |
204 | * |
205 | * @code reenter (this) |
206 | * { |
207 | * do |
208 | * { |
209 | * socket_.reset(new tcp::socket(my_context_)); |
210 | * yield acceptor->async_accept(*socket_, *this); |
211 | * fork server(*this)(); |
212 | * } while (is_parent()); |
213 | * ... client-specific handling follows ... |
214 | * } @endcode |
215 | * |
216 | * The logical steps involved in a @c fork are: |
217 | * |
218 | * @li @c fork saves the current state of the coroutine. |
219 | * @li The statement creates a copy of the coroutine and either executes it |
220 | * immediately or schedules it for later execution. |
221 | * @li The resume point is defined immediately following the semicolon. |
222 | * @li For the "parent", control immediately continues from the next line. |
223 | * |
224 | * The functions is_parent() and is_child() can be used to differentiate |
225 | * between parent and child. You would use these functions to alter subsequent |
226 | * control flow. |
227 | * |
228 | * Note that @c fork doesn't do the actual forking by itself. It is the |
229 | * application's responsibility to create a clone of the coroutine and call it. |
230 | * The clone can be called immediately, as above, or scheduled for delayed |
231 | * execution using something like boost::asio::post(). |
232 | * |
233 | * @par Alternate macro names |
234 | * |
235 | * If preferred, an application can use macro names that follow a more typical |
236 | * naming convention, rather than the pseudo-keywords. These are: |
237 | * |
238 | * @li @c BOOST_ASIO_CORO_REENTER instead of @c reenter |
239 | * @li @c BOOST_ASIO_CORO_YIELD instead of @c yield |
240 | * @li @c BOOST_ASIO_CORO_FORK instead of @c fork |
241 | */ |
242 | class coroutine |
243 | { |
244 | public: |
245 | /// Constructs a coroutine in its initial state. |
246 | coroutine() : value_(0) {} |
247 | |
248 | /// Returns true if the coroutine is the child of a fork. |
249 | bool is_child() const { return value_ < 0; } |
250 | |
251 | /// Returns true if the coroutine is the parent of a fork. |
252 | bool is_parent() const { return !is_child(); } |
253 | |
254 | /// Returns true if the coroutine has reached its terminal state. |
255 | bool is_complete() const { return value_ == -1; } |
256 | |
257 | private: |
258 | friend class detail::coroutine_ref; |
259 | int value_; |
260 | }; |
261 | |
262 | |
263 | namespace detail { |
264 | |
265 | class coroutine_ref |
266 | { |
267 | public: |
268 | coroutine_ref(coroutine& c) : value_(c.value_), modified_(false) {} |
269 | coroutine_ref(coroutine* c) : value_(c->value_), modified_(false) {} |
270 | coroutine_ref(const coroutine_ref&) = default; |
271 | ~coroutine_ref() { if (!modified_) value_ = -1; } |
272 | operator int() const { return value_; } |
273 | int& operator=(int v) { modified_ = true; return value_ = v; } |
274 | private: |
275 | void operator=(const coroutine_ref&); |
276 | int& value_; |
277 | bool modified_; |
278 | }; |
279 | |
280 | } // namespace detail |
281 | } // namespace asio |
282 | } // namespace boost |
283 | |
284 | #define BOOST_ASIO_CORO_REENTER(c) \ |
285 | switch (::boost::asio::detail::coroutine_ref _coro_value = c) \ |
286 | case -1: if (_coro_value) \ |
287 | { \ |
288 | goto terminate_coroutine; \ |
289 | terminate_coroutine: \ |
290 | _coro_value = -1; \ |
291 | goto bail_out_of_coroutine; \ |
292 | bail_out_of_coroutine: \ |
293 | break; \ |
294 | } \ |
295 | else /* fall-through */ case 0: |
296 | |
297 | #define BOOST_ASIO_CORO_YIELD_IMPL(n) \ |
298 | for (_coro_value = (n);;) \ |
299 | if (_coro_value == 0) \ |
300 | { \ |
301 | case (n): ; \ |
302 | break; \ |
303 | } \ |
304 | else \ |
305 | switch (_coro_value ? 0 : 1) \ |
306 | for (;;) \ |
307 | /* fall-through */ case -1: if (_coro_value) \ |
308 | goto terminate_coroutine; \ |
309 | else for (;;) \ |
310 | /* fall-through */ case 1: if (_coro_value) \ |
311 | goto bail_out_of_coroutine; \ |
312 | else /* fall-through */ case 0: |
313 | |
314 | #define BOOST_ASIO_CORO_FORK_IMPL(n) \ |
315 | for (_coro_value = -(n);; _coro_value = (n)) \ |
316 | if (_coro_value == (n)) \ |
317 | { \ |
318 | case -(n): ; \ |
319 | break; \ |
320 | } \ |
321 | else |
322 | |
323 | #if defined(_MSC_VER) |
324 | # define BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD_IMPL(__COUNTER__ + 1) |
325 | # define BOOST_ASIO_CORO_FORK BOOST_ASIO_CORO_FORK_IMPL(__COUNTER__ + 1) |
326 | #else // defined(_MSC_VER) |
327 | # define BOOST_ASIO_CORO_YIELD BOOST_ASIO_CORO_YIELD_IMPL(__LINE__) |
328 | # define BOOST_ASIO_CORO_FORK BOOST_ASIO_CORO_FORK_IMPL(__LINE__) |
329 | #endif // defined(_MSC_VER) |
330 | |
331 | #endif // BOOST_ASIO_COROUTINE_HPP |
332 | |