This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4476. run_loop should not have a set_error completion

Section: 33.12.1.1 [exec.run.loop.general] Status: New Submitter: Eric Niebler Opened: 2025-11-16 Last modified: 2025-11-16

Priority: Not Prioritized

View all issues with New status.

Discussion:

When run_loop was proposed, the only implementation we had synchronized with a mutex and a condition variable. Operations on those can theoretically throw exceptions, so run_loop got a set_error_t(exception_ptr) completion signature.

Since then, a lock-free implementation of run_loop has been found. Atomic operations cannot fail with an exception, so an atomic run_loop can never complete with an error.

Proposed resolution:

This wording is relative to N5014.

[Drafting note: It should be pointed out that member function finish should be noexcept for other reasons as well, see LWG 4215(i).]

  1. Modify 33.12.1.1 [exec.run.loop.general], class run_loop synopsis, as indicated:

    namespace std::execution {
      class run_loop {
        // 33.12.1.2 [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() noexcept = 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
          
        // 33.12.1.4 [exec.run.loop.members], member functions
        run-loop-opstate-base* pop-front() noexcept;     // exposition only
        void push-back(run-loop-opstate-base*) noexcept; // exposition only
        
      public:
        // 33.12.1.3 [exec.run.loop.ctor], constructor and destructor
        run_loop() noexcept;
        run_loop(run_loop&&) = delete;
        ~run_loop();
        
        // 33.12.1.4 [exec.run.loop.members] member functions
        run-loop-scheduler get_scheduler() noexcept;
        void run() noexcept;
        void finish() noexcept;
      };
    }
    
  2. Modify 33.12.1.2 [exec.run.loop.types] as indicated:

    class run-loop-sender;
    

    -5- run-loop-sender is an exposition-only type that satisfies sender. completion_signatures_of_t<runloop-sender> is

    completion_signatures<set_value_t(), set_error_t(exception_ptr), set_stopped_t()>
    

    […]

    -9- 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:

    • (9.1) — […]
    • (9.2) — […]
    • (9.3) — The expression start(o) is equivalent to:
      try {
        o.loop->push-back(addressof(o));
      } catch(...) {
        set_error(std::move(REC(o)), current_exception());
      }
      
  3. Modify 33.12.1.4 [exec.run.loop.members] as indicated:

    run-loop-opstate-base* pop-front() noexcept;
    

    -1- Effects: Blocks (3.6 [defns.block]) until one of the following conditions is true: […]

    void push-back(run-loop-opstate-base* item) noexcept;
    

    -2- Effects: Adds item to the back of the queue and increments count by 1.

    -3- Synchronization: This operation synchronizes with the pop-front operation that obtains item.

    run-loop-scheduler get_scheduler() noexcept;
    

    -4- Returns: An instance of run-loop-scheduler that can be used to schedule work onto this run_loop instance.

    void run() noexcept;
    

    -5- Preconditions: is either starting or finishing.

    -6- Effects: If state is starting, sets the state to running, otherwise leaves state unchanged. Then, equivalent to:

    while (auto* op = pop-front()) {
      op->execute();
    }
    

    -7- Remarks: When state changes, it does so without introducing data races.

    void finish() noexcept;
    

    -8- Preconditions: state is either starting or running.

    -9- Effects: Changes state to finishing.

    -10- Synchronization: finish synchronizes with the pop-front operation that returns nullptr.