From 91743d3a3da5e7cce6ab841aec07d85adf966c86 Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Sun, 5 Apr 2026 18:58:17 +0200 Subject: [PATCH 1/2] P3941R4 Scheduler Affinity Fixes NB US 232-366, US 233-365, US 234-364, US 235-363, US 236-362 (C++26 CD). --- source/exec.tex | 267 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 193 insertions(+), 74 deletions(-) diff --git a/source/exec.tex b/source/exec.tex index 4455a1c991..4a91978f60 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -463,6 +463,7 @@ // \ref{exec.queries}, queries struct @\libglobal{get_domain_t}@ { @\unspec@ }; struct @\libglobal{get_scheduler_t}@ { @\unspec@ }; + struct @\libglobal{get_start_scheduler_t}@ { @\unspec@ }; struct @\libglobal{get_delegation_scheduler_t}@ { @\unspec@ }; struct @\libglobal{get_forward_progress_guarantee_t}@ { @\unspec@ }; template @@ -729,14 +730,6 @@ template with_error(E) -> with_error; - template<@\libconcept{scheduler}@ Sch> - struct change_coroutine_scheduler { - using type = remove_cvref_t; - type scheduler; - }; - template<@\libconcept{scheduler}@ Sch> - change_coroutine_scheduler(Sch) -> change_coroutine_scheduler; - // \ref{exec.task}, class template \tcode{task} template class @\libglobal{task}@; @@ -962,6 +955,34 @@ \tcode{forwarding_query(execution::get_scheduler)} is a core constant expression and has value \tcode{true}. +\rSec2[exec.get.start.scheduler]{\tcode{execution::get_start_scheduler}} + +\pnum +\tcode{get_start_scheduler} asks a queryable object +for the scheduler an operation will be or was started on. + +\pnum +The name \tcode{get_start_scheduler} denotes a query object. +For a subexpression \tcode{env}, +\tcode{get_start_scheduler(\brk{}env)} is expression-equivalent to +\tcode{\exposid{MANDATE-NOTHROW}(\exposid{AS-CONST}(env).query(get_start_scheduler))}. + +\mandates +If the expression above is well-formed, +its type satisfies \libconcept{scheduler}. + +\pnum +\tcode{forwarding_query(execution::get_start_scheduler)} +is a core constant expression and has value \tcode{true}. + +\pnum +Given subexpressions \tcode{sndr} and \tcode{rcvr} +such that \tcode{sender_to} is \tcode{true} +and the expression \tcode{get_start_scheduler(get_env(rcvr))} is well-formed, +an operation state that is the result of calling \tcode{connect(sndr, rcvr)} +shall, if it is started, be started on an execution agent +associated with the scheduler \tcode{get_start_scheduler(get_env(rcvr))}. + \rSec2[exec.get.delegation.scheduler]{\tcode{execution::get_delegation_scheduler}} \pnum @@ -1163,6 +1184,26 @@ pending completion of any receivers connected to the sender objects returned from \tcode{schedule}. +\pnum +The exposition-only \exposconcept{infallible-scheduler} concept +defines the requirements of a scheduler type +whose \tcode{schedule} asynchronous operation +can only complete with \tcode{set_value} unless stop can be requested: +\begin{codeblock} +template + concept @\defexposconcept{infallible-scheduler}@ = + @\libconcept{scheduler}@ && + (@\libconcept{same_as}@, + completion_signatures_of_t())), Env>> || + (!@\libconcept{unstoppable_token}@> && ( + @\libconcept{same_as}@, + completion_signatures_of_t())), Env>> || + @\libconcept{same_as}@, + completion_signatures_of_t())), Env>>) + ) + ) +\end{codeblock} + \rSec1[exec.recv]{Receivers} \rSec2[exec.recv.concepts]{Receiver concepts} @@ -1380,6 +1421,28 @@ \rSec2[exec.snd.expos]{Exposition-only entities} +\pnum +Given an expression \tcode{sndr}, +whose type is any sender type defined in the standard library, +it is unspecified +whether the expression \tcode{sndr.affine_on()} is well-formed. +If that expression is well-formed, +then the evaluation thereof meets the semantic requirements of +the \tcode{affine_on}\iref{exec.affine.on} algorithm. + +\pnum +Given an expression \tcode{sndr}, +whose type is any sender type defined in the standard library, +and an expression \tcode{p}, +whose type is a promise type, +it is unspecified +whether the expression \tcode{sndr.as_awaitable(p)} is well-formed. +If that expression is well-formed, +then the evaluation thereof meets the semantic requirements of +the \tcode{as_awaitable}\iref{exec.as.awaitable} algorithm. + + + \pnum Subclause \ref{exec.snd} makes use of the following exposition-only entities. @@ -1441,7 +1504,7 @@ \tcode{sch.query(get_domain)}. \tcode{\exposid{SCHED-ENV}(sch)} is an expression \tcode{o2} whose type satisfies \exposconcept{queryable} -such that \tcode{o2.query(get_scheduler)} is a prvalue +such that \tcode{o2.query(get_start_scheduler)} is a prvalue with the same type and value as \tcode{sch}, and such that \tcode{o2.query(get_domain)} is expression-equivalent to \tcode{sch.query(get_domain)}. @@ -3700,7 +3763,7 @@ auto&& [_, data, child] = out_sndr; if constexpr (@\libconcept{scheduler}@) { auto orig_sch = - @\exposid{query-with-default}@(get_scheduler, env, @\exposid{not-a-scheduler}@()); + @\exposid{query-with-default}@(get_start_scheduler, env, @\exposid{not-a-scheduler}@()); if constexpr (@\libconcept{same_as}@) { return @\exposid{not-a-sender}@{}; @@ -3743,7 +3806,8 @@ Calling \tcode{start(op)} shall \begin{itemize} \item -remember the current scheduler, \tcode{get_scheduler(get_env(rcvr))}; +remember the current scheduler, +which is obtained by \tcode{get_start_scheduler(get_env(rcvr))}; \item start \tcode{sndr} on an execution agent belonging to \tcode{sch}'s associated execution resource; @@ -5667,6 +5731,10 @@ return @\exposid{loop}@->get_scheduler(); } + auto query(execution::get_start_scheduler_t) const noexcept { + return @\exposid{loop}@->get_scheduler(); + } + auto query(execution::get_delegation_scheduler_t) const noexcept { return @\exposid{loop}@->get_scheduler(); } @@ -6443,9 +6511,15 @@ \pnum \exposid{run-loop-sender} is an exposition-only type that satisfies \libconcept{sender}. -\tcode{completion_signatures_of_t<\exposid{run-\linebreak{}loop-sender}>} is +Let \tcode{E} be the type of an environment. +If \tcode{unstoppable_token>} is \tcode{true}, then +\tcode{completion_signatures_of_t<\exposid{run-\linebreak{}loop-sender}, E>} is +\begin{codeblock} +completion_signatures +\end{codeblock} +Otherwise, it is \begin{codeblock} -completion_signatures +completion_signatures \end{codeblock} \pnum @@ -6797,29 +6871,42 @@ \tcode{\exposconcept{is-awaitable}} is \tcode{true}, where \tcode{A} is the type of the expression above. \item +Otherwise, +\begin{codeblock} +@\exposid{adapt-for-await-completion}@(transform_sender(expr, get_env(p))).as_awaitable(p) +\end{codeblock} +if this expression is well-formed, +\tcode{sender_in>} is \tcode{true}, and +\tcode{\exposid{single-sender-\brk{}value-type}>} +is well-formed, +except that \tcode{p} is only evaluated once. +\item Otherwise, \tcode{(void(p), expr)} if \tcode{decltype(\exposid{GET-AWAITER}(expr))} satisfies \tcode{\exposconcept{is-awaiter}}. \item -Otherwise, \tcode{\exposid{sender-awaitable}\{\exposid{adapted-expr}, p\}} -if -\begin{codeblock} -@\exposid{has-queryable-await-completion-adaptor}@ -\end{codeblock} -and +Otherwise, \begin{codeblock} -@\exposid{awaitable-sender}@ +sender-awaitable{adapt-for-await-completion(transform_sender(expr, get_env(p))), p} \end{codeblock} -are both satisfied, where \exposid{adapted-expr} is -\tcode{get_await_completion_adaptor(get_env(expr))(expr)}, -except that \tcode{expr} is evaluated only once. -\item -Otherwise, \tcode{\exposid{sender-awaitable}\{expr, p\}} -if \tcode{\exposconcept{awaitable-sender}} is \tcode{true}. +if \tcode{sender_in>} is \tcode{true} and +\tcode{\exposid{single-sender-value-type}>} +is well-formed, +except that \tcode{p} is only evaluated once. \item Otherwise, \tcode{(void(p), expr)}. \end{itemize} +\pnum +\tcode{\exposid{adapt-for-await-completion}(s)} is expression-equivalent to +\begin{itemize} +\item +\tcode{get_await_completion_adaptor(get_env(s))(s)} if that is well-formed, +except that \tcode{s} is evaluated only once, +\item +otherwise, \tcode{s}. +\end{itemize} + \rSec2[exec.with.awaitable.senders]{\tcode{execution::with_awaitable_senders}} \pnum @@ -6908,26 +6995,67 @@ \pnum \tcode{affine_on} adapts a sender into one that completes on -the specified scheduler. +the receiver's scheduler. If the algorithm determines that the adapted sender already completes on the correct scheduler it can avoid any scheduling operation. \pnum The name \tcode{affine_on} denotes a pipeable sender adaptor object. -For subexpressions \tcode{sch} and \tcode{sndr}, if \tcode{decltype((sch))} -does not satisfy \libconcept{scheduler}, or \tcode{decltype((sndr))} -does not satisfy \libconcept{sender}, \tcode{affine_on(sndr, sch)} +For a subexpression \tcode{sndr}, if \tcode{decltype((\brk{}sndr))} +does not satisfy \libconcept{sender}, \tcode{affine_on(sndr)} is ill-formed. \pnum -Otherwise, the expression \tcode{affine_on(sndr, sch)} is +Otherwise, the expression \tcode{affine_on(sndr)} is expression-equivalent to: \begin{codeblock} -transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(affine_on, sch, sndr)) +transform_sender(@\exposid{get-domain-early}@(sndr), @\exposid{make-sender}@(affine_on, env<>(), sndr)) \end{codeblock} except that \tcode{sndr} is evaluated only once. +\pnum +For a subexpression \tcode{sch} whose type models \libconcept{scheduler}, +let \tcode{\exposid{UNSTOPPABLE-SCHEDULER}(sch)} be an expression \tcode{e} +whose type models \libconcept{scheduler} such that: +\begin{itemize} +\item +\tcode{schedule(e)} is expression-equivalent to \tcode{unstoppable(schedule(sch))}. +\item +For any query object \tcode{q} and pack of subexpressions \tcode{args...}, +\tcode{e.query(q, args...)} is expression-equivalent to +\tcode{sch.query(q, args...)}. +\item +The expression \tcode{e == \exposid{UNSTOPPABLE-SCHEDULER}(other)} +is expression-equivalent to \tcode{sch == other}. +\end{itemize} + +\pnum +Let \tcode{sndr} and \tcode{ev} be subexpressions +such that \tcode{Sndr} is \tcode{decltype((sndr))}. +If \tcode{sender-for} is \tcode{false}, +then the expression \tcode{affine_on.transform_sender(sndr, ev)} is ill-formed; +otherwise, it is equal to: +\begin{codeblock} +auto&[_, _, child] = sndr; +if constexpr (requires{ std::forward_like(child).affine_on(); }) + return std::forward_like(child).affine_on(); +else + return continues_on(std::forward_like(child), + @\exposid{UNSTOPPABLE-SCHEDULER}@(get_start_scheduler(ev))); +\end{codeblock} + +\pnum +\recommended +Implementations should provide \tcode{affine_on} member functions +for senders that are known to resume on the scheduler where they were started. +Example senders for which that is the case are +\tcode{just}, +\tcode{just_error}, +\tcode{just_stopped}, +\tcode{read_env}, and +\tcode{write_env}. + \pnum The exposition-only class template \exposid{impls-for}\iref{exec.snd.expos} is specialized for \tcode{affine_on_t} as follows: @@ -6946,23 +7074,24 @@ \pnum Let \tcode{\placeholder{out_sndr}} be a subexpression denoting a sender -returned from \tcode{affine_on(sndr, sch)} or one equal to such, +returned from \tcode{affine_on(sndr)} or one equal to such, and let \tcode{\placeholder{OutSndr}} be the type \tcode{decltype((\placeholder{out_sndr}))}. Let \tcode{\placeholder{out_rcvr}} be a subexpression denoting a receiver that -has an environment of type \tcode{Env} such that \tcode{\libconcept{sender_in}<\placeholder{OutSndr}, Env>} -is \tcode{true}. +has an environment of type \tcode{Env}. +If \tcode{get_start_scheduler(get_env(\placeholder{out_rcvr}))} is ill-formed or +does not satisfy \tcode{\exposid{infallible-scheduler}}, +then evaluation of +the expression \tcode{get_completion_signatures()} +exits with an exception. Let \tcode{\placeholder{op}} be an lvalue referring to the operation state that results from connecting \tcode{\placeholder{out_sndr}} to \tcode{\placeholder{out_rcvr}}. Calling \tcode{start(\placeholder{op})} will start \tcode{sndr} on the current execution agent and execute completion operations on \tcode{\placeholder{out_rcvr}} on an execution agent of the execution resource associated with -\tcode{sch}. +\tcode{\placeholder{sch}}. If the current execution resource is the same as the execution -resource associated with \tcode{sch}, the completion operation on +resource associated with \tcode{\placeholder{sch}}, the completion operation on \tcode{\placeholder{out_rcvr}} may be called before \tcode{start(\placeholder{op})} completes. -If scheduling onto \tcode{sch} fails, an error completion on -\tcode{\placeholder{out_rcvr}} shall be executed on an unspecified execution -agent. \rSec2[exec.inline.scheduler]{\tcode{execution::inline_scheduler}} @@ -6999,10 +7128,10 @@ let \tcode{rcvr} be an expression such that \tcode{\libconcept{receiver_of}} is \tcode{true} where \tcode{CS} is \tcode{completion_signatures}, -then: +then \begin{itemize} \item the expression \tcode{connect(sndr, rcvr)} has -type \tcode{\exposid{inline-state}>} +type \tcode{\exposid{inline-state}>} and is potentially-throwing if and only if \tcode{((void)sndr, auto(rcvr))} is potentially-throwing, and \item the expression @@ -7074,6 +7203,10 @@ explicit task_scheduler(Sch&& sch, Allocator alloc = {}); \end{itemdecl} \begin{itemdescr} +\pnum +\mandates +\tcode{Sch} satisfies \tcode{\exposid{infallible-scheduler}>}. + \pnum \effects Initialize \exposid{sch_} with @@ -7140,14 +7273,10 @@ \end{codeblock} \exposid{ts-sender} is an exposition-only class that models \libconcept{sender}\iref{exec.snd} and for which -\tcode{completion_signatures_of_t<\exposid{ts-sender}>} denotes: -\begin{codeblock} -completion_signatures< - set_value_t(), - set_error_t(error_code), - set_error_t(exception_ptr), - set_stopped_t()> -\end{codeblock} +\tcode{completion_signatures_of_t<\exposid{ts-sender}>} denotes +\tcode{completion_signatures} +if \tcode{unstoppable_token>} is \tcode{true}, and +otherwise \tcode{completion_signatures}. \pnum Let \tcode{\placeholder{sch}} be an object of type \tcode{task_scheduler} @@ -7229,7 +7358,7 @@ using sender_concept = sender_t; using completion_signatures = @\seebelow@; using allocator_type = @\seebelow@; - using scheduler_type = @\seebelow@; + using start_scheduler_type = @\seebelow@; using stop_source_type = @\seebelow@; using stop_token_type = decltype(declval().get_token()); using error_types = @\seebelow@; @@ -7263,7 +7392,7 @@ \item \tcode{allocator_type} is \tcode{Environment::allocator_type} if that \grammarterm{qualified-id} is valid and denotes a type, \tcode{allocator} otherwise. -\item \tcode{scheduler_type} is \tcode{Environment::scheduler_type} +\item \tcode{start_scheduler_type} is \tcode{Environment::start_scheduler_type} if that \grammarterm{qualified-id} is valid and denotes a type, \tcode{task_scheduler} otherwise. \item \tcode{stop_source_type} is \tcode{Environment::stop_source_type} @@ -7427,8 +7556,8 @@ \item \tcode{\exposid{STATE}(\placeholder{prom})} is \tcode{*this}. \item \tcode{\exposid{RCVR}(\placeholder{prom})} is \exposid{rcvr}. \item \tcode{\exposid{SCHED}(\placeholder{prom})} is the object initialized -with \tcode{scheduler_type(get_scheduler(get_env(\exposid{rcvr})))} -if that expression is valid and \tcode{scheduler_type()} otherwise. +with \tcode{start_scheduler_type(get_start_scheduler(get_env(\exposid{rcvr})))} +if that expression is valid and \tcode{start_scheduler_type()} otherwise. If neither of these expressions is valid, the program is ill-formed. \end{itemize} Let \tcode{\placeholder{st}} be \tcode{get_stop_token(get_env(\exposid{rcvr}))}. @@ -7478,8 +7607,6 @@ template auto await_transform(A&& a); - template - auto await_transform(change_coroutine_scheduler sch); @\unspec@ get_env() const noexcept; @@ -7601,23 +7728,15 @@ \begin{itemdescr} \pnum \returns -If \tcode{\libconcept{same_as}} is \tcode{true} -returns \tcode{as_awaitable(\brk{}std::\brk{}for\-ward(sndr), *this)}; +If \tcode{\libconcept{same_as}} +is \tcode{true}, +returns +\begin{codeblock} +as_awaitable(std::forward(sndr), *this) +\end{codeblock} otherwise returns -\tcode{as_awaitable(affine_on(\brk{}std::\brk{}for\-ward(sndr), \exposid{SCHED}(*this)), *this)}. -\end{itemdescr} - -\indexlibrarymember{await_transform}{task::promise_type}% -\begin{itemdecl} -template - auto await_transform(change_coroutine_scheduler sch) noexcept; -\end{itemdecl} -\begin{itemdescr} -\pnum -\effects -Equivalent to: \begin{codeblock} -return await_transform(just(exchange(@\exposid{SCHED}@(*this), scheduler_type(sch.scheduler))), *this); +as_awaitable(affine_on(std::forward(sndr)), *this) \end{codeblock} \end{itemdescr} @@ -7679,7 +7798,7 @@ \returns An object \tcode{env} such that queries are forwarded as follows: \begin{itemize} -\item \tcode{env.query(get_scheduler)} returns \tcode{scheduler_type(\exposid{SCHED}(*this))}. +\item \tcode{env.query(get_start_scheduler)} returns \tcode{start_scheduler_type(\exposid{SCHED}(*this))}. \item \tcode{env.query(get_allocator)} returns \exposid{alloc}. \item \tcode{env.query(get_stop_token)} returns \exposid{token}. \item For any other query \tcode{q} and arguments \tcode{a...} a @@ -8002,7 +8121,7 @@ }; using @\exposid{sched-sender}@ = // \expos - decltype(schedule(get_scheduler(get_env(declval())))); + decltype(schedule(get_start_scheduler(get_env(declval())))); using @\exposid{op-t}@ = // \expos connect_result_t<@\exposid{sched-sender}@, @\exposid{rcvr-t}@>; @@ -8014,7 +8133,7 @@ noexcept(@\exposconcept{nothrow-callable}@) : @\exposid{scope}@(scope), @\exposid{receiver}@(rcvr), - @\exposid{op}@(connect(schedule(get_scheduler(get_env(rcvr))), @\exposid{rcvr-t}@(rcvr))) {} + @\exposid{op}@(connect(schedule(get_start_scheduler(get_env(rcvr))), @\exposid{rcvr-t}@(rcvr))) {} void @\exposid{complete}@() noexcept { // \expos start(@\exposid{op}@); From 3ff23b575b598b60286bb529dfa081165ac4067a Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Tue, 7 Apr 2026 12:21:25 +0200 Subject: [PATCH 2/2] Fixup: \libconcept markup etc. --- source/exec.tex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/exec.tex b/source/exec.tex index 4a91978f60..f6874f6164 100644 --- a/source/exec.tex +++ b/source/exec.tex @@ -1201,7 +1201,7 @@ @\libconcept{same_as}@, completion_signatures_of_t())), Env>>) ) - ) + ); \end{codeblock} \rSec1[exec.recv]{Receivers} @@ -6876,7 +6876,7 @@ @\exposid{adapt-for-await-completion}@(transform_sender(expr, get_env(p))).as_awaitable(p) \end{codeblock} if this expression is well-formed, -\tcode{sender_in>} is \tcode{true}, and +\tcode{\libconcept{sender_in}>} is \tcode{true}, and \tcode{\exposid{single-sender-\brk{}value-type}>} is well-formed, except that \tcode{p} is only evaluated once. @@ -6887,7 +6887,7 @@ \item Otherwise, \begin{codeblock} -sender-awaitable{adapt-for-await-completion(transform_sender(expr, get_env(p))), p} +@\exposid{sender-awaitable}@{@\exposid{adapt-for-await-completion}@(transform_sender(expr, get_env(p))), p} \end{codeblock} if \tcode{sender_in>} is \tcode{true} and \tcode{\exposid{single-sender-value-type}>} @@ -7033,7 +7033,7 @@ \pnum Let \tcode{sndr} and \tcode{ev} be subexpressions such that \tcode{Sndr} is \tcode{decltype((sndr))}. -If \tcode{sender-for} is \tcode{false}, +If \tcode{\exposconcept{sender-for}} is \tcode{false}, then the expression \tcode{affine_on.transform_sender(sndr, ev)} is ill-formed; otherwise, it is equal to: \begin{codeblock} @@ -7079,7 +7079,7 @@ Let \tcode{\placeholder{out_rcvr}} be a subexpression denoting a receiver that has an environment of type \tcode{Env}. If \tcode{get_start_scheduler(get_env(\placeholder{out_rcvr}))} is ill-formed or -does not satisfy \tcode{\exposid{infallible-scheduler}}, +does not satisfy \tcode{\exposconcept{infallible-scheduler}}, then evaluation of the expression \tcode{get_completion_signatures()} exits with an exception. @@ -7205,7 +7205,7 @@ \begin{itemdescr} \pnum \mandates -\tcode{Sch} satisfies \tcode{\exposid{infallible-scheduler}>}. +\tcode{Sch} satisfies \tcode{\exposconcept{infallible-scheduler}>}. \pnum \effects