From 8cd4f2ed4dc072b578e56f4939b1a00392980330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 15 Mar 2026 20:33:33 +0000 Subject: [PATCH 01/11] rebase the P3941 wording on P3826r3 - add get_completion_domain in the quoted synopsis - change SCHED-ENV to use get_start_scheduler instead of get_scheduler - remove the write_env use from on (should have been done by P3826r3) - change on to use get_start_scheduler instead of get_scheduler - update the sentence for make-sender(affine_on...) - change affine_on to use continues_on instead of schedule_from - update the reference paragraph numbers in [exec.run.loop.types] --- docs/P3941-affinity.md | 142 +++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 78 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index 69d175f..128f52d 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -1,7 +1,7 @@ --- title: Scheduler Affinity -document: P3941R2 -date: 2026-02-23 +document: D3941R3 +date: 2026-03-14 audience: - Concurrency Working Group (SG1) - Library Evolution Working Group (LEWG) @@ -31,6 +31,10 @@ meet its objective at run-time. # Change History +## R3 + +- rebase changes on the customization changes [P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html) + ## R2 - added requirement on `get_scheduler`/`get_start_scheduler` @@ -549,6 +553,13 @@ algorithm a better name. # Wording Changes +::: ednote +This wording is relative to [N5032](https://wg21.link/N5032) with +the changes from +[P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html) +applied. +::: + ::: ednote If `get_start_scheduler` is introduced add it to the synopsis in [execution.syn] after `get_scheduler` as follows: @@ -565,6 +576,8 @@ namespace std::execution { struct get_forward_progress_guarantee_t { @_unspecified_@ }; template struct get_completion_scheduler_t { @_unspecified_@ }; + template + struct get_completion_domain_t { @_unspecified_@ }; struct get_await_completion_adaptor_t { @_unspecified_@ }; inline constexpr get_domain_t get_domain{}; @@ -575,6 +588,8 @@ namespace std::execution { inline constexpr get_forward_progress_guarantee_t get_forward_progress_guarantee{}; template constexpr get_completion_scheduler_t get_completion_scheduler{}; + template + constexpr get_completion_domain_t get_completion_domain{}; inline constexpr get_await_completion_adaptor_t get_await_completion_adaptor{}; ... } @@ -609,13 +624,30 @@ expression `get_start_scheduler(get_env(rcvr))` is well-formed, an operation state that is the result of calling `connect(sndr, rcvr)` shall, if it is started, be started on an execution agent associated with the scheduler `get_start_scheduler(get_env(rcvr))`. +::: + +::: ednote + +If `get_start_scheduler` is introduced change +[[exec.snd.expos](https://wg21.link/exec.snd.expos)] paragraph 8 to have SCHED-ENV +use `get_start_scheduler` instead of `get_scheduler`: ::: +[8]{.pnum} SCHED-ENV(sch) is an expression `o2` whose type +satisfies queryable such that `o2.query(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@)` +is a prvalue with the same type and value as `sch`, and such that +`o2.query(get_domain)` is expression-equivalent to `sch.query(get_domain)`. + + ::: ednote -If `get_start_scheduler` is introduced change how `on` gets its -scheduler in [exec.on], i.e., change the use from `get_scheduler` -to use `get_start_scheduler`: +The specification of `on` [[exec.on](https://wg21.link/exec.on)] shouldn't use `write_env` as it does, +i.e., this change removes these (not removing them was an oversight +in +[P3826](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html)). +In addition, if `get_start_scheduler` is introduced change how `on` +gets its scheduler in [[exec.on](https://wg21.link/exec.on)], i.e., change the use from +`get_scheduler` to use `get_start_scheduler`: :::

@@ -623,41 +655,31 @@ to use `get_start_scheduler`:

-The expression `on.transform_sender(out_sndr, env)` has effects equivalent to: +[8]{.pnum} Otherwise, the expression `on.transform_sender(set_value, out_sndr, env)` has effects equivalent to:

``` auto&& [_, data, child] = out_sndr; if constexpr (scheduler) { auto orig_sch = - @_query-with-default_@(get_@[start_]{.add}@scheduler, env, @_not-a-scheduler_@()); - - if constexpr (same_as) { - return @_not-a-sender_@{}; - } else { - return continues_on( - starts_on(std::forward_like(data), std::forward_like(child)), - std::move(orig_sch)); - } + call-with-default(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@, @_not-a-scheduler_@(), env); + + return continues_on( + starts_on(std::forward_like(data), std::forward_like(child)), + std::move(orig_sch)); } else { auto& [sch, closure] = data; - auto orig_sch = @_query-with-default_@( - get_completion_scheduler, - get_env(child), - @_query-with-default_@(get_@[start_]{.add}@scheduler, env, @_not-a-scheduler_@())); - - if constexpr (same_as) { - return @_not-a-sender_@{}; - } else { - return write_env( - continues_on( - std::forward_like(closure)( - continues_on( - write_env(std::forward_like(child), @_SCHED-ENV_@(orig_sch)), - sch)), - orig_sch), - @_SCHED-ENV_@(sch)); - } + auto orig_sch = @_call-with-default_@( + get_completion_scheduler, @_not-a-scheduler_@(), get_env(child), env); + + return continues_on( + @[write_env(]{.rm}@ + std::forward_like(closure)( + continues_on( + @[write_env(]{.rm}@std::forward_like(child)@[,]{.rm}@ @[_SCHED-ENV_(orig_sch))]{.rm}@, + sch)), + @[_SCHED-ENV_(sch)),]{.rm}@ + orig_sch); } ```

@@ -770,26 +792,8 @@ satisfy sender, affine_on(sndr[, sch]{.rm}) is ill-formed. [3]{.pnum} Otherwise, the expression affine_on(sndr[, sch]{.rm}) -is expression-equivalent to: -transform_sender(_get-domain-early_(sndr), _make-sender_(affine_on, -[sch]{.rm}[env<>()]{.add}, sndr)) except that `sndr` -is evaluated only once. - -[4]{.pnum} -The exposition-only class template _impls-for_ -([exec.snd.expos]) is specialized for `affine_on_t` as follows: - -```c++ -namespace std::execution { - template<> - struct impls-for : default-impls { - static constexpr auto get-attrs = - [](const auto&@[ data]{.rm}]@, const auto& child) noexcept -> decltype(auto) { - return @[_JOIN-ENV_(_SCHED-ATTRS_(data),_FWD-ENV_(]{.rm}@get_env(child)@[))]{.rm}@; - }; - }; -} -``` +is expression-equivalent to +`@_make-sender_@(affine_on, @[sch]{.rm}@@[env<>()]{.add}@, sndr)`. :::{.add} [?]{.pnum} @@ -806,13 +810,13 @@ if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); }) return t.affine_on(child, ev); else return write_env( - schedule_from(get_start_scheduler(get_env(ev)), write_env(std::move(child), ev)), + continues_on(write_env(std::move(child), ev), get_start_scheduler(get_env(ev))), JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev) ); ``` [Note 1: This causes the `affine_on(sndr)` sender to become -`schedule_from(sch, sndr)` when it is connected with a receiver +`continues_on(sdnr, sch)` when it is connected with a receiver `rcvr` whose execution domain does not customize `affine_on`, for which `get_start_scheduler(get_env(rcvr))` is `sch`, and `affine_on` isn't specialized for the child sender. @@ -875,26 +879,8 @@ satisfy sender, affine_on(sndr[, sch]{.rm}) is ill-formed. [3]{.pnum} Otherwise, the expression affine_on(sndr[, sch]{.rm}) -is expression-equivalent to: -transform_sender(_get-domain-early_(sndr), _make-sender_(affine_on, -[sch]{.rm}[env<>()]{.add}, sndr)) except that `sndr` -is evaluated only once. - -[4]{.pnum} -The exposition-only class template _impls-for_ -([exec.snd.expos]) is specialized for `affine_on_t` as follows: - -```c++ -namespace std::execution { - template<> - struct impls-for : default-impls { - static constexpr auto get-attrs = - [](const auto&@[ data]{.rm}]@, const auto& child) noexcept -> decltype(auto) { - return @[_JOIN-ENV_(_SCHED-ATTRS_(data),_FWD-ENV_(]{.rm}@get_env(child)@[))]{.rm}@; - }; - }; -} -``` +is expression-equivalent to +`@_make-sender_@(affine_on, @[sch]{.rm}@@[env<>()]{.add}@, sndr)`. :::{.add} [?]{.pnum} @@ -911,13 +897,13 @@ if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); }) return t.affine_on(child, ev); else return write_env( - schedule_from(get_scheduler(get_env(ev)), write_env(std::move(child), ev)), + continues_on(write_env(std::move(child), ev), get_scheduler(get_env(ev))), JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev) ); ``` [Note 1: This causes the `affine_on(sndr)` sender to become -`schedule_from(sch, sndr)` when it is connected with a receiver +`continues_on(sndr, sch)` when it is connected with a receiver `rcvr` whose execution domain does not customize `affine_on`, for which `get_scheduler(get_env(rcvr))` is `sch`, and `affine_on` isn't specialized for the child sender. @@ -1128,7 +1114,7 @@ In [exec.run.loop.types] change the paragraph defining the completion signatures class run-loop-sender; ``` -[5]{.pnum} +[6]{.pnum} run-loop-sender is an exposition-only type that satisfies `sender`. [Let `E` be the type of an environment. If `unstoppable_token()))>` is `true`, then ]{.add} completion_signatures_of_t<run-loop-sender[, E]{.add}> is @@ -1149,7 +1135,7 @@ Otherwise it is ``` ::: -[6]{.pnum} An instance of run-loop-sender remains +[7]{.pnum} An instance of run-loop-sender remains valid until the end of the lifetime of its associated `run_loop` instance. From df3b3397b11fadeac011685c501c4d81980709fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 15 Mar 2026 21:26:21 +0000 Subject: [PATCH 02/11] fixed typos noted by the AI (the other changes AI comments on are intentional) --- docs/P3941-affinity.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index 128f52d..d3efe2d 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -662,7 +662,7 @@ gets its scheduler in [[exec.on](https://wg21.link/exec.on)], i.e., change the u auto&& [_, data, child] = out_sndr; if constexpr (scheduler) { auto orig_sch = - call-with-default(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@, @_not-a-scheduler_@(), env); + @_call-with-default_@(@[get_scheduler]{.rm}@@[get_start_scheduler]{.add}@, @_not-a-scheduler_@(), env); return continues_on( starts_on(std::forward_like(data), std::forward_like(child)), @@ -816,7 +816,7 @@ else ``` [Note 1: This causes the `affine_on(sndr)` sender to become -`continues_on(sdnr, sch)` when it is connected with a receiver +`continues_on(sndr, sch)` when it is connected with a receiver `rcvr` whose execution domain does not customize `affine_on`, for which `get_start_scheduler(get_env(rcvr))` is `sch`, and `affine_on` isn't specialized for the child sender. From 08a238787ae4b736440aa2154941b6beb970215f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Thu, 19 Mar 2026 23:20:44 +0000 Subject: [PATCH 03/11] applied enhancements/fixes suggested by Eric --- docs/P3941-affinity.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index d3efe2d..d79ff73 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -1016,7 +1016,7 @@ template auto await_transform(Sender&& sndr) noexcept; ``` [9]{.pnum} -_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@), *this)`. +_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(@[transform_sender(]{.add}@affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@)@[, get_env(*this))]{.add}@, *this)`. ::: rm ``` @@ -1052,8 +1052,8 @@ explicit task_scheduler(Sch&& sch, Allocator alloc = {}); ::: add [?]{.pnum} _Mandates_: Let `e` be an environment and let `E` be `decltype(e)`. -If `unstoppable_token` is `true`, then -the type `completion_signatures_of_t` +If `unstoppable_token>` is `true`, then +the type `completion_signatures_of_t, E>` only includes `set_value_t()`, otherwise it may additionally include `set_stopped_t()`. ::: @@ -1091,7 +1091,7 @@ namespace std::execution { ts-sender is an exposition-only class that models `sender` ([exec.snd]) and for which completion_signatures_of_t<ts-sender[, E]{.add}> -denotes[:]{.rm}[ `completion_signatures` if `unstoppable_token()))>` is `true`, and +denotes[:]{.rm}[ `completion_signatures` if `unstoppable_token>` is `true`, and otherwise `completion_signatures`.]{.add} ::: rm @@ -1116,7 +1116,7 @@ class run-loop-sender; [6]{.pnum} run-loop-sender is an exposition-only type that satisfies `sender`. -[Let `E` be the type of an environment. If `unstoppable_token()))>` is `true`, +[Let `E` be the type of an environment. If `unstoppable_token>` is `true`, then ]{.add} completion_signatures_of_t<run-loop-sender[, E]{.add}> is ::: rm From 0bf2febb7f4e9bbc85042d2fab35e5fd19d9cbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 20 Mar 2026 20:36:51 +0000 Subject: [PATCH 04/11] minor fix --- docs/P3941-affinity.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index d79ff73..4424724 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -1,7 +1,7 @@ --- title: Scheduler Affinity -document: D3941R3 -date: 2026-03-14 +document: P3941R3 +date: 2026-03-19 audience: - Concurrency Working Group (SG1) - Library Evolution Working Group (LEWG) From a91c23b9872120bd095ccf303a019790cb2a3d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 20 Mar 2026 21:15:52 +0000 Subject: [PATCH 05/11] fixed a few minor issues with the wording --- docs/P3941-affinity.md | 4 ++-- examples/issue-symmetric-transfer.cpp | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index 4424724..4300fbb 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -1016,7 +1016,7 @@ template auto await_transform(Sender&& sndr) noexcept; ``` [9]{.pnum} -_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(@[transform_sender(]{.add}@affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@)@[, get_env(*this))]{.add}@, *this)`. +_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(@[transform_sender(]{.add}@affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@)@[, get_env())]{.add}@, *this)`. ::: rm ``` @@ -1051,7 +1051,7 @@ explicit task_scheduler(Sch&& sch, Allocator alloc = {}); ::: add [?]{.pnum} -_Mandates_: Let `e` be an environment and let `E` be `decltype(e)`. +_Mandates_: Let `E` be the type of a queryable. If `unstoppable_token>` is `true`, then the type `completion_signatures_of_t, E>` only includes `set_value_t()`, otherwise it may additionally include diff --git a/examples/issue-symmetric-transfer.cpp b/examples/issue-symmetric-transfer.cpp index a029cd7..d0fab95 100644 --- a/examples/issue-symmetric-transfer.cpp +++ b/examples/issue-symmetric-transfer.cpp @@ -4,16 +4,13 @@ #include #include #include -#define NODEBUG -#include namespace ex = beman::execution; template ex::task test() { for (std::size_t i{}; i < 1000000; ++i) { - // co_await std::invoke([]() -> ex::task<> { co_await ex::just(); }); - co_await std::invoke([]() -> ex::task<> { co_return; }); + co_await std::invoke([]() -> ex::task<> { co_await ex::when_all(ex::just()); }); } } @@ -22,9 +19,6 @@ int main() { struct inline_env { using scheduler_type = ex::inline_scheduler; }; - [[maybe_unused]] auto env = affine_env{}; - [[maybe_unused]] auto sched = inline_env::scheduler_type{}; - - // ex::sync_wait(test()); // OK - // ex::sync_wait(test()); // error: stack overflow without symmetric transfer + ex::sync_wait(test()); // OK + ex::sync_wait(test()); // error: stack overflow without symmetric transfer } From d2407d9bec6259fc5d84908a6b26a98d60da877b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 20 Mar 2026 23:04:56 +0000 Subject: [PATCH 06/11] change as_awaitable to transform the sender if necessary --- docs/P3941-affinity.md | 73 +++++++++++++++++++++++++-- examples/issue-symmetric-transfer.cpp | 2 +- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index 4300fbb..e596a37 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -34,6 +34,8 @@ meet its objective at run-time. ## R3 - rebase changes on the customization changes [P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html) +- use `transform_sender` in `as_awaitable` to locate possible customisation of nested + senders in [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)] ## R2 @@ -770,6 +772,71 @@ scheduler `get_scheduler(get_env(rcvr))`. ::: +::: ednote + +Change [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)] +paragraph 7 such that it tries to locate a customisation for +`as_awaitable` on the transformed nested sender. + +::: + +[7]{.pnum} `as_awaitable` is a customization point object. For +subexpressions `expr` and `p` where `p` is an lvalue, `Expr` names +the type `decltype((expr))` and `Promise` names the type +`decay_t, as_awaitable(expr, p)` is expression-equivalent +to, except that the evaluations of `expr` and `p` are indeterminately +sequenced: + +

    +
  • +

    [7.1]{.pnum} +`expr.as_awaitable(p)` if that expression is well-formed. +

    +

    +Mandates: `@_is-awaitable_@` is `true`, where `A` is +the type of the expression above. +

    +
  • +
  • + +[7.?]{.pnum} + +[`@_adapt-for-await-completion_@(transform_sender(expr, get_env(p))).as_awaitable(p)` +if this expression is well-formed, `sender_in>` is `true`, +and `@_single-sender-value-type_@>` is well-formed.]{.add} + +
  • +
  • +[7.2]{.pnum} Otherwise, `(void(p), expr)` if +`decltype(@_GET-AWAITER_@(expr))` satisfies `@_is-awaiter_@`. +
  • +
  • +

    +[7.3]{.pnum} [Otherwise, `@_sender-awaitable_@{@_adapted-expr_@, p}` if]{.rm} +

    +

    [`@_has-queryable-await-completion-adaptor_@`]{.rm}

    +

    [and]{.rm}

    +

    [`@_awaitable-sender_@`]{.rm}

    +

    +[are both satisfied, where `@_adapted-expr_@` is +`get_await_completion_adaptor(get_env(expr))(expr)`, except that +`expr` is evaluated only once.]{.rm} +

    +
  • +
  • +[7.4]{.pnum} Otherwise, [`@_sender-awaitable_@{expr, p}` if +`@_awaitable-sender_@` is `true`.]{.rm} +[`@_sender-awaitable_@{@_adapt-for-await-completion_@(transform_sender(expr, get_env(p))), P}` if `sender_in>` is `true` and `@_single-sender-value-type_@>` is well-formed]{.add} + +
  • +
  • [7.5]{.pnum} Otherwise, `(void(p), expr)`. +
  • +
+ +[8]{.pnum} [The expression `@_adapt-for-await-completion_@(s)` is +`get_await_completion_adaptor(get_env(s))(s)` if that is well-formed, +and `s` otherwise.]{.add} + ::: ednote Change [exec.affine.on] to use only one parameter, require an infallible scheduler from the receiver, and add a default implementation @@ -810,7 +877,7 @@ if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); }) return t.affine_on(child, ev); else return write_env( - continues_on(write_env(std::move(child), ev), get_start_scheduler(get_env(ev))), + continues_on(write_env(std::move(child), ev), get_start_scheduler(ev)), JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev) ); ``` @@ -897,7 +964,7 @@ if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); }) return t.affine_on(child, ev); else return write_env( - continues_on(write_env(std::move(child), ev), get_scheduler(get_env(ev))), + continues_on(write_env(std::move(child), ev), get_scheduler(ev)), JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev) ); ``` @@ -1016,7 +1083,7 @@ template auto await_transform(Sender&& sndr) noexcept; ``` [9]{.pnum} -_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(@[transform_sender(]{.add}@affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@)@[, get_env())]{.add}@, *this)`. +_Returns_: If `same_as` is `true` returns `as_awaitable(​std​::​​forward(sndr), *this);` otherwise returns `as_awaitable(affine_on(​std​::​​forward(sndr)@[, SCHED(*this)]{.rm}@), *this)`. ::: rm ``` diff --git a/examples/issue-symmetric-transfer.cpp b/examples/issue-symmetric-transfer.cpp index d0fab95..84c136a 100644 --- a/examples/issue-symmetric-transfer.cpp +++ b/examples/issue-symmetric-transfer.cpp @@ -20,5 +20,5 @@ int main() { using scheduler_type = ex::inline_scheduler; }; ex::sync_wait(test()); // OK - ex::sync_wait(test()); // error: stack overflow without symmetric transfer + //ex::sync_wait(test()); // error: stack overflow without symmetric transfer } From 6db342338db902c5971ad8260e9ce7c1977fd5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 21 Mar 2026 20:41:53 +0000 Subject: [PATCH 07/11] changed affine_on to use UNSTOPPABLE-SCHEDULER. --- docs/P3941-affinity.md | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index e596a37..25d4a11 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -1,7 +1,7 @@ --- title: Scheduler Affinity document: P3941R3 -date: 2026-03-19 +date: 2026-03-21 audience: - Concurrency Working Group (SG1) - Library Evolution Working Group (LEWG) @@ -876,12 +876,20 @@ using child_tag_t = tag_of_t>; if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); }) return t.affine_on(child, ev); else - return write_env( - continues_on(write_env(std::move(child), ev), get_start_scheduler(ev)), - JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev) - ); + return continues_on(child, @_UNSTOPPABLE-SCHEDULER_@(get_start_scheduler(ev))); ``` +[?]{.pnum} For a subexpression `sch` whose type satisfies `scheduler`, +let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type +satisfies `scheduler` such that: +
    +
  • [?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.
  • +
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q.args...)` +is expression-equivalent to `sch.query(q, args...)`.
  • +
  • [?.3]{.pnum} Let `f` be the subexpression `@_UNSTOPPABLE-SCHEDULER_@(other)`. `e == f` +is expression-equivalent to `sch == other`.
  • +
+ [Note 1: This causes the `affine_on(sndr)` sender to become `continues_on(sndr, sch)` when it is connected with a receiver `rcvr` whose execution domain does not customize `affine_on`, @@ -963,12 +971,20 @@ using child_tag_t = tag_of_t>; if constexpr (requires(const child_tag_t& t){ t.affine_on(child, ev); }) return t.affine_on(child, ev); else - return write_env( - continues_on(write_env(std::move(child), ev), get_scheduler(ev)), - JOIN-ENV(env{prop{get_stop_token, never_stop_token()}}, ev) - ); + return continues_on(child, @_UNSTOPPABLE-SCHEDULER_@(get_scheduler(ev))); ``` +[?]{.pnum} For a subexpression `sch` whose type satisfies `scheduler`, +let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type +satisfies `scheduler` such that: +
    +
  • [?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.
  • +
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q.args...)` +is expression-equivalent to `sch.query(q, args...)`.
  • +
  • [?.3]{.pnum} Let `f` be the subexpression `@_UNSTOPPABLE-SCHEDULER_@(other)`. `e == f` +is expression-equivalent to `sch == other`.
  • +
+ [Note 1: This causes the `affine_on(sndr)` sender to become `continues_on(sndr, sch)` when it is connected with a receiver `rcvr` whose execution domain does not customize `affine_on`, From 73687e4f9dd0ea607aebedc867ecc1efabf846ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 21 Mar 2026 22:29:44 +0000 Subject: [PATCH 08/11] fixed CI issues and some feedback on the wording --- docs/P3941-affinity.md | 22 ++++------------------ examples/issue-symmetric-transfer.cpp | 4 +++- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/P3941-affinity.md b/docs/P3941-affinity.md index 25d4a11..3fc1f2d 100644 --- a/docs/P3941-affinity.md +++ b/docs/P3941-affinity.md @@ -34,7 +34,7 @@ meet its objective at run-time. ## R3 - rebase changes on the customization changes [P3826r3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3826r3.html) -- use `transform_sender` in `as_awaitable` to locate possible customisation of nested +- use `transform_sender` in `as_awaitable` to locate possible customization of nested senders in [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)] ## R2 @@ -775,7 +775,7 @@ scheduler `get_scheduler(get_env(rcvr))`. ::: ednote Change [[exec.as.awaitable](https://wg21.link/exec.as.awaitable#7)] -paragraph 7 such that it tries to locate a customisation for +paragraph 7 such that it tries to locate a customization for `as_awaitable` on the transformed nested sender. ::: @@ -884,19 +884,12 @@ let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type satisfies `scheduler` such that:
  • [?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.
  • -
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q.args...)` +
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q, args...)` is expression-equivalent to `sch.query(q, args...)`.
  • [?.3]{.pnum} Let `f` be the subexpression `@_UNSTOPPABLE-SCHEDULER_@(other)`. `e == f` is expression-equivalent to `sch == other`.
-[Note 1: This causes the `affine_on(sndr)` sender to become -`continues_on(sndr, sch)` when it is connected with a receiver -`rcvr` whose execution domain does not customize `affine_on`, -for which `get_start_scheduler(get_env(rcvr))` is `sch`, and `affine_on` -isn't specialized for the child sender. -end note] - [?]{.pnum} _Recommended Practice_: Implementations should provide `affine_on` member functions for senders which are known to resume on the @@ -979,19 +972,12 @@ let `@_UNSTOPPABLE-SCHEDULER_@(sch)` be an expression `e` whose type satisfies `scheduler` such that:
  • [?.1]{.pnum} `schedule(e)` is expression-equivalent to `unstoppable(schedule(sch))`.
  • -
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q.args...)` +
  • [?.2]{.pnum} For any query object `q` and pack of subexpressions `args...`, `e.query(q, args...)` is expression-equivalent to `sch.query(q, args...)`.
  • [?.3]{.pnum} Let `f` be the subexpression `@_UNSTOPPABLE-SCHEDULER_@(other)`. `e == f` is expression-equivalent to `sch == other`.
-[Note 1: This causes the `affine_on(sndr)` sender to become -`continues_on(sndr, sch)` when it is connected with a receiver -`rcvr` whose execution domain does not customize `affine_on`, -for which `get_scheduler(get_env(rcvr))` is `sch`, and `affine_on` -isn't specialized for the child sender. -end note] - [?]{.pnum} _Recommended Practice_: Implementations should provide `affine_on` member functions for senders which are known to resume on the diff --git a/examples/issue-symmetric-transfer.cpp b/examples/issue-symmetric-transfer.cpp index 84c136a..765fcd6 100644 --- a/examples/issue-symmetric-transfer.cpp +++ b/examples/issue-symmetric-transfer.cpp @@ -19,6 +19,8 @@ int main() { struct inline_env { using scheduler_type = ex::inline_scheduler; }; +#ifndef _MSC_VER ex::sync_wait(test()); // OK - //ex::sync_wait(test()); // error: stack overflow without symmetric transfer +#endif + // ex::sync_wait(test()); // error: stack overflow without symmetric transfer } From e35226557b8cfef3d099efe35ae67a4eefa7a7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 21 Mar 2026 23:33:52 +0000 Subject: [PATCH 09/11] fix another CI issu --- examples/issue-symmetric-transfer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/issue-symmetric-transfer.cpp b/examples/issue-symmetric-transfer.cpp index 765fcd6..0682f74 100644 --- a/examples/issue-symmetric-transfer.cpp +++ b/examples/issue-symmetric-transfer.cpp @@ -15,8 +15,8 @@ ex::task test() { } int main() { - struct affine_env {}; - struct inline_env { + [[maybe_unused]] struct affine_env {}; + [[maybe_unused]] struct inline_env { using scheduler_type = ex::inline_scheduler; }; #ifndef _MSC_VER From 8f3f2b1fa30c1013bac7fb81e5b0caa92ee9d4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 21 Mar 2026 23:49:45 +0000 Subject: [PATCH 10/11] another error discovered by CI --- examples/issue-symmetric-transfer.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/issue-symmetric-transfer.cpp b/examples/issue-symmetric-transfer.cpp index 0682f74..0f3c7dd 100644 --- a/examples/issue-symmetric-transfer.cpp +++ b/examples/issue-symmetric-transfer.cpp @@ -15,10 +15,13 @@ ex::task test() { } int main() { - [[maybe_unused]] struct affine_env {}; - [[maybe_unused]] struct inline_env { + struct affine_env {}; + struct inline_env { using scheduler_type = ex::inline_scheduler; }; + + [[maybe_unused]] affine_env ae{}; + [[maybe_unused]] inline_env ie{}; #ifndef _MSC_VER ex::sync_wait(test()); // OK #endif From 176f1f2e9f404aeda5bee7b73becb0d87bb15f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 21 Mar 2026 23:59:37 +0000 Subject: [PATCH 11/11] another attempt to get past the CI --- examples/issue-symmetric-transfer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/issue-symmetric-transfer.cpp b/examples/issue-symmetric-transfer.cpp index 0f3c7dd..f6455cf 100644 --- a/examples/issue-symmetric-transfer.cpp +++ b/examples/issue-symmetric-transfer.cpp @@ -14,12 +14,12 @@ ex::task test() { } } -int main() { - struct affine_env {}; - struct inline_env { - using scheduler_type = ex::inline_scheduler; - }; +struct affine_env {}; +struct inline_env { + using scheduler_type = ex::inline_scheduler; +}; +int main() { [[maybe_unused]] affine_env ae{}; [[maybe_unused]] inline_env ie{}; #ifndef _MSC_VER