Title
`run_loop` should not have a `set_error` completion
Status
wp
Section
[exec.run.loop.general]
Submitter
Eric Niebler

Created on 2025-11-16.00:00:00 last changed 2 weeks ago

Messages

Date: 2026-03-31.16:35:58

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.]

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

    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() 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
    
        // [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:
        // [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() noexcept;
        void run() noexcept;
        void finish() noexcept;
      };
    }
    
  2. Modify [exec.run.loop.types] as indicated:

    -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 [exec.run.loop.members] as indicated:

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

    -1- Effects: Blocks ([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`.

Date: 2026-03-31.16:35:58

[ Croydon 2026-03-28; Status changed: Immediate → WP. ]

Date: 2026-03-27.16:09:46

[ Croydon 2026-03-27; Status changed: New → Immediate. ]

Date: 2026-03-15.00:00:00

[ 2026-03-27; Jonathan provides new wording ]

Change to [exec.run.loop.types]/5 superseded by P3941.

Date: 2026-03-27.16:09:16

[ Croydon 2026-03-27; LEWG confirmed they want `noexcept`. Status changes: LEWG → Open. ]

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.]

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

    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() 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
          
        // [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:
        // [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() noexcept;
        void run() noexcept;
        void finish() noexcept;
      };
    }
    
  2. Modify [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 [exec.run.loop.members] as indicated:

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

    -1- Effects: Blocks ([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`.

Date: 2026-03-15.00:00:00

[ 2026-03-26; Status New → LEWG. ]

Date: 2026-01-15.00:00:00

[ 2026-01-16; Reflector poll. ]

Set priority to 2 after reflector poll.

"Very helpful to know if something completes without exceptions, it affects whether completions on some types of scheduler are correct."

"Want confirmation from LEWG as `run()` and `finish()` have narrow contracts."

Date: 2025-11-16.00:00:00

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.

History
Date User Action Args
2026-03-31 16:35:58adminsetmessages: + msg16203
2026-03-31 16:35:58adminsetstatus: immediate -> wp
2026-03-27 16:09:46adminsetmessages: + msg16137
2026-03-27 16:09:46adminsetstatus: open -> immediate
2026-03-27 16:09:16adminsetmessages: + msg16136
2026-03-27 16:05:19adminsetmessages: + msg16135
2026-03-27 16:05:19adminsetstatus: lewg -> open
2026-03-26 14:14:30adminsetmessages: + msg16097
2026-03-26 14:14:30adminsetstatus: new -> lewg
2026-01-16 15:25:05adminsetmessages: + msg15857
2025-11-16 16:00:37adminsetmessages: + msg15750
2025-11-16 00:00:00admincreate