33 Execution control library [exec]

33.11 Execution contexts [exec.ctx]

33.11.1 execution​::​run_loop [exec.run.loop]

33.11.1.1 General [exec.run.loop.general]

A run_loop is an execution resource on which work can be scheduled.
It maintains a thread-safe first-in-first-out queue of work.
Its run member function removes elements from the queue and executes them in a loop on the thread of execution that calls run.
A run_loop instance has an associated count that corresponds to the number of work items that are in its queue.
Additionally, a run_loop instance has an associated state that can be one of starting, running, or finishing.
Concurrent invocations of the member functions of run_loop other than run and its destructor do not introduce data races.
The member functions pop-front, push-back, and finish execute atomically.
Recommended practice: Implementations should use an intrusive queue of operation states to hold the work units to make scheduling allocation-free.
namespace std::execution { class run_loop { // [exec.run.loop.types], associated types class run-loop-scheduler; // exposition only class run-loop-sender; // exposition only struct run-loop-opstate-base { // exposition only virtual void execute() = 0; // exposition only run_loop* loop; // exposition only run-loop-opstate-base* next; // exposition only }; template<class Rcvr> using run-loop-opstate = unspecified; // exposition only // [exec.run.loop.members], member functions run-loop-opstate-base* pop-front(); // exposition only void push-back(run-loop-opstate-base*); // exposition only public: // [exec.run.loop.ctor], constructor and destructor run_loop() noexcept; run_loop(run_loop&&) = delete; ~run_loop(); // [exec.run.loop.members], member functions run-loop-scheduler get_scheduler(); void run(); void finish(); }; }

33.11.1.2 Associated types [exec.run.loop.types]

class run-loop-scheduler;
run-loop-scheduler is an unspecified type that models scheduler.
Instances of run-loop-scheduler remain valid until the end of the lifetime of the run_loop instance from which they were obtained.
Two instances of run-loop-scheduler compare equal if and only if they were obtained from the same run_loop instance.
Let sch be an expression of type run-loop-scheduler.
The expression schedule(sch) has type run-loop-
sender
and is not potentially-throwing if sch is not potentially-throwing.
class run-loop-sender;
run-loop-sender is an exposition-only type that satisfies sender.
For any type Env, completion_signatures_of_t<run-loop-sender, Env> is completion_signatures<set_value_t(), set_error_t(exception_ptr), set_stopped_t()>
An instance of run-loop-sender remains valid until the end of the lifetime of its associated run_loop instance.
Let sndr be an expression of type run-loop-sender, let rcvr be an expression such that receiver_of<decltype((rcvr)), CS> is true where CS is the completion_signatures specialization above.
Let C be either set_value_t or set_stopped_t.
Then:
  • The expression connect(sndr, rcvr) has type run-loop-opstate<decay_t<decltype((rcvr))>> and is potentially-throwing if and only if (void(sndr), auto(rcvr)) is potentially-throwing.
  • The expression get_completion_scheduler<C>(get_env(sndr)) is potentially-throwing if and only if sndr is potentially-throwing, has type run-loop-scheduler, and compares equal to the run-loop-
    scheduler
    instance from which sndr was obtained.
template<class Rcvr> struct run-loop-opstate;
run-loop-opstate<Rcvr> inherits privately and unambiguously from run-loop-opstate-base.
Let o be a non-const lvalue of type run-loop-opstate<Rcvr>, and let REC(o) be a non-const lvalue reference to an instance of type Rcvr that was initialized with the expression rcvr passed to the invocation of connect that returned o.
Then:
  • The object to which REC(o) refers remains valid for the lifetime of the object to which o refers.
  • The type run-loop-opstate<Rcvr> overrides run-loop-opstate-base​::​execute() such that o.execute() is equivalent to: if (get_stop_token(REC(o)).stop_requested()) { set_stopped(std::move(REC(o))); } else { set_value(std::move(REC(o))); }
  • The expression start(o) is equivalent to: try { o.loop->push-back(addressof(o)); } catch(...) { set_error(std::move(REC(o)), current_exception()); }

33.11.1.3 Constructor and destructor [exec.run.loop.ctor]

run_loop() noexcept;
Postconditions: count is 0 and state is starting.
~run_loop();
Effects: If count is not 0 or if state is running, invokes terminate ([except.terminate]).
Otherwise, has no effects.

33.11.1.4 Member functions [exec.run.loop.members]

run-loop-opstate-base* pop-front();
Effects: Blocks ([defns.block]) until one of the following conditions is true:
  • count is 0 and state is finishing, in which case pop-front returns nullptr; or
  • count is greater than 0, in which case an item is removed from the front of the queue, count is decremented by 1, and the removed item is returned.
void push-back(run-loop-opstate-base* item);
Effects: Adds item to the back of the queue and increments count by 1.
Synchronization: This operation synchronizes with the pop-front operation that obtains item.
run-loop-scheduler get_scheduler();
Returns: An instance of run-loop-scheduler that can be used to schedule work onto this run_loop instance.
void run();
Preconditions: state is starting.
Effects: Sets the state to running.
Then, equivalent to: while (auto* op = pop-front()) { op->execute(); }
Remarks: When state changes, it does so without introducing data races.
void finish();
Effects: Changes state to finishing.
Synchronization: finish synchronizes with the pop-front operation that returns nullptr.