- 
                Notifications
    
You must be signed in to change notification settings  - Fork 5
 
Description
Background: continues_on and schedule_from:
std::execution has two customizable algorithms for transfering execution from one context to another: continues_on and schedule_from. The reason for having two is due to the fact there are two execution contexts in play: the context we're transitioning from and the one we're transitioning to. The source context may need special sauce to transfer execution back to the CPU, and the destination context may need special sauce to transition from the CPU onto it.
The schedule_from algorithm looks for customizations based on the domain of the destination, and the continues_on algorithm dispatches based on the domain of the source. A "domain" is a tag type associated with an execution context that is used to find algorithm customizations for that context. The continues_on algorithm is required to lower to the result of a call to schedule_from. In this way, every context transition gets all the special sauce it needs to get from one arbitrary context to another, even if the two contexts know nothing about each other.
We can see this in the definitions of the continues_on and schedule_from customizations points:
| Algorithm | Returns | 
|---|---|
continues_on(sndr, sched) | 
transform_sender(get-domain-early(sndr), make-sender(continues_on, sched, sndr)) | 
schedule_from(sched, sndr) | 
transform_sender(query-or-default(get_domain, sched, default_domain{}), make-sender(schedule_from, sched, sndr)) | 
By asking for the predecessor sender's domain, continues_on uses the domain of the source to find its customization. And by asking for the scheduler's domain, schedule_from uses the domain of the destination.
The final piece is the transformation, within the connect customization point, of the continues_on sender to the schedule_from sender, which is done with the continues_on.transform_sender(Sndr, Env) member function (see [exec.continues.on] p5).
Problem 1
When connect-time customization was added to std::execution in
[@P3303], the logic of continues_on/schedule_from
customization accidentally got reversed: The exposition-only get-domain-late function,
which is called from connect, is used to determine the domain to use to find a sender
transform function. It says:
template<class Sndr, class Env> constexpr auto get-domain-late(const Sndr& sndr, const Env& env) noexcept;
Effects: Equivalent to:
- If
 sender-for<Sndr, continues_on_t>istrue, thenreturn Domain();where
Domainis the type of the following expression:[] { auto [_, sch, _] = sndr; return query-or-default(get_domain, sch, default_domain()); }();[Note 1: The
continues_onalgorithm works in tandem withschedule_from
([exec.schedule.from]) to give scheduler authors a way to customize both how to
transition onto (continues_on) and off of (schedule_from) a given execution
context. Thus,continues_onignores the domain of the predecessor and uses the
domain of the destination scheduler to select a customization, a property that is
unique tocontinues_on. That is why it is given special treatment here. — end
note]
- Otherwise,
 return Domain();where
Domainis the first of the following expressions that is well-formed and whose type is notvoid:
get_domain(get_env(sndr))completion-domain<void>(sndr)get_domain(env)get_domain(get_scheduler(env))default_domain()
Paragraph 14.1 above gets the roles of continues_on and schedule_from mixed up. They should be reversed.
Problem 2
All of the adaptor algorithm CPOs use the domain of the predecessor(s) to find customizations. For example, then(sndr, fn) returns transform_sender(get-domain-early(sndr), make-sender(then, fn, sndr)); i.e., the domain is pulled from sndr. A sender that advertizes a domain is making an assertion about where it will complete. Where the predecessor completes is where the current sender will start. Therefore, then(sndr, fn) is dispatching based on where the then sender will start.
If we look at the connect customization point at how a late customization is found, we see that before it does anything else, it transforms the input sender as follows:
transform_sender(decltype(get-domain-late(sndr, get_env(rcvr))){}, sndr, get_env(rcvr))
We can see that when passed a then sender, we ask the then sender for its domain (and use the domain of the receiver's env as a fallback). That means that for then senders, connect dispatches to a customization based on the domain of the then sender itself. That is different from early customization, which used the domain of the predecessor. The inconsistency is not intentional.
For then and most other adaptors, it doesn't make any difference. The then sender completes wherever its predecessor completes, so the "start" and "complete" domains are the same. That is not the case, however, for continues_on. The domain on which it starts can be different from the domain on which it completes.
The tl;dr of problem 2 is that connect uses the wrong domain to find a customization for continues_on. The domain of the continues_on(sndr, sched) sender is the domain on which it will complete, which is the domain of sched. But continues_on should be using the domain on which it starts to find a customization.
A principled solution would be to recognize that a sender really has 2 associated domains: the starting domain and the completing domain. it follows that we should have two queries: get_starting_domain and get_completion_domain. Early customization would use the completion domain of the predecessor, whereas late customization would use the starting domain of the sender itself.
A simpler solution recognizes that for all adaptors besides continues_on, the starting domain and the completion domain are the same, so separate queries are not needed. (This is even true for the schedule_from sender: early or late, it should always use the domain of the destination scheduler). So we can leave connect alone and treat continues_on as special in get-domain-late.
Back to First Principles
Given what we already know about why schedule_from and continues_on both exist and find customization the way they do, we can build a table that shows how customizations should be selected for all the scenarios of interest. Consider the sender expression A | continues_on(Sch) | B. The table below shows the domain that each algorithm should use to find a customization.
Table 2: Domain that should be used to find customizations in  A | continues_on(Sch) | B
| Algorithm | Early customization uses... | Late customization uses... | get_domain(get_env(ALGO(A...))) should return... | 
|---|---|---|---|
schedule_from | 
domain of Sch or default_domain | 
domain of Sch or default_domain | 
domain of Sch | 
continues_on | 
domain of A or default_domain | 
domain of A or domain of Env | 
domain of Sch | 
B | 
domain of Sch or default_domain | 
domain of Sch or domain of Env | 
n/a | 
We can use this table to correct the effected parts of the spec:
- 
get-domain-late(sndr, env)should be changed to:- when 
sndriscompletes_on(Pred,Sch), return the domain ofPredand use domain ofenvas a fallback. - when 
sndrisschedule_from(Sch,Pred), return the domain ofSchand usedefault_domainas a fallback (ignoringenv). - otherwise, return the domain of 
sndrand use domain ofenvas a fallback. 
 - when 
 - 
Change the
SCHED-ATTRSandSCHED-ENVpseudo-macros from [exec.snd.expos] to
accept a fallback environment in addition to a scheduler, but never use the fallback
env to answer theget_domainquery. - 
Change all uses of
SCHED-ATTRSandSCHED-ENVas appropriate. 
Proposed Resolution
[Editorial note: Change [exec.snd.expos]/p6 as follows:]
For a scheduler
schand environmentenv,
SCHED-ATTRS(sch, env)is an expressiono1whose type
satisfiesqueryablesuch that: [Editorial note: reformatted as a list.]
o1.query(get_completion_scheduler<is an expression with the same type and value asTag
set_value_t>)
schwhere,Tagis one ofset_value_torset_stopped_tand such
that
o1.query(get_completion_scheduler<Tag>)is ill-formed forTag
other thanset_value_t,
o1.query(get_domain)is expression-equivalent tosch.query(get_domain),
andFor a pack of subexpressions
asand query objectQsuch that
forwarding_query(Q)istrue,o1.query(Q, as...)is
expression-equivalent toenv.query(Q, as...).
SCHED-ATTRS(sch)is expression-equivalent to
SCHED-ATTRS(sch, execution::env<>{}).
SCHED-ENV(sch, env)is an expressiono2whose type
satisfiesqueryablesuch that: [Editorial note: reformatted as a list.]
o2.query(get_scheduler)is a prvalue with the same type and value assch,and
such that
o2.query(get_domain)is expression-equivalent to
sch.query(get_domain)., andFor a pack of subexpressions
asand query objectQsuch that
forwarding_query(Q)istrue,o1.query(Q, as...)is
expression-equivalent toenv.query(Q, as...).
SCHED-ENV(sch)is expression-equivalent to
SCHED-ENV(sch, execution::env<>{}).
[Editorial note: Change [exec.snd.expos]/p14 as follows:]
template<class Sndr, class Env> constexpr auto get-domain-late(const Sndr& sndr, const Env& env) noexcept;
Effects: Equivalent to:
[Editorial note: Taken from 14.3 with edits.] Let
DEFAULT-LATE-DOMAIN(S, E)be the first of the following
expressions that is well-formed and whose type is notvoid:
get_domain(get_env(sndr))completion-domain<void>(sndr)get_domain(env)get_domain(get_scheduler(env))default_domain()If
sender-for<Sndr,is
continues_on_tschedule_from_t>true, thenreturn Domain();where
Domainis the type of the following expression:[] { auto [_, sch, _] = sndr; return query-or-default(get_domain, sch, default_domain()); }();[Note 1: The
continues_onschedule_fromalgorithm works
in tandem withschedule_from([exec.schedule.from])continues_on
([exec.continues.on]) to give scheduler authors a way to customize both how
to transitionontooff of (continues_on) andoffonto (
ofschedule_from) a given execution context. Thus,
continues_onschedule_fromignores the domain of the
predecessor and uses the domain of the destination scheduler to select a
customization, a property that is unique to
continues_onschedule_from. That is why it is given
special treatment here. — end note]
Otherwise, if
sender-for<Sndr, continues_on_t>is
true, thenreturn Domain();where
Domainis the type of the following expression:[] { const auto& [_, _, child] = sndr; return DEFAULT-LATE-DOMAIN(child, env); }();
Otherwise,
DEFAULT-LATE-DOMAIN(sndr, env)return Domain();where
Domainis the first of the following expressions that is well-formed and
whose type is notvoid:
get_domain(get_env(sndr))completion-domain<void>(sndr)get_domain(env)get_domain(get_scheduler(env))default_domain()
[Editorial note: Change [exec.starts.on]/p4.1 as follows:]
Let [Editorial note: ...as before...]; otherwise:
starts_on.transform_env(out_sndr, env)is equivalent to:auto&& [_, sch, _] = out_sndr;return JOIN-ENV(SCHED-ENV(sch), FWD-ENV(env));return SCHED-ENV(sch, env);
[Editorial note: Change [exec.continues.on]/p4 as follows:]
The exposition-only class template
impls-foris specialized forcontinues_on_tas follows:namespace std::execution { template<> struct impls-for<continues_on_t> : default-impls { static constexpr auto get-attrs = [](const auto& data, const auto& child) noexcept -> decltype(auto) {return JOIN-ENV(SCHED-ATTRS(data), FWD-ENV(get_env(child)));return SCHED-ATTRS(data, get_env(child)); }; }; }
[Editorial note: Change [exec.on]/p7 as follows:]
The expression
on.transform_env(out_sndr, env)has effects equivalent to:auto&& [_, data, _] = out_sndr; if constexpr (scheduler<decltype(data)>) {return JOIN-ENV(SCHED-ENV(std::forward_like<OutSndr>(data)), FWD-ENV(std::forward<Env>(env)));return SCHED-ENV(std::forward_like<OutSndr>(data), std::forward<Env>(env)); } else { return std::forward<Env>(env); }