1//
2// Copyright (c) 2022 Klemens Morgenstern (klemens.morgenstern@gmx.net)
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
8#ifndef BOOST_COBALT_DETAIL_GENERATOR_HPP
9#define BOOST_COBALT_DETAIL_GENERATOR_HPP
10
11#include <boost/cobalt/concepts.hpp>
12#include <boost/cobalt/result.hpp>
13#include <boost/cobalt/detail/exception.hpp>
14#include <boost/cobalt/detail/forward_cancellation.hpp>
15#include <boost/cobalt/detail/this_thread.hpp>
16#include <boost/cobalt/unique_handle.hpp>
17#include <boost/cobalt/detail/wrapper.hpp>
18
19#include <boost/asio/bind_allocator.hpp>
20#include <boost/core/exchange.hpp>
21#include <boost/variant2/variant.hpp>
22
23namespace boost::cobalt
24{
25
26
27template<typename Yield, typename Push>
28struct generator;
29
30namespace detail
31{
32
33template<typename Yield, typename Push>
34struct generator_yield_awaitable;
35
36template<typename Yield, typename Push>
37struct generator_receiver;
38
39template<typename Yield, typename Push>
40struct generator_receiver_base
41{
42 std::optional<Push> pushed_value;
43 auto get_awaitable(const Push & push) requires std::is_copy_constructible_v<Push>
44 {
45 using impl = generator_receiver<Yield, Push>;
46 return typename impl::awaitable{static_cast<impl*>(this), &push};
47 }
48 auto get_awaitable( Push && push)
49 {
50 using impl = generator_receiver<Yield, Push>;
51 return typename impl::awaitable{static_cast<impl*>(this), &push};
52 }
53};
54
55template<typename Yield>
56struct generator_receiver_base<Yield, void>
57{
58 bool pushed_value{false};
59
60 auto get_awaitable()
61 {
62 using impl = generator_receiver<Yield, void>;
63 return typename impl::awaitable{static_cast<impl*>(this), static_cast<void*>(nullptr)};
64 }
65};
66
67template<typename Yield, typename Push>
68struct generator_promise;
69
70template<typename Yield, typename Push>
71struct generator_receiver : generator_receiver_base<Yield, Push>
72{
73 std::exception_ptr exception;
74 std::optional<Yield> result, result_buffer;
75 Yield get_result()
76 {
77 if (result_buffer)
78 {
79 auto res = *std::exchange(result, std::nullopt);
80 if (result_buffer)
81 result.emplace(*std::exchange(result_buffer, std::nullopt));
82 return res;
83 }
84 else
85 return *std::exchange(result, std::nullopt);
86 }
87 bool done = false;
88 unique_handle<void> awaited_from{nullptr};
89 unique_handle<generator_promise<Yield, Push>> yield_from{nullptr};
90
91 bool lazy = false;
92
93 bool ready() { return exception || result || done; }
94
95 generator_receiver() = default;
96 generator_receiver(generator_receiver && lhs)
97 : generator_receiver_base<Yield, Push>{std::move(lhs.pushed_value)},
98 exception(std::move(lhs.exception)), done(lhs.done),
99 result(std::move(lhs.result)),
100 result_buffer(std::move(lhs.result_buffer)),
101 awaited_from(std::move(lhs.awaited_from)), yield_from{std::move(lhs.yield_from)},
102 lazy(lhs.lazy), reference(lhs.reference), cancel_signal(lhs.cancel_signal)
103
104 {
105 if (!lhs.done && !lhs.exception)
106 {
107 reference = this;
108 lhs.exception = moved_from_exception();
109 }
110 lhs.done = true;
111 }
112
113 ~generator_receiver()
114 {
115 if (!done && reference == this)
116 reference = nullptr;
117 }
118
119 generator_receiver(generator_receiver * &reference, asio::cancellation_signal & cancel_signal)
120 : reference(reference), cancel_signal(cancel_signal)
121 {
122 reference = this;
123 }
124
125 generator_receiver * &reference;
126 asio::cancellation_signal & cancel_signal;
127
128 using yield_awaitable = generator_yield_awaitable<Yield, Push>;
129
130 yield_awaitable get_yield_awaitable(generator_promise<Yield, Push> * pro) {return {pro}; }
131 static yield_awaitable terminator() {return {nullptr}; }
132
133 template<typename T>
134 void yield_value(T && t)
135 {
136 if (!result)
137 result.emplace(std::forward<T>(t));
138 else
139 {
140 BOOST_ASSERT(!result_buffer);
141 result_buffer.emplace(std::forward<T>(t));
142 }
143
144 }
145
146 struct awaitable
147 {
148 generator_receiver *self;
149 std::exception_ptr ex;
150 asio::cancellation_slot cl;
151
152 variant2::variant<variant2::monostate, Push *, const Push *> to_push;
153
154
155 awaitable(generator_receiver * self, Push * to_push) : self(self), to_push(to_push)
156 {
157 }
158 awaitable(generator_receiver * self, const Push * to_push)
159 : self(self), to_push(to_push)
160 {
161 }
162
163 awaitable(const awaitable & aw) noexcept : self(aw.self), to_push(aw.to_push)
164 {
165 }
166
167 bool await_ready() const
168 {
169 BOOST_ASSERT(!ex);
170 return self->ready();
171 }
172
173 template<typename Promise>
174 std::coroutine_handle<void> await_suspend(std::coroutine_handle<Promise> h)
175 {
176 if (self->done) // ok, so we're actually done already, so noop
177 return std::noop_coroutine();
178
179
180 if (!ex && self->awaited_from != nullptr) // generator already being awaited, that's an error!
181 ex = already_awaited();
182
183 if (ex)
184 return h;
185
186 if constexpr (requires (Promise p) {p.get_cancellation_slot();})
187 if ((cl = h.promise().get_cancellation_slot()).is_connected())
188 cl.emplace<forward_cancellation>(args&: self->cancel_signal);
189
190 self->awaited_from.reset(handle: h.address());
191
192 std::coroutine_handle<void> res = std::noop_coroutine();
193 if (self->yield_from != nullptr)
194 res = self->yield_from.release();
195
196 if ((to_push.index() > 0) && !self->pushed_value && self->lazy)
197 {
198 if constexpr (std::is_void_v<Push>)
199 self->pushed_value = true;
200 else
201 {
202 if (to_push.index() == 1)
203 self->pushed_value.emplace(std::move(*variant2::get<1>(to_push)));
204 else
205 {
206 if constexpr (std::is_copy_constructible_v<Push>)
207 self->pushed_value.emplace(std::move(*variant2::get<2>(to_push)));
208 else
209 {
210 BOOST_ASSERT(!"push value is not movable");
211 }
212 }
213 }
214 to_push = variant2::monostate{};
215 }
216 return std::coroutine_handle<void>::from_address(a: res.address());
217 }
218
219 Yield await_resume(const boost::source_location & loc = BOOST_CURRENT_LOCATION)
220 {
221 return await_resume(as_result_tag{}).value(loc);
222 }
223
224 std::tuple<std::exception_ptr, Yield> await_resume(
225 const as_tuple_tag &)
226 {
227 auto res = await_resume(as_result_tag{});
228 if (res.has_error())
229 return {res.error(), Yield{}};
230 else
231 return {nullptr, res.value()};
232 }
233
234 system::result<Yield, std::exception_ptr> await_resume(const as_result_tag& )
235 {
236 if (cl.is_connected())
237 cl.clear();
238 if (ex)
239 return {system::in_place_error, ex};
240 if (self->exception)
241 return {system::in_place_error, std::exchange(obj&: self->exception, new_val: nullptr)};
242 if (!self->result) // missing co_return this is accepted behaviour, if the compiler agrees
243 return {system::in_place_error, std::make_exception_ptr(ex: std::runtime_error("cobalt::generator returned void"))};
244
245 if (to_push.index() > 0)
246 {
247 BOOST_ASSERT(!self->pushed_value);
248 if constexpr (std::is_void_v<Push>)
249 self->pushed_value = true;
250 else
251 {
252 if (to_push.index() == 1)
253 self->pushed_value.emplace(std::move(*variant2::get<1>(to_push)));
254 else
255 {
256 if constexpr (std::is_copy_constructible_v<Push>)
257 self->pushed_value.emplace(std::move(*variant2::get<2>(to_push)));
258 else
259 {
260 BOOST_ASSERT(!"push value is not movable");
261 }
262 }
263
264 }
265 to_push = variant2::monostate{};
266 }
267
268 // now we also want to resume the coroutine, so it starts work
269 if (self->yield_from != nullptr && !self->lazy)
270 {
271 auto exec = self->yield_from->get_executor();
272 auto alloc = asio::get_associated_allocator(self->yield_from);
273 asio::post(
274 std::move(exec),
275 asio::bind_allocator(
276 alloc,
277 [y = std::exchange(self->yield_from, nullptr)]() mutable
278 {
279 if (y->receiver) // make sure we only resume eagerly when attached to a generator object
280 std::move(y)();
281 }));
282 }
283
284 return {system::in_place_value, self->get_result()};
285 }
286
287 void interrupt_await() &
288 {
289 if (!self)
290 return ;
291 ex = detached_exception();
292
293 if (self->awaited_from)
294 self->awaited_from.release().resume();
295 }
296 };
297
298 void interrupt_await() &
299 {
300 exception = detached_exception();
301 awaited_from.release().resume();
302 }
303
304 void rethrow_if()
305 {
306 if (exception)
307 std::rethrow_exception(exception);
308 }
309};
310
311template<typename Yield, typename Push>
312struct generator_promise
313 : promise_memory_resource_base,
314 promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>,
315 promise_throw_if_cancelled_base,
316 enable_awaitables<generator_promise<Yield, Push>>,
317 enable_await_allocator<generator_promise<Yield, Push>>,
318 enable_await_executor< generator_promise<Yield, Push>>
319{
320 using promise_cancellation_base<asio::cancellation_slot, asio::enable_total_cancellation>::await_transform;
321 using promise_throw_if_cancelled_base::await_transform;
322 using enable_awaitables<generator_promise<Yield, Push>>::await_transform;
323 using enable_await_allocator<generator_promise<Yield, Push>>::await_transform;
324 using enable_await_executor<generator_promise<Yield, Push>>::await_transform;
325
326 [[nodiscard]] generator<Yield, Push> get_return_object()
327 {
328 return generator<Yield, Push>{this};
329 }
330
331 mutable asio::cancellation_signal signal;
332
333 using executor_type = executor;
334 executor_type exec;
335 const executor_type & get_executor() const {return exec;}
336
337 template<typename ... Args>
338 generator_promise(Args & ...args)
339 :
340#if !defined(BOOST_COBALT_NO_PMR)
341 promise_memory_resource_base(detail::get_memory_resource_from_args(args...)),
342#endif
343 exec{detail::get_executor_from_args(args...)}
344 {
345 this->reset_cancellation_source(signal.slot());
346 }
347
348 std::suspend_never initial_suspend() {return {};}
349
350 struct final_awaitable
351 {
352 generator_promise * generator;
353 bool await_ready() const noexcept
354 {
355 return generator->receiver && generator->receiver->awaited_from.get() == nullptr;
356 }
357
358 auto await_suspend(std::coroutine_handle<generator_promise> h) noexcept
359 {
360 std::coroutine_handle<void> res = std::noop_coroutine();
361 if (generator->receiver && generator->receiver->awaited_from.get() != nullptr)
362 res = generator->receiver->awaited_from.release();
363 if (generator->receiver)
364 generator->receiver->done = true;
365
366
367 if (auto & rec = h.promise().receiver; rec != nullptr)
368 {
369 if (!rec->done && !rec->exception)
370 rec->exception = detail::completed_unexpected();
371 rec->done = true;
372 rec->awaited_from.reset(nullptr);
373 rec = nullptr;
374 }
375
376 detail::self_destroy(h);
377 return res;
378 }
379
380 void await_resume() noexcept
381 {
382 if (generator->receiver)
383 generator->receiver->done = true;
384 }
385 };
386
387 auto final_suspend() noexcept
388 {
389 return final_awaitable{this};
390 }
391
392 void unhandled_exception()
393 {
394 if (this->receiver)
395 this->receiver->exception = std::current_exception();
396 else
397 throw ;
398 }
399
400 void return_value(const Yield & res) requires std::is_copy_constructible_v<Yield>
401 {
402 if (this->receiver)
403 this->receiver->yield_value(res);
404 }
405
406 void return_value(Yield && res)
407 {
408 if (this->receiver)
409 this->receiver->yield_value(std::move(res));
410 }
411
412 generator_receiver<Yield, Push>* receiver{nullptr};
413
414 auto await_transform(this_coro::initial_t val)
415 {
416 if(receiver)
417 {
418 receiver->lazy = true;
419 return receiver->get_yield_awaitable(this);
420 }
421 else
422 return generator_receiver<Yield, Push>::terminator();
423 }
424
425 template<typename Yield_>
426 auto yield_value(Yield_ && ret)
427 {
428 if(receiver)
429 {
430 // if this is lazy, there might still be a value in there.
431 receiver->yield_value(std::forward<Yield_>(ret));
432 return receiver->get_yield_awaitable(this);
433 }
434 else
435 return generator_receiver<Yield, Push>::terminator();
436 }
437
438 void interrupt_await() &
439 {
440 if (this->receiver)
441 {
442 this->receiver->exception = detached_exception();
443 std::coroutine_handle<void>::from_address(a: this->receiver->awaited_from.release()).resume();
444 }
445 }
446
447 ~generator_promise()
448 {
449 if (this->receiver)
450 {
451 if (!this->receiver->done && !this->receiver->exception)
452 this->receiver->exception = detail::completed_unexpected();
453 this->receiver->done = true;
454 this->receiver->awaited_from.reset(nullptr);
455 }
456 }
457
458};
459
460template<typename Yield, typename Push>
461struct generator_yield_awaitable
462{
463 generator_promise<Yield, Push> *self;
464 constexpr bool await_ready() const
465 {
466 return self && self->receiver && self->receiver->pushed_value && !self->receiver->result;
467 }
468
469 std::coroutine_handle<void> await_suspend(
470 std::coroutine_handle<generator_promise<Yield, Push>> h
471#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
472 , const boost::source_location & loc = BOOST_CURRENT_LOCATION
473#endif
474 )
475 {
476 if (self == nullptr) // we're a terminator, kill it
477 {
478 if (auto & rec = h.promise().receiver; rec != nullptr)
479 {
480 if (!rec->done && !rec->exception)
481 rec->exception = detail::completed_unexpected();
482 rec->done = true;
483 rec->awaited_from.reset(nullptr);
484 rec = nullptr;
485 }
486
487 detail::self_destroy(h);
488 return std::noop_coroutine();
489 }
490 std::coroutine_handle<void> res = std::noop_coroutine();
491 if (self->receiver->awaited_from.get() != nullptr)
492 res = self->receiver->awaited_from.release();
493#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
494 self->receiver->yield_from.reset(&h.promise(), loc);
495#else
496 self->receiver->yield_from.reset(&h.promise());
497#endif
498 return res;
499 }
500
501 Push await_resume()
502 {
503 BOOST_ASSERT(self->receiver);
504 BOOST_ASSERT(self->receiver->pushed_value);
505 return *std::exchange(self->receiver->pushed_value, std::nullopt);
506 }
507};
508
509template<typename Yield>
510struct generator_yield_awaitable<Yield, void>
511{
512 generator_promise<Yield, void> *self;
513 constexpr bool await_ready() { return self && self->receiver && self->receiver->pushed_value; }
514
515 std::coroutine_handle<> await_suspend(
516 std::coroutine_handle<generator_promise<Yield, void>> h
517#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
518 , const boost::source_location & loc = BOOST_CURRENT_LOCATION
519#endif
520 )
521 {
522 if (self == nullptr) // we're a terminator, kill it
523 {
524 if (auto & rec = h.promise().receiver; rec != nullptr)
525 {
526 if (!rec->done && !rec->exception)
527 rec->exception = detail::completed_unexpected();
528 rec->done = true;
529 rec->awaited_from.reset(nullptr);
530 rec = nullptr;
531 }
532 detail::self_destroy(h);
533 return std::noop_coroutine();
534 }
535 std::coroutine_handle<void> res = std::noop_coroutine();
536 BOOST_ASSERT(self);
537 if (self->receiver->awaited_from.get() != nullptr)
538 res = self->receiver->awaited_from.release();
539#if defined(BOOST_ASIO_ENABLE_HANDLER_TRACKING)
540 self->receiver->yield_from.reset(&h.promise(), loc);
541#else
542 self->receiver->yield_from.reset(&h.promise());
543#endif
544 return res;
545 }
546
547 void await_resume()
548 {
549 BOOST_ASSERT(self->receiver->pushed_value);
550 self->receiver->pushed_value = false;
551 }
552
553};
554
555
556template<typename Yield, typename Push>
557struct generator_base
558{
559 auto operator()( Push && push)
560 {
561 return static_cast<generator<Yield, Push>*>(this)->receiver_.get_awaitable(std::move(push));
562 }
563 auto operator()(const Push & push) requires std::is_copy_constructible_v<Push>
564 {
565 return static_cast<generator<Yield, Push>*>(this)->receiver_.get_awaitable(push);
566 }
567};
568
569template<typename Yield>
570struct generator_base<Yield, void>
571{
572 auto operator co_await ()
573 {
574 return static_cast<generator<Yield, void>*>(this)->receiver_.get_awaitable();
575 }
576};
577
578template<typename T>
579struct generator_with_awaitable
580{
581 generator_base<T, void> &g;
582 std::optional<typename detail::generator_receiver<T, void>::awaitable> awaitable;
583
584 template<typename Promise>
585 void await_suspend(std::coroutine_handle<Promise> h)
586 {
587 g.cancel();
588 awaitable.emplace(g.operator co_await());
589 return awaitable->await_suspend(h);
590 }
591
592 void await_resume() {}
593
594};
595
596}
597
598}
599
600#endif //BOOST_COBALT_DETAIL_GENERATOR_HPP
601

source code of boost/libs/cobalt/include/boost/cobalt/detail/generator.hpp