Conversation
|
Cool. What impact does this have on algorithm implementations? I assume it's not safe to just drop a tail sender on the floor, or is it? |
|
Correct. The results of start(), set_value(), set_error(), and set_stopped() must either be propagated or resumed. This affects lifetimes as well. When a tail_sender is returned, the lifetime of the object that returned the tail_sender is extended until all tail_senders are resumed. This is why sync_wait will resume the tail_senders before the operation_state is destructed. |
6cbd664 to
30b788e
Compare
cf59183 to
d05357c
Compare
|
@kirkshoop there's a |
|
/ok to test |
This change adds support for TailCalls, in library form, to allow Sender/Receiver algorithms to iterate without blowing the stack or using a scheduler.
Needs examples, documentation and a paper.
The solution here is translated from an original design and implementation by @lewissbaker.
A tail_sender is a constrained sender concept where always_completes_inline_v (a new CPO) is true, all destructors are trivial and all methods are noexcept (copy/move/connect etc..) and the tail_operation_state must support the new unwind() CPO.
A nullable_tail_sender must also have a tail_operation_state that is convertible to bool.
A terminal_tail_sender has a tail_operation_state that returns void from start().
The existing start, set_value, set_error, and set_stopped CPOs now support returning a tail_sender.
A tail_sender would be used extensively in sender/receiver sequences.
A run() that returns a tail_sender is also the way to compose multiple 'drivable' schedulers (like run_loop) on the same thread. This will be important for the system_executor.
The repeat() algorithm would use tail_sender per start() instead of a schedule() per start().
To drive a set of recursive tail_senders without exhausting the stack, use:
TailSender resume_tail_senders_until_one_remaining(TailReceiver, TailSenders...)An example main() that safely starts and stops async execution:
Other framework loops can be integrated into main as additional 'drivable' contexts that provide run(). Or by inserting:
Running this each iteration of the loop in the framework, will ensure that the schedulers are making forward progress.