Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it wrong to expect resumable functions actually resume in the original thread?

Tags:

c++

After watching the interview with Gor Nishanov on await and coroutines, I decided to toy a bit with resumable functions N4131. I then realized that the following code printed 'false' to my second question (tested with Visual Studio 2015 RC and the online compiler (requires flag: /await)):

#include <future>
#include <iostream>

using namespace std;
using namespace chrono;

struct Foo
{
  future<void> coro(thread::id ui_thread_id)
  {
    cout << "Q: awaiting result in UI thread? " << boolalpha << (this_thread::get_id() == ui_thread_id) << endl;
    auto intermediate_result = __await async([] {
      this_thread::sleep_for(250ms);
      return 42;
    });
    cout << "Q: received result in UI thread? " << boolalpha << (this_thread::get_id() == ui_thread_id) << endl;
    result = intermediate_result; // race
  }

  int result = 0;
};

int main()
{
  const auto ui_thread_id = this_thread::get_id();
  Foo f;
  auto c = f.coro(ui_thread_id);
  for (int i = 0; i < 7; ++i)
  {
    cout << "  -- event loop: " << f.result << endl;
    this_thread::sleep_for(50ms);
  }
  c.wait();
} 

My expectation resulted from the statement that "the fact that a function is implemented as resumable function is unobservable by the caller" (N4131, p.8, Resumable Function, 4th paragraph).

This seems to be an important detail and I could not find any statement in N4131 which clarifies my question.

Is it wrong to expect resumable functions actually resume their work in the original thread? If it is wrong, please try to explain why?

like image 510
Hauke Heibel Avatar asked Nov 01 '22 00:11

Hauke Heibel


1 Answers

To answer your question: yes, it is probably wrong to expect resumable functions actually resume their work in the original thread because the proposal is underspecified with regards to that behavior (maybe on purpose, maybe not).

I think your misconception is around which function is resumable in the example, and thus which function is bound by "the fact that a function is implemented as resumable function is unobservable by the caller".

From the proposal (emphasis mine):

A function or a lambda is called resumable function or resumable lambda if a body of the function or lambda contains at least one suspend/resume point. Suspend/resume points are expressions with one or more await operators, yield statements or await-for statements

This means that the lambda isn't the resumable function, coro is because the await keyword is part of its body. The proposal specifies how the resumable function behaves with regards to its callers and the example shows that it is indeed unobservable by the caller, main() in this case, whether it was implemented as resumable or not (main() is also still on the UI thread when coro returns).

Now, one could argue that this might be an oversight in the proposal to not specify this but I'm sure there was a good reason for it, yet I wasn't able to find anything in the proposal relating to the actual resuming of the function.

There could also be debate around the interaction with modifiable parameters. For example:

future<void> coro(thread::id ui_thread_id, int* foo)
{
  auto intermediate_result = __await async([] {
    this_thread::sleep_for(250ms);
  });

  *foo = 42;
}

I would personally consider the fact that foo can be modified by another thread as "observable" from the caller, but then again someone could likely argue otherwise (I'm not the greatest at standard-ese).

Last but not least, I'm not sure what you consider extraordinarily race-y about the assignment to result. It does race with the reads in main() but you wrote the code specifically for this and there's only one assignment (the body of the function is suspended at the await call and resumed once the async lambda returns). Changing await to resume on the same thread would not affect this situation at all (except in the case of non-atomic read-writes where you could read an object in an inconsistent state, which is another can of worms and is probably a valid point).

Note that this feature is very young and currently only experimentally implemented in MSVC IIRC so I would expect it to go through some changes before it's standardized (if it ever is). There's also a non-negligible chance that this is actually specification/implementation bug.

That being said, there's a good chance that the behavior on resuming resumable functions was barely specified to allow more freedom in the implementation.

like image 145
anthonyvd Avatar answered Nov 15 '22 03:11

anthonyvd