cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[dcl.fct.def.coroutine] p8 What thread the resumed coroutine will be executed is underspecified #454

Open xmh0511 opened 11 months ago

xmh0511 commented 11 months ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

Consider this case, the coroutine is suspended in thread 1, and in thread 2, the resume member function of the coroutine is invoked, the document seems to haven't specified what thread the resumed coroutine will be executed on.

#include <iostream>
#include <coroutine>
#include <thread>
#include <atomic>
struct Task{
    struct promise_type;
    using coroutine_type = std::coroutine_handle<promise_type>;
    struct promise_type{
        auto initial_suspend(){
            return std::suspend_never{};
        }
        auto final_suspend() noexcept{
            return std::suspend_always{};
        }
        auto get_return_object(){
            return Task{coroutine_type::from_promise(*this)};
        }
        static void unhandled_exception(){

        }
        auto return_void(){
        }
    };
    bool await_ready(){
        return false;
    }
    void await_suspend(coroutine_type h){
    }
    auto await_resume(){
    }
public:
   ~Task(){
       std::cout<<"destroy Task\n";
       coro_.destroy();
   }
private:
    Task(coroutine_type h):coro_(h){}
    coroutine_type coro_;
};
struct Line{
    bool await_ready(){
        return false;
    }
    void await_suspend(std::coroutine_handle<> h){
      auto t1 = std::thread([this,h]() mutable{
          //std::this_thread::sleep_for(std::chrono::seconds(2));
          std::cout<<"invoke resume member on "<< std::this_thread::get_id()<<"\n";
          h.resume();
          std::cout<<"back \n";
      });
      t1.detach();
    }
    auto await_resume(){
       std::cout<<"task() resumed to run on "<< std::this_thread::get_id()<<"\n";
    }
};
Task task(){
   std::cout<<"#1 task() run on "<< std::this_thread::get_id()<<"\n";
   co_await Line();
   std::cout<<"#2 task() run on "<< std::this_thread::get_id()<<"\n";
}
int main(){
    auto r = task();
    std::cout<<"main\n";
    std::cin.get();
}

In major implementations, the output is:

#1 task() run on 140659102840640
main
invoke resume member on 140659102836480
task() resumed to run on 140659102836480
#2 task() run on 140659102836480
back 
destroy Task

It can be seen that the resumed coroutine is executed on the thread on which the coroutine is resumed. However, [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.

We say this case is not implementation-defined because the "unless", however, we also didn't specify what the behavior is.