Title
Resuming a coroutine on a different execution agent that is an instance of a `std::thread` is underspecified
Status
new
Section
[coroutine.handle.resumption]
Submitter
jim x

Created on 2026-03-11.00:00:00 last changed 2 weeks ago

Messages

Date: 2026-03-15.10:26:29

Proposed resolution:

This wording is relative to N5032.

  1. Modify [coroutine.handle.resumption] as indicated:

    -1- Resuming a coroutine via `resume`, `operator()`, or `destroy` on an execution agent other than the one on which it was suspended has implementation-defined behavior unless each execution agent either is an instance of `std::thread` or `std::jthread`, or is the thread that executes `main`. Otherwise, the execution of the coroutine is resumed, or the coroutine is destroyed, on the execution agent on which the corresponding member function is invoked.

    […]

Date: 2026-03-11.00:00:00

Consider the following example:

#include <atomic>
#include <cassert>
#include <coroutine>
#include <thread>

std::atomic<bool> flag = false;
int global = 0;

struct Task 
{
  struct promise_type 
  {
    Task get_return_object() {
      return {std::coroutine_handle<promise_type>::from_promise(*this)};
    }
    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }
    void return_void() {}
    void unhandled_exception() {}
  };

  std::coroutine_handle<promise_type> handle;

  Task(std::coroutine_handle<promise_type> h) : handle(h) {}
  ~Task() {}
};

struct SuspendOnce 
{
  bool suspended = false;
  bool await_ready() { return suspended; }
  void await_suspend(std::coroutine_handle<>) { suspended = true; }
  void await_resume() {}
};

Task my_coroutine() {
  co_await SuspendOnce{};
  flag.store(true, std::memory_order::release); // #2
}

auto t1 = std::thread([]() {
  while (!flag.load(std::memory_order::acquire)); //#3
  assert(global == 1); // #4
});

int main() {
  auto task = my_coroutine();
  auto& h = task.handle;
  auto t2 = std::thread([&]() {
    global = 1; // #1
    h.resume();
  });
  t2.join();
  t1.join();
}

[coroutine.handle.resumption] p1 says:

Resuming a coroutine via `resume`, `operator()`, or `destroy` on an execution agent other than the one on which it was suspended has implementation-defined behavior unless each execution agent either is an instance of `std::thread` or `std::jthread`, or is the thread that executes `main`.

The wording doesn't specify the behavior for the latter case. We want `#1` to be sequenced before `#2`.

The sequenced-before is described in terms of two evaluations that are both executed by a single thread. The coroutine is initially executed in the main thread; however, it's resumed in a different thread. The first paragraph doesn't specify the specific behavior; it only says the behavior is implementation-defined if the different execution agent is not an instance of `std::thread` or `std::jthread`, or is the thread that executes `main`.

History
Date User Action Args
2026-03-15 10:26:29adminsetmessages: + msg16027
2026-03-11 00:00:00admincreate