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.

4542. Resuming a coroutine on a different execution agent that is an instance of a std::thread is underspecified

Section: 17.13.4.6 [coroutine.handle.resumption] Status: New Submitter: jim x Opened: 2026-03-11 Last modified: 2026-03-15

Priority: Not Prioritized

View all issues with New status.

Discussion:

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();
}

17.13.4.6 [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.

Proposed resolution:

This wording is relative to N5032.

  1. Modify 17.13.4.6 [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.

    […]