Description
The current (P2300R10) cancellation semantics of the split
algorithm are that if any of the consumers sends a stop-request then the underlying operation is cancelled for all consumers (see #200).
We should revise the semantics so that it instead only cancels the underlying operation when the last consumer goes away. However, to do this in a way that avoids the possibility of detached work, it needs to be an algorithm over an async-scope.
We should replace the current semantic of split()
with something with the following shape:
sender auto split(sender auto&& source, async_scope_token auto scope, queryable env = empty_env{});
And with the following semantics:
- Calling
split()
allocates shared state for holding the child operation state and the result of the operation. - The shared state is allocated using
get_allocator(env)
- Calling
split()
nests the input sender in the async-scope usingscope.nest(source)
and eagerly connects the result to an internal receiver, storing the operation-state in the allocated shared state. - The internal receiver's
get_env()
returnsenv
. - The returned sender has shared ownership of the shared state and is said to be "associated" with the underlying operation.
- The returned sender type is copy-constructible and copying the sender copies the ownership of the shared state.
- The first sender to connect/start a sender associated with the underlying operation which does not have an outstanding stop-request upon invocation of start() starts the underlying operation.
- Destroying the split-sender before connecting/starting it releases shared ownership of the shared state.
- The operation-state created by connecting the sender also has shared ownership of the shared-state and destroying the operation-state releases the shared ownership.
- If the operation-state is started and then receives a stop-request before the underlying operation completes, the consumer detaches from the underlying operation, completing immediately with set_stopped().
- If the underlying operation completes before a consumer detaches, then completes the consumer with an lvalue reference to the result datums (should this be const or non-const?)
- When the last owner of the shared-state is released, then if the operation has either not started, or has been started and completed, then destroys the shared-state. Otherwise, if the operation has been started and has not yet completed, then defers destruction of the shared-state until the operation completes, discarding the result.
There are similar questions with this algorithm, though, about whether the allocation of the shared state should be guarded by the async-scope. i.e. so that join()
completes only after all allocations made by split()
, et. al. are released.