Discussion:
Constructive comments on P0054R0: Coroutines
(too old to reply)
TONGARI J
2015-10-07 14:34:28 UTC
Permalink
Hi Gor,

I have some comments on the current design:

RAII

coroutine_handle is just like raw-pointer, it doesn't manages the resource
and it's even more dangerous and error-prone than manual memory management.


coroutine_handle should embrace RAII semantic, more precisely, the
unique-semantic. There should be at most one instance executing the same
coroutine at the same time.


When coroutine_handle is destructed, it should force the stack to be
unwound.


I'd suggest renaming coroutine_handle to coroutine. std::coroutine<> should
define a promise_type, so it can be used as the *Coroutine Return Type*:

std::coroutine<> coro(int i, int e)
{
for (; i != e; ++i)
{
await std::suspend_always{};
std::cout << i;
}
}
...// usageauto c = coro(1, 10);while (c)
{
c();
std::cout << ", ";
}

There's no need for a done() member function, when the coroutine is done,
it'll reset itself so operator bool will report false.


std::coroutine<Promise> has a static void destroy(Promise*) function, which
destroys the coroutine-frame from the Promise pointer. Unlike the original
proposal, in which destroy is a non-static member function and will unwind
the stack, the new destroy is a static function and won't unwind the stack.


The Promise::get_return_object is also changed to accepts a param, i.e. get_return_object(coroutine<Promise>&
coro). The user can now decide when and how to run the coroutine on the
first time.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#case-study>Case Study

Consider the code below:

std::future<void> f()
{
Resource res(...);
await std::suspend_always{};
}
...// usagef().get();

Without RAII, you'll get memory & other resource leaks; with RAII, you'll
get broken_promise exception without any resource leak.


As an additional benefit, a coroutine can now safely cancel itself (i.e.
just suspend it), it also makes cascading cancellation possible without
exceptions.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#implementation>
Implementation

The idea is already implemented in CO2 <https://github.com/jamboree/co2>,
an emulation library based on earlier proposal.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#additional-optional-customization-points-for-promise>Additional
(optional) customization points for Promise

-

void cancel() This allows you specify the behavior of the coroutine when
it is cancelled (i.e. when cancellation_requested() returns true or
coroutine is reset).
-

bool resume() This is called before the coroutine is resumed, if it
returns false, the coroutine won't be resumed, instead, the coroutine
will be detached.
-

void suspend() This is called before the coroutine is gonna be
suspended, however, it won't be called for final_suspend.

Although theoretical there should be at most one instance executing the
same coroutine at the same time, but sometimes you need to resume more than
one concurrently, for example, to support "active-cancellation". The
resume() & suspend() customization points provide such possibility.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#bikeshedding>
Bikeshedding

- on_cancel
- on_resume
- on_suspend

<https://gist.github.com/jamboree/611b8c6551f853956f7d#implementation-1>
Implementation

Also implemented in CO2.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#restoration-of-cancellation_requested>Restoration
of cancellation_requested

I think cancellation_requested is still needed to implement
passive-cancellation.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#inspect-promisevalue_typereturn_type-check-on-return>Inspect
Promise::value_type(return_type), check on return

The return_type information could help the compiler to provide more robust
return behavior. It allows the compiler to check type compatibility before
calling Promise::return_value/void.

- return reference to local warning

// return type is a reference
std::future<int&> f()
{
int a = 9;
await std::experimental::suspend_never{};
return a; // warning wanted
}


- return local variable with the same type as return_type:

// return type and object have the same type
std::future<MoveOnly> f()
{
MoveOnly var;
await std::experimental::suspend_never{};
return var; // `var` should be treated as rvalue-ref
}

The Promise::return_value will get the param as rvalue-ref instead of
lvalue-ref.

- help in case that the Promise::return_value is generic that can't
deduce the type from braced-init:

std::future<Aggregate> f()
{
await std::experimental::suspend_never{};
return {1, 2, 3};
}


<https://gist.github.com/jamboree/611b8c6551f853956f7d#bikeshedding-1>
Bikeshedding

value_type/return_type/result_type


<https://gist.github.com/jamboree/611b8c6551f853956f7d#when-does-stack-unwinding-happen>When
does stack-unwinding happen?

It'd better happen before Promise::return_value/void. Consider a normal
function call:

int f()
{
LovalVar local;
doSomething();
return 0;
}
...// on useint i = f();// `local` should be destructed before we get the return value

We'd expect the stack-frame of f get unwound before it returns, the same
expectation applies to resumable functions as well:

Task<int> f()
{
LovalVar local;
await doSomething();
return 0;
}
...// on useint i = await f();

The implementation of Task may invoke its continuation on return_value/void
or final_suspend, depends on the author's choice. To get the consistent
behavior, stack-unwinding should happen before return_value/void.


I checked the current MSVC implementation, it's done before final_suspend,
and sometimes before return_value/void, which is inconsistent.

See the example <https://gist.github.com/jamboree/9bbe2435677295475f36>.

The result I got is:

[ret_value]
return_value()
~A()
final_suspend()
------
[ret_void]
return_void()
~A()
final_suspend()
------
[ret_void_implicit]
~A()
return_void()
final_suspend()
------

where A is a local variable of the resumable function.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#customization-points>Customization
points

These customization points should be made public (user-accessible):

- await_ready
- await_suspend
- await_resume

What I mean "public" is that the user can call the free functions, like
below:

using std::await_ready;if (await_ready(someAwaitable))
{
...
}

Better yet, the customization points could follow what Eric Niebler has
suggested: https://ericniebler.github.io/std/wg21/D4381.html


Also, operator await needs to be public as well, but I'd suggest renaming
it to get_awaiter, which is easier to write for the user, and also allows
the possibility of Eric's proposal.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#case-study-1>Case
Study

Suppose you want to make a wrapper for any *Awaiatable* that allows you to
wait without extracting the result, it could be implemented this way:

template<class Task>struct ready_awaiter
{
Task task;

bool await_ready()
{
return std::await_ready(task);
}

template<class F>
auto await_suspend(F&& f)
{
return std::await_suspend(task, std::forward<F>(f));
}

void await_resume() noexcept {}
};
template<class Task>inline ready_awaiter<Task> ready_impl(Task&& task)
{
return {std::forward<Task>(task)};
}
template<class Task>inline auto ready(Task&& task)
{
return ready_impl(std::get_awaiter(std::forward<Task>(task)));
}
...// usage
await ready(someAwaitable);

<https://gist.github.com/jamboree/611b8c6551f853956f7d#initialfinal_suspend---bool-or-awaiatable>
initial/final_suspend - bool or Awaiatable?

I don't see why initial/final_suspend needs to return *Awaiatable*.


If initial/final_suspend returns bool, it's just like returning an
*Awaiatable* that onlys defines bool await_suspend.


In the example of final_suspend you shown, it's exactly the same as the bool version.
What am I missing?


On the other hand, yield_value does need to return *Awaiatable*, I also
reported it here
<https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/zEMU_F-QcBo>
.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#syntax-to-specify-an-allocator>Syntax
to specify an allocator

A new clause can be used, for example:

template<class Allocator>
std::future<void> f(Allocator alloc) new(alloc)
{
await ...
}

The is irrelevant to how coroutine_traits *default* allocates, if the user
specifies new(alloc), the alloc will override the default allocation
strategy defined by coroutine_traits.


<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-vs-resumableasync>auto
await v.s. resumable(async)

P0054R0 draws the idea "Automatically Awaited Awaitables", however, I think
we'd better not to interfere with the type system. The original sample:

auto MyProxy::operator=(int output)
{
struct Awaitable auto await { ... };
return Awaitable{...};
}

could be written this way:

await auto MyProxy::operator=(int output)
{
struct Awaitable { ... };
return Awaitable{...};
}

Note that the await prefix is not part of the type system here, it just
instructs the compiler to await on what it returns.


That said, the "auto await" idea is not flexible enough, consider the
special functions, e.g. constructors & destructors, which could also be
resumable in theory, there's no way for them to use "auto await".


A better solution is to consider something like the approach taken by
Resumable Expressions (P0114R0).


I had a draft describing the joint idea:
https://gist.github.com/jamboree/a2b3fe32eeb8c21e820c


The same example written in the draft will look like:

async auto MyProxy::operator=(int output)
{
struct Awaitable { ... };
return await Awaitable{...};
}

The most interesting part of the draft is "Context-based overloading",
which I haven't seen in other proposals, I'd like to get some feedback from
you.


Thanks.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-07 16:04:16 UTC
Permalink
Hi Tongari:

Thank you for constructive comments. I love those :-) .
I need some time to think about the ideas you expressed before I can
comment on them properly.

A few quick things I can answer immediately.

RAII: Consider cases of non-owning use of coroutine_handle, such as
illustrated in http://wg21.link/P0055. Since coroutine_handle is a compiler
magic type, we need to be careful of not adding too much semantics to it
and leave considerations like RAII to the library. You can trivially write
safe_coroutine_handle that has the properties you desire.

resume/suspend - see the await_transform in http://wg21.link/P0054. Would
it allow you to solve the use case that you were thinking about.

initial_suspend/final_suspend - First section of http://wg21.link/P0054 goes
into details explaining why bool won't work.

More later.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 01:07:01 UTC
Permalink
Continued:

1. Customization points:

Coroutines proposal have not been looked by LEWG, so I expect that part to
be modified as needed according to the LEWG preferences. If they like
Eric's approach, that's what it will be. I have no objections.

2. return moveOnly:

I agree, the behavior should much ordinary function. I will tweak the
wording to fit it in.

3. unwind

One of the idea that was suggested recently was to make return_value return
awaitable. That way, the consumer can directly access the value from the
expression without copy/move into the promise. In that case, there is
definitely no unwind at the point of return <expr>. I think the efficiency
gains outweigh the consideration you mentioned.

4. syntax for allocator

I don't mind. There were other suggestions like adding
using(traits/promise) at the same position, so that you not only can
control the allocator, but, the entire machinery. I think those a valuable
ideas to explore in the future. At the moment, I am looking for things to
cut :-) . I would have cut yield, but, it only saves a paragraph or two in
the wording and implementation is just await $p.yield_value(expr).

5. automatically awaited awaitables and your proposal on the github

I have concerns about magically injecting suspend points and turning
functions into coroutines and vice versa. I think there are ways to evolve
the coroutines in that direction in the future, but, I am not sure it is a
good idea.

As I said. At the moment I am trying to keep proposal to the minimally
useful size. C++17 is very close. Let's get something efficient and useful
and grow it in the future as needed.

Cheers,
Gor
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 01:10:24 UTC
Permalink
much => match
I agree, the behavior should *match *much ordinary function.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-10 08:08:54 UTC
Permalink
Hi Gor,
<https://gist.github.com/jamboree/611b8c6551f853956f7d#raii>RAII

I've read P0055 as well, but I don't see what non-owning cases you
mentioned, could you elaborate?

Note that my suggestion is not groundless - I have a small wrapper library
act <https://github.com/jamboree/act> for ASIO, which is the base of the
Network proposal, and it works well with my CO2 library, which exposed
unique-semantic for coroutine. It's implementable with my
preprocessor-based emulation, I don't believe the same cannot be
implemented in the language itself.

Surely you can have something like safe_coroutine_handle, but you're
missing the important feature to interact with the coroutine.

Without RAII, the await_suspend looks like:

bool await_suspend(coroutine_handle<Promise> coro); // by value

With unique-semantic, it looks like:

bool await_suspend(coroutine<Promise>& coro); // by reference

The coro is taken by reference, so it can *transfer* the ownership of the
coroutine. If the coroutine is not transferred but is suspended, the caller
still owns the coroutine and can safely unwind the stack, it's impossible
without RAII at the first place.
<https://gist.github.com/jamboree/611b8c6551f853956f7d#resumesuspend>
resume/suspend

I think their purpose is different from await_transform, they're desgined
for RAII to complement unique-semantic in some cases.
<https://gist.github.com/jamboree/611b8c6551f853956f7d#initial_suspendfinal_suspend>
initial_suspend/final_suspend

I can't understand the rationale, I think the example of final_suspend you
shown is exactly the same as the bool version, what's the difference?
<https://gist.github.com/jamboree/611b8c6551f853956f7d#customization-points>Customization
points

Even if the LEWG doesn't consider Eric's approach, you should still make
those customization points available as free functions like std::begin,
std::end, etc.
<https://gist.github.com/jamboree/611b8c6551f853956f7d#unwind>unwind

I don't understand the benefit of making it return Awaitable, any example?
Anyway, I think you can still unwind the stack before return_value as long
as you store the value somewhere before unwinding the stack.
<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>auto
await alternatives

Another benefit of resumable/async (whatever the mark is called) approach
is that you don't need to have await and yield as keywords, instead, they
could be free functions std::await andstd::yield. In the standard, you can
define 2 pseudo keywords __await and __yield and their semantic, the
implementation doesn't need to provide them, but the std::await and
std::yield can be defined in terms of __await and __yield respectively:

template<Awaitable T>
async decltype(auto) await(T&& t)
{
return __await forward<T>(t);
}
template<class T>
async decltype(auto) yield(T&& t)
{
return __yield forward<T>(t);
}


It'd be nice if we could have coroutine in C++17, but it'll be much harder
to fix it afterwards considering the backward-compatibility. We'd better
make the consideration thorough and not in a rush.

Thanks.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-10 13:51:50 UTC
Permalink
Post by TONGARI J
Hi Gor,
<https://gist.github.com/jamboree/611b8c6551f853956f7d#raii>RAII
I've read P0055 as well, but I don't see what non-owning cases you
mentioned, could you elaborate?
Note that my suggestion is not groundless - I have a small wrapper library
act <https://github.com/jamboree/act> for ASIO, which is the base of the
Network proposal, and it works well with my CO2 library, which exposed
unique-semantic for coroutine. It's implementable with my
preprocessor-based emulation, I don't believe the same cannot be
implemented in the language itself.
Surely you can have something like safe_coroutine_handle, but you're
missing the important feature to interact with the coroutine.
bool await_suspend(coroutine_handle<Promise> coro); // by value
bool await_suspend(coroutine<Promise>& coro); // by reference
The coro is taken by reference, so it can *transfer* the ownership of the
coroutine. If the coroutine is not transferred but is suspended, the caller
still owns the coroutine and can safely unwind the stack, it's impossible
without RAII at the first place.
This is a bit confusing, so I want to explain how I understand things.

`await_suspend` is called by `await`. It is called on `e`, which is the
evaluation of `operator await` on `await`'s expression. So the function
call is `e.await_suspend`. It is given a coroutine handle that is the
coroutine for *the current function*, not the coroutine that was just
called. So `await_suspend` tells the promise object (converted by `operator
await`) to suspend the current function.

Given all of that, if you were to transfer ownership of the current
function's coroutine handle to the promise object, that would mean:

1) The current function actually *has ownership of itself* (you can't
transfer ownership if you don't own something).

2) The coroutine promise from the `await`ing expression will own the
current function now.

#1 does not make sense. The owner of a coroutine is the *caller*, not the
function itself (things can't own themselves uniquely). The caller has the
right to destroy the coroutine when it is suspended, and there is no reason
to deny the caller that right (for example, if the caller throws an
exception).

And I'm just not sure of the point of #2. Why do you want to give the
coroutine promise that was just called ownership of the function that
called it?

Now P0057 is a complex proposal, so I may be misunderstanding what you and
they are talking about. But from my limited comprehension of it, it seems
like you're performing an action that doesn't make sense.

Then again, I'm not sure why the `operator await` class from the promise
being waited on is being told to suspend the function that called it. But
to the extent that this operation makes sense, that operation clearly
should not transfer ownership.

Though I did find something highly disconcerting. I see a lot of code
taking `coroutine_handle<>` by value. Since they take the parameter by
value, that strongly suggests *slicing* (copying of the base class). Maybe
that's OK, but I think it would be clearer if they took them by reference
or something.
Post by TONGARI J
resume/suspend
I think their purpose is different from await_transform, they're desgined
for RAII to complement unique-semantic in some cases.
<https://gist.github.com/jamboree/611b8c6551f853956f7d#initial_suspendfinal_suspend>
initial_suspend/final_suspend
I can't understand the rationale, I think the example of final_suspend you
shown is exactly the same as the bool version, what's the difference?
P0054 explained exactly why the bool version doesn't work, with specific
cases. It is complex, but the general gist is that there are race
conditions with threaded coroutine scheduling that make a `bool`-only
version non-functional.
Post by TONGARI J
<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>auto
await alternatives
Another benefit of resumable/async (whatever the mark is called) approach
is that you don't need to have await and yield as keywords, instead, they
could be free functions std::await andstd::yield. In the standard, you
can define 2 pseudo keywords __await and __yield and their semantic, the
implementation doesn't need to provide them, but the std::await and
What are "pseudo keywords"? More importantly, you can't make keywords (or
any such semantic) with double-underscores. That is explicitly reserved for
*implementation* use, not standard use.

Also, we shouldn't be judging a proposal to be better solely because it
uses fewer keywords. We've got too many proposals in the pipe that need
genuine keywords to avoid adopting P0056 (soft keywords). At which point,
what is and isn't a keyword essentially becomes irrelevant.

If the committee rejects P0056, then an approach like the one you suggested
could be adopted. But that really ought to be something that gets dealt
with once we're sure we have the functionality behaving correctly.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 14:43:53 UTC
Permalink
Post by Nicol Bolas
Though I did find something highly disconcerting. I see a lot of code
taking `coroutine_handle<>` by value. Since they take the parameter by
value, that strongly suggests *slicing* (copying of the base class).
Maybe that's OK, but I think it would be clearer if they took them by
reference or something.
By design coroutine_handle<> and coroutine<P> are pointer sized values.
coroutine_handle<> is a base class for coroutine_handle<P>. upcasting and
downcasting from one another does not change the bit pattern. It is still
the same pointer to the coroutine. coroutine_handle<P>, knows how given a
coroutine pointer to get access to the promise of the coroutine.
coroutine_handle<> only knows how to get to the coroutine.

Since it is a pointer sized value, it is cheaper to pass it by value and
not by reference.

I understand the perception you have and open to suggestions on how to
improve it. One thing to note that: P0057 has layered complexity (stealing
from N4287).

Everybody
• Safe by default, novice friendly: Use coroutines and awaitables defined
by standard library and boost and other high quality libraries
• Power Users: Define new awaitables to customize await for their
environment using existing coroutine types
• Experts: Define new coroutine types

There will be significantly more users of coroutines of coroutines (let's
say 3,000,000 :-)) than those who define awaitables (1000?) and those who
define their own coroutine types (200?). We can educate the power users and
experts. They have very high pain resistance they do template
metaprogramming for breakfast :-).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-10 16:35:51 UTC
Permalink
Post by Nicol Bolas
Post by TONGARI J
The coro is taken by reference, so it can *transfer* the ownership of
the coroutine. If the coroutine is not transferred but is suspended, the
caller still owns the coroutine and can safely unwind the stack, it's
impossible without RAII at the first place.
This is a bit confusing, so I want to explain how I understand things.
`await_suspend` is called by `await`. It is called on `e`, which is the
evaluation of `operator await` on `await`'s expression. So the function
call is `e.await_suspend`. It is given a coroutine handle that is the
coroutine for *the current function*, not the coroutine that was just
called. So `await_suspend` tells the promise object (converted by `operator
await`) to suspend the current function.
The terminology here is a bit confusing, let me clarify that first:
* if the coroutine-return-type is `Future`,
`std::coroutine_traits<Future>::promise_type` is the coroutine-promise-type
* for `await expr`, the result of `operator await(expr)` is typically
called the "awaiter".

I assume what you mean by "promise object" is what we call "awaiter".
Post by Nicol Bolas
Given all of that, if you were to transfer ownership of the current
1) The current function actually *has ownership of itself* (you can't
transfer ownership if you don't own something).
2) The coroutine promise from the `await`ing expression will own the
current function now.
#1 does not make sense. The owner of a coroutine is the *caller*, not the
function itself (things can't own themselves uniquely). The caller has the
right to destroy the coroutine when it is suspended, and there is no reason
to deny the caller that right (for example, if the caller throws an
exception).
And I'm just not sure of the point of #2. Why do you want to give the
coroutine promise that was just called ownership of the function that
called it?
To explain it in English is quite tricky, let me try that in code:

```c++
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
```

You can see that `coro` is the caller, and it's the coroutine that will be
transferred (i.e. what passed into `await_suspend`).
Post by Nicol Bolas
Now P0057 is a complex proposal, so I may be misunderstanding what you and
they are talking about. But from my limited comprehension of it, it seems
like you're performing an action that doesn't make sense.
Then again, I'm not sure why the `operator await` class from the promise
being waited on is being told to suspend the function that called it. But
to the extent that this operation makes sense, that operation clearly
should not transfer ownership.
I'm not sure if "ownership" is the proper term, how about "the right to
execute the continuation"?
Post by Nicol Bolas
Post by TONGARI J
<https://gist.github.com/jamboree/611b8c6551f853956f7d#initial_suspendfinal_suspend>
initial_suspend/final_suspend
I can't understand the rationale, I think the example of final_suspend
you shown is exactly the same as the bool version, what's the difference?
P0054 explained exactly why the bool version doesn't work, with specific
cases. It is complex, but the general gist is that there are race
conditions with threaded coroutine scheduling that make a `bool`-only
version non-functional.
I don't see any code example for initial_suspend, so I'll skip that one for
now. My question was on the final_suspend example in that paper.

What is the difference between this:
```c++
auto final_suspend()
{
struct awaiter
{
promise_type * me;
bool await_ready() { return false; }
void await_resume() {}
bool await_suspend(coroutine_handle<>) {
auto need_suspending = (me->decrement_refcount() > 0);
return need_suspending;
}
};
return awaiter{this};
}
```
and this:
```c++
bool final_suspend()
{
auto need_suspending = (decrement_refcount() > 0);
return need_suspending;
}
```

? In my understanding, it only matters if `await_suspend` tends to execute
the continuation before it returns, but in the example, it's not the case,
I don't see why making the coroutine resumable matters before calling
`await_suspend` here.
Post by Nicol Bolas
Post by TONGARI J
<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>auto
await alternatives
Another benefit of resumable/async (whatever the mark is called) approach
is that you don't need to have await and yield as keywords, instead,
they could be free functions std::await andstd::yield. In the standard,
you can define 2 pseudo keywords __await and __yield and their semantic,
the implementation doesn't need to provide them, but the std::await and
What are "pseudo keywords"? More importantly, you can't make keywords (or
any such semantic) with double-underscores. That is explicitly reserved for
*implementation* use, not standard use.
I mean they're only for definition purpose, they don't need to exist in
real.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-10 18:14:27 UTC
Permalink
Post by TONGARI J
Post by Nicol Bolas
Post by TONGARI J
The coro is taken by reference, so it can *transfer* the ownership of
the coroutine. If the coroutine is not transferred but is suspended, the
caller still owns the coroutine and can safely unwind the stack, it's
impossible without RAII at the first place.
This is a bit confusing, so I want to explain how I understand things.
`await_suspend` is called by `await`. It is called on `e`, which is the
evaluation of `operator await` on `await`'s expression. So the function
call is `e.await_suspend`. It is given a coroutine handle that is the
coroutine for *the current function*, not the coroutine that was just
called. So `await_suspend` tells the promise object (converted by `operator
await`) to suspend the current function.
* if the coroutine-return-type is `Future`,
`std::coroutine_traits<Future>::promise_type` is the coroutine-promise-type
* for `await expr`, the result of `operator await(expr)` is typically
called the "awaiter".
I assume what you mean by "promise object" is what we call "awaiter".
Yes. I got my terminology crossed up, but that's what I meant.
Post by TONGARI J
Post by Nicol Bolas
Given all of that, if you were to transfer ownership of the current
1) The current function actually *has ownership of itself* (you can't
transfer ownership if you don't own something).
2) The coroutine promise from the `await`ing expression will own the
current function now.
#1 does not make sense. The owner of a coroutine is the *caller*, not
the function itself (things can't own themselves uniquely). The caller has
the right to destroy the coroutine when it is suspended, and there is no
reason to deny the caller that right (for example, if the caller throws an
exception).
And I'm just not sure of the point of #2. Why do you want to give the
coroutine promise that was just called ownership of the function that
called it?
```c++
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
```
You can see that `coro` is the caller, and it's the coroutine that will be
transferred (i.e. what passed into `await_suspend`).
I find this code to be rather dubious.

To whom is the ownership transferred? (with this being defined as you do:
"the right to execute the continuation", but we'll discuss that more later)
A thing cannot own itself; there would be no one available to continue it.
So the only thing this can do is transfer ownership to someone else.

Given that, why should the caller of the coroutine be ignorant of said
transference? That is, why shouldn't the code look like this:

std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context

something_takes_the_ownership(std::move(coro));

//Now, coro is empty.

I think that makes a lot more sense. It makes it clear to the reader that
`coro` is no longer to be used. You can even encapsulate it in a factory
function, so that nobody can call `fn` directly.

This style of code also prevents this:

std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
coro(); //Well, it looked like a valid object...

By having your `std::coroutine` (which is a coroutine promise, and
therefore not the same thing as `coroutine_handle`) implement move
semantics, you make it clear that ownership of `coro` has been transferred
to someone else. It's not implicitly gone; it's explicitly gone.

If ownership means "right to call", and the caller of the coroutine had
that right, why should that right be lost without the caller *knowing*
about it? What you seem to want is conceptually no different from this:

unique_ptr<T> t = make_unique<T>(...);
t->TransferOwnership();
//t is now empty... somehow.

This doesn't work with smart pointers. And I don't know why you want it to
work with coroutines. Either way, it seems excessively dangerous.

Think of it like this. `coroutine_handle` is conceptually like a `T*`: it
is copyable and moveable, it has reference semantics, and it has absolutely
no ownership semantics. But like `T*`, you can build whatever ownership
semantics you need. If you want a coroutine promise type that has unique
ownership of the object, you can.

The only thing you can't do is the kind of automatic self-ownership that
`t->TransferOwnership()` would need in order to work.
Post by TONGARI J
Now P0057 is a complex proposal, so I may be misunderstanding what you and
Post by Nicol Bolas
they are talking about. But from my limited comprehension of it, it seems
like you're performing an action that doesn't make sense.
Then again, I'm not sure why the `operator await` class from the promise
being waited on is being told to suspend the function that called it. But
to the extent that this operation makes sense, that operation clearly
should not transfer ownership.
I'm not sure if "ownership" is the proper term, how about "the right to
execute the continuation"?
I think "ownership" works well enough. If you have the right to continue
executing the coroutine, then you also have the right to destroy it. After
all, if you throw an exception, you need to clean yourself up. And
"yourself" now includes the coroutine.

But if you want to separate "person who destroys it" from "person who calls
it", you can do that too. You can implement a coroutine promise object that
has that distinction: someone can get the right to call it from the object,
which is transferrable. And someone can extract the right to destroy it.

It would basically be like a shared_ptr/weak_ptr relationship.

And this is easy to do precisely *because* `coroutine_promise` does not
implement ownership semantics. If it forced unique_coroutine-like
semantics, you'd have to do a lot of extra memory allocation or other
tricks to make it work.
Post by TONGARI J
Post by Nicol Bolas
Post by TONGARI J
<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>auto
await alternatives
Another benefit of resumable/async (whatever the mark is called)
approach is that you don't need to have await and yield as keywords,
instead, they could be free functions std::await andstd::yield. In the
standard, you can define 2 pseudo keywords __await and __yield and
their semantic, the implementation doesn't need to provide them, but the
std::await and std::yield can be defined in terms of __await and __yield
What are "pseudo keywords"? More importantly, you can't make keywords (or
any such semantic) with double-underscores. That is explicitly reserved for
*implementation* use, not standard use.
I mean they're only for definition purpose, they don't need to exist in
real.
Then why introduce them in the text at all? If you're going to have
`std::await` and `std::yield` as functions, then they should be standard
library functions with documentation as such. No need for "pseudo keywords"
"for definition purpose[s]". Just talk about what the functions do.

If you're going the resumable expressions route, do what P0114 does: make
it a function, period.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-10 19:39:45 UTC
Permalink
Post by Nicol Bolas
Post by TONGARI J
```c++
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
```
You can see that `coro` is the caller, and it's the coroutine that will
be transferred (i.e. what passed into `await_suspend`).
I find this code to be rather dubious.
To whom is the ownership transferred?
Whoever wants to execute the continuation.

(with this being defined as you do: "the right to execute the
Post by Nicol Bolas
continuation", but we'll discuss that more later) A thing cannot own
itself; there would be no one available to continue it. So the only thing
this can do is transfer ownership to someone else.
A `coroutine` owns the "execution-context", not itself, it can transfer the
context to others.
Post by Nicol Bolas
Given that, why should the caller of the coroutine be ignorant of said
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
something_takes_the_ownership(std::move(coro));
//Now, coro is empty.
I think that makes a lot more sense. It makes it clear to the reader that
`coro` is no longer to be used. You can even encapsulate it in a factory
function, so that nobody can call `fn` directly.
Uh, that's just a demo how things work. In the "awaiter", what
`await_suspend` does is exactly `std::move(coro)`.
Post by Nicol Bolas
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
coro(); //Well, it looked like a valid object...
By having your `std::coroutine` (which is a coroutine promise, and
therefore not the same thing as `coroutine_handle`) implement move
semantics, you make it clear that ownership of `coro` has been transferred
to someone else. It's not implicitly gone; it's explicitly gone.
It does implement move semantics, and no, it's not a coroutine promise,
it's a coroutine-return-type, but you're right that it's not the same as
`coroutine_handle` because the latter doesn't provide RAII semantic and
thus is not safe to be a coroutine-return-type.
Post by Nicol Bolas
If ownership means "right to call", and the caller of the coroutine had
that right, why should that right be lost without the caller *knowing*
unique_ptr<T> t = make_unique<T>(...);
t->TransferOwnership();
//t is now empty... somehow.
This doesn't work with smart pointers. And I don't know why you want it to
work with coroutines. Either way, it seems excessively dangerous.
It's more like this: `t->TransferOwnership(t);`
Post by Nicol Bolas
Think of it like this. `coroutine_handle` is conceptually like a `T*`: it
is copyable and moveable, it has reference semantics, and it has absolutely
no ownership semantics. But like `T*`, you can build whatever ownership
semantics you need. If you want a coroutine promise type that has unique
ownership of the object, you can.
The only thing you can't do is the kind of automatic self-ownership that
`t->TransferOwnership()` would need in order to work.
It's not self-ownership but ownership-transfer.
Post by Nicol Bolas
Now P0057 is a complex proposal, so I may be misunderstanding what you and
Post by TONGARI J
Post by Nicol Bolas
they are talking about. But from my limited comprehension of it, it seems
like you're performing an action that doesn't make sense.
Then again, I'm not sure why the `operator await` class from the promise
being waited on is being told to suspend the function that called it. But
to the extent that this operation makes sense, that operation clearly
should not transfer ownership.
I'm not sure if "ownership" is the proper term, how about "the right to
execute the continuation"?
I think "ownership" works well enough. If you have the right to continue
executing the coroutine, then you also have the right to destroy it. After
all, if you throw an exception, you need to clean yourself up. And
"yourself" now includes the coroutine.
It does have the right to destroy the coroutine-context.
Post by Nicol Bolas
But if you want to separate "person who destroys it" from "person who
calls it", you can do that too. You can implement a coroutine promise
object that has that distinction: someone can get the right to call it from
the object, which is transferrable. And someone can extract the right to
destroy it.
It would basically be like a shared_ptr/weak_ptr relationship.
And this is easy to do precisely *because* `coroutine_promise` does not
implement ownership semantics. If it forced unique_coroutine-like
semantics, you'd have to do a lot of extra memory allocation or other
tricks to make it work.
I have implemented
coroutine/generator/recursive_generator/task/shared_task/cancellable
task...etc and I didn't observe any extra memory allocation, as to tricks?
maybe.
Post by Nicol Bolas
Post by TONGARI J
Post by Nicol Bolas
Post by TONGARI J
<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>auto
await alternatives
Another benefit of resumable/async (whatever the mark is called)
approach is that you don't need to have await and yield as keywords,
instead, they could be free functions std::await andstd::yield. In the
standard, you can define 2 pseudo keywords __await and __yield and
their semantic, the implementation doesn't need to provide them, but the
std::await and std::yield can be defined in terms of __await and
What are "pseudo keywords"? More importantly, you can't make keywords
(or any such semantic) with double-underscores. That is explicitly reserved
for *implementation* use, not standard use.
I mean they're only for definition purpose, they don't need to exist in
real.
Then why introduce them in the text at all? If you're going to have
`std::await` and `std::yield` as functions, then they should be standard
library functions with documentation as such. No need for "pseudo keywords"
"for definition purpose[s]". Just talk about what the functions do.
If you're going the resumable expressions route, do what P0114 does: make
it a function, period.
Not the same as P0114, but they're not normal functions as well, the
implementation has to do some compiler magic (intrinsic). Anyway, that's
the matter of standard wording and out of the scope of this discussion.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-10 22:03:04 UTC
Permalink
Post by TONGARI J
Post by Nicol Bolas
Post by TONGARI J
```c++
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
```
You can see that `coro` is the caller, and it's the coroutine that will
be transferred (i.e. what passed into `await_suspend`).
I find this code to be rather dubious.
To whom is the ownership transferred?
Whoever wants to execute the continuation.
(with this being defined as you do: "the right to execute the
Post by Nicol Bolas
continuation", but we'll discuss that more later) A thing cannot own
itself; there would be no one available to continue it. So the only thing
this can do is transfer ownership to someone else.
A `coroutine` owns the "execution-context", not itself, it can transfer
the context to others.
I don't know what you mean by "execution context".

If you mean "all of the information needed to be able to resume execution",
we have a word for that: "coroutine". It's a function, a piece of memory
representing its stack variables, and whatever other bits of information
are needed to That's all a coroutine is: what is needed to resume execution
after execution is paused. So if that's what you mean by "execution
context", then we seem to be in agreement.

If you mean something else, well, I'm not sure what that would be.
Post by TONGARI J
Given that, why should the caller of the coroutine be ignorant of said
Post by Nicol Bolas
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
something_takes_the_ownership(std::move(coro));
//Now, coro is empty.
I think that makes a lot more sense. It makes it clear to the reader that
`coro` is no longer to be used. You can even encapsulate it in a factory
function, so that nobody can call `fn` directly.
Uh, that's just a demo how things work. In the "awaiter", what
`await_suspend` does is exactly `std::move(coro)`.
I'm aware of that. I understand that, somewhere in some library code,
movement took place. `std::move` was called.

My point is that this is *not apparent to the user*. They don't know it
happened without looking it up in documentation.

In normal code, if you want to move something, the person who currently own
it must agree to the move, typically by using `std::move`. If you have a
sequence of functions that all take `unique_ptr`, each and every one of
them must std::move their value into each function call. Thus making it
clear that ownership is being transferred at each call site.

We agree that the function who called the coroutine owns it immediately
after this call. And you want the function who owns the coroutine to lose
ownership of it, simply by having resumed it.

That is not a transferal of ownership. In a transfer of ownership, the
source and destination both agree that the source is losing ownership and
the destination is gaining it.

What you want to do is not *give* ownership; you want to *steal* it. You
had the coroutine arbitrarily decide that the caller no longer has the
right to call it. This code cannot be statically inspected to know that
this has happened, not without the coroutine function's implementation.

I would consider this to be *perfidy*; code willfully lying to other code.

This is also why I consider it a code smell to move from a parameter that
is not a value or an rvalue reference. Either the function moves from it or
it doesn't; it shouldn't "sometimes maybe kinda could do so". How can you
possibly write safe code if you don't even know whether the object will be
valid after calling it?

I'm sure you've implemented a coroutine system that does this. That doesn't
make it a good idea.
Post by TONGARI J
Post by Nicol Bolas
std::coroutine<> fn()
{
await std::suspend_always{}; // [1]
await something_takes_the_ownership(); // [2]
}
...
std::coroutine<> coro = fn(); // stop at [1], `coro` owns the context
coro(); // caller - transfer the ownership at [2]
// now `coro` has no associated context
coro(); //Well, it looked like a valid object...
By having your `std::coroutine` (which is a coroutine promise, and
therefore not the same thing as `coroutine_handle`) implement move
semantics, you make it clear that ownership of `coro` has been transferred
to someone else. It's not implicitly gone; it's explicitly gone.
It does implement move semantics, and no, it's not a coroutine promise,
it's a coroutine-return-type,
By P0057, the return type of a coroutine *must* be a coroutine promise, as
defined in 18.11.4. That is what the "coroutine-return-type" is called.
Post by TONGARI J
but you're right that it's not the same as `coroutine_handle` because the
latter doesn't provide RAII semantic and thus is not safe to be a
coroutine-return-type.
That is *not* why coroutines don't directly return a `coroutine_handle`.
The point of the distinction between coroutine promises and coroutine
handles is to allow users to build higher-level semantics over the
lower-level constructs. Handles are the lower level, promises are the
higher. Much like pointers and smart pointers.

If ownership means "right to call", and the caller of the coroutine had
Post by TONGARI J
Post by Nicol Bolas
that right, why should that right be lost without the caller *knowing*
unique_ptr<T> t = make_unique<T>(...);
t->TransferOwnership();
//t is now empty... somehow.
This doesn't work with smart pointers. And I don't know why you want it
to work with coroutines. Either way, it seems excessively dangerous.
It's more like this: `t->TransferOwnership(t);`
No, that's different. Because you gave it a unique_ptr as an explicit
parameter, there is at least the possibility of a transfer of ownership.
Unlike the `coro` case, where there is no apparent reason for `coro` to
become empty. I'm not saying you couldn't implement it that way; I'm saying
that it's not *apparent* and is therefore not good code.

Also, you missed the `std::move`. Unless you're moving from a reference,
which is a bad idea, as previously discussed (and hopefully will become a
core guideline <https://github.com/isocpp/CppCoreGuidelines/issues/316>).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 22:39:58 UTC
Permalink
Post by Nicol Bolas
By P0057, the return type of a coroutine *must* be a coroutine promise,
as defined in 18.11.4. That is what the "coroutine-return-type" is called.
Nicol:

If P0057 states so, it is my mistake and I need to fix it.
The return type of a coroutine has a very boring name: "coroutine return
type". There is no semantics associated with it.
It could be 'void', 'int', 'float', whatever, as long as there is a
corresponding specialization of coroutine_traits so that compiler knows
what to do with it.

The predefined primary template for coroutine_traits states that:

template <typename R, typename... Args>

struct coroutine_traits {

using promise_type = typename R::promise_type;

};



This primary template allows to define coroutine return types without
having to specialize coroutine_traits, by providing an member
struct/typedef named promise_type, as in:

template <typename T>

struct my_generator {

struct promise_type {

auto get_return_object() { return my_generator{this}; }

auto initial_suspend() { return true; }

auto final_suspend() { return true; }

void yield_value(T value) { current_value = value; }

T current_value;

};

bool move_next() {

coro.resume();

return !coro.done();

}

T current_value() { return coro.promise().current_value; }

~my_generator() { coro.destroy(); }

private:

explicit my_generator(promise_type* myPromise)

: coro(coroutine_handle<promise_type>::from_promise(myPromise)) {}

coroutine_handle<promise_type> coro;

};


*coroutine frame* is an object which is an aggregation of all objects with
automatic storage duration in the coroutine body that persists across
suspend points including the *coroutine promise*.


strongly typed *coroutine_handle<P>* pointing at the coroutine frame will
give access to the *coroutine promise* and will allow to manipulate it as
you can see my_generator is doing.


So, to summarize, the terminology in P0057 is:


coroutine = function with suspend points

coroutine return object = what coroutine returns (it may have a
coroutine_handle inside or not. Depends on the library writer)

coroutine frame = an object with all the coroutine state that needs to
persist across suspends (could be on the heap or not)

coroutine_handle = a pointer to a coroutine frame

coroutine_promise = a special automatic object through which compiler
communicates with the library to figure out the semantics of the coroutine
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-11 00:34:27 UTC
Permalink
Post by Gor Nishanov
Post by Nicol Bolas
By P0057, the return type of a coroutine *must* be a coroutine promise,
as defined in 18.11.4. That is what the "coroutine-return-type" is called.
If P0057 states so, it is my mistake and I need to fix it.
OK, so the promise type can be the return value, and by default it is, but
it does not *have* to be.

I'm not sure what the point of the distinction is. Especially since
coroutine_traits is specialized based on something that is hardly unique to
a particular coroutine function: the function's signature. Which means
every function of the form `int()` will have the exact same promise type,
no matter what.

It seems much safer to just return the promise itself.

Also, I was reading through the definition of await, and I realized that it
wasn't doing what I thought it was supposed to be doing.

I assumed that the point of await was to halt the current function's
execution until the process in the await-expr had completed (ie: either hit
its final return or yielded an actual value). Well, I looked at how `await`
is unpacked, and I realized that... there's no loop.

It's a single conditional check. So if I execute a coroutine, and it comes
back as "not ready" due to `await`ing on an async process, what will stop
the caller of the coroutine from simply calling it again, thus causing it
to execute past the suspend point? After all, execution will resume at
exactly the `suspend-resume-point`. And this point is always after the
conditional. So the condition won't even be checked.

So do I have to do something like `while(await <expr>)` to actually wait on
something? Or have I misunderstood something?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 01:29:25 UTC
Permalink
Post by Nicol Bolas
OK, so the promise type can be the return value, and by default it is, but
it does not *have* to be.
Look at the picture on slide 37 of
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4287.pdf

Given a function like this:

R foo(whatever...) { ... await ... }

Compiler instantiates

coroutine_traits<R,whatever...> <-- coroutine traits for 'foo', let's
call this type CT

from these traits compiler grabs

CT::promise_type <-- this is coroutine promise type, let's call it P

Then logically, after the open curly, it injects P $p; <-- $p is coroutine
promise.

Then it transform the body roughly in this way:

R foo(whatever...) {P $p; R $result = $p.get_return_object(); ... await
... }

$result is what will get returned to the caller the first time coroutine
suspends. These steps are also described in P0057/8.4.4 if you want more
precision.
R is coroutine return type. P is a promise type. Type P is discovered when
we instantiate coroutine_traits<R,whatever...>.
Normally R != P.
Post by Nicol Bolas
Post by Nicol Bolas
So do I have to do something like `while(await <expr>)` to actually wait
on something? Or have I misunderstood something?

If it makes it easier, you can watch last year CppCon presentation from
about 10 minutes mark. I will slowly go over how awaitables work:


--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-11 16:09:22 UTC
Permalink
Post by Nicol Bolas
So do I have to do something like `while(await <expr>)` to actually
wait on something? Or have I misunderstood something?
If it makes it easier, you can watch last year CppCon presentation from
http://youtu.be/KUhSjfSbINE
That's a nice presentation, but I'm not sure how it resolve the problem I
raised.

OK, this is going to be long, because I need to explain at every step
what's going on.

Let's take the case of non-threading cooperative operations. Let's say I
have a simple coroutine that executes 3 processes, awaiting on each one:

<whatever> operation()
{
await Process1();
await Process2();
await Process3();
}

Now, I have someone who calls this operation. This code knows that the
process doesn't immediately terminate, so it will have some other work to
do between calls. But it also has some work to do both before and after the
operation:

void cooperative()
{
//Some processing before starting `operation`.

auto op = operation();
while(!op.ready())
{
op.resume();
//Do some arbitrary processing.
}

//Do some processing that requires `operation` to be complete.

return;
}

Given that `<whatever>` and its associated types implements the appropriate
things to make coroutines work, this should do what it says. It will call
each process in `operation`, and it will not go past the loop until
`operation` has finished.

If this is incorrect, let me know, and you can ignore the rest of this.

Now, let's say that I decide that `cooperative` is getting way too big and
complex to reason about. Maybe the processing before or after has grown.
Maybe there are a half-dozen operations in the function that are being done
in series, so I'm having to repeat a lot of "wait and do stuff" loops. The
reason doesn't matter.

What matters is that I now want to change `cooperative` to be a coroutine.
This means that it's up to the caller to implement the "Do some arbitrary
processing" part. So my new code... well, I want it to look like this:

<whatever2> cooperative_co()
{
//Some processing before starting `operation`.

await operation();

//Do some processing that requires `operation` to be complete.

return;
}

Let's assume that `<whatever>` from `operation` implements whatever `await`
requires. Is this implementation of `cooperative_co` the equivalent of
`cooperative`?

I believe that they're *intended* to be the same, since every example of
how to use `await` strongly suggests that they are. But given P0057, I
don't see how they can be, as it seems to synthesize this:

<whatever2> cooperative_co()
{
//Some processing before starting `operation`.

{
auto &&e = operator await(operation());
if(e.await_ready())
e.await_resume();
else
{
e.await_suspend(my_handle);
<SUSPEND_RESUME_POINT>
e.await_resume();
}
}

//Do some processing that requires `operation` to be complete.

return;
}

If the caller to `cooperative_co` resumes the coroutine, then execution
will continue from the <SUSPEND_RESUME_POINT>. So `coroutine_co` will
resume `operation`... but it will only resume it *once*. When `operation`
halts the second time, control transfers back to `coroutine_co` just after
the call to `e.await_resume`.

And then processing goes awry; execution continues straight into processing
that assumes `operation` has completed. That's bad.

What am I missing here? Is the intent that `await` doesn't *actually* wait
until the process has completed? If so, what's the point? When would you
ever use `await` outside of some loop that makes sure the process is
actually finished?

And how do you implement that loop? Does it look like this:

<whatever2> cooperative_co()
{
//Some processing before starting `operation`.

auto op = operation();
while(!await op);

//Do some processing that requires `operation` to be complete.

return;
}

Does that even work? Do you have to manually use `operator await`?

Or is this kind of looping `await` supposed to be implemented by the
various machinery that I glossed over (`<whatever2>`, `<whatever>`, and the
result of `operator await`)?

Or is there something that I'm completely missing here? Because examples
from the video appear to exhibit the exact same flaw.

Take your example of `lock_or_suspend`. If the lock couldn't be immediately
acquired, it will create a thread that will wait for the lock. Then, the
`await` expansion will suspend the coroutine.

But there is nothing in the `await` expansion which will verify that the
lock has been acquired *before* allowing the coroutine to proceed ahead.
There is no loop over the `e.await_ready`. There is no thread.join. There
is nothing that would even conceptually ensure this.

If the caller of `DoSomething` resumes the coroutine after it is suspended,
there's no guarantee that the lock has been acquired. `await` is defined as
a one-time test, not a continuous poll.

So what exactly is `await` for, if it can't even ensure that the conditions
to continue have been met?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 17:42:20 UTC
Permalink
Post by Nicol Bolas
If this is incorrect, let me know, and you can ignore the rest of this.
This part is fine:

<whatever> operation()
{
await Process1();
await Process2();
await Process3();
}

But I think the rest is not how I would approach the cooperative
single-threaded multitasking.
I think what you need is a queue of work, that will be filled with
coroutine handles that are ready to execute.

In initial_suspend() every coroutine participating in that system will
place its handle into the ready queue and suspend.
await Process1(), will link coroutine_handle of the operation coroutine to
a completion of Process1 coroutine.

When Process1() completes, (say in its final_suspend) it will post the
coroutine handles of all of the coroutines which are waiting on it to the
ready queue.
No coroutine should ever call resume() directly. That is responsibility of
your scheduler in the main program, which looks like this.

CoroutineHandleQueue q;

int main() {
Start The Initial Coroutine
while (auto h = q.pull()) { // will block if q is empty
if (!h) break; // I am using empty coroutine_handle as a singnal to
shutdown the loop
h.resume();
}
}
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 19:58:54 UTC
Permalink
What am I doing! There is no need for if(!h) break; Scheduler loop is even
simpler.

CoroutineHandleQueue q;
Post by Gor Nishanov
int main() {
Start The Initial Coroutine
while (auto h = q.pull()) h.resume();
}
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-11 21:05:36 UTC
Permalink
I may have had an epiphany regarding `await`. But first, I think there are
some things you said that aren't correct, according to P0057:

In initial_suspend() every coroutine participating in that system will
Post by Gor Nishanov
place its handle into the ready queue and suspend.
`initial_suspend` is not given a coroutine_handle. Indeed, I'm not sure how
a coroutine promise object is given access to its handle. You can convert
from a handle to its promise, but not the other way around.

However, on to the epiphany.

All of the examples of code using `await` give a general, high-level idea
of what the keyword does: it suspends the current function until the given
object is finished, for some definition of "finished".

And the low-level idea of `await` was clear from P0057. What I couldn't
reconcile was how you get from the low-level details to the high-level
idea. The "mid-level" concept of `await`, if you will.

Now, I think I get it. And the example of `lock_or_suspend` is what made it
clear to me. From a mid-level perspective, `await` means exactly and only
this:

The continued execution of this function (until the next await/return) will
take place at a time and in a fashion defined by *the object being awaited
on*.

It was that last part that kept throwing me off, because it's
counter-intuitive considering most of the examples out there.

But considering the relationship between await and `future::then`, it makes
sense. If you're running a task in a thread, and you ".then" that future,
that task runs on the thread the future is running on (probably). It almost
certainly isn't going to run on *this* thread.

So control over "scheduling" is created by the operation being waited on,
not by the coroutine doing the waiting.

The problem with this is that making things execute in the correct order
requires some from of global God-object that everyone is registered to when
they await on a coroutine. By all rights, it should be able to be a stack
object, created by the ultimate caller of the first coroutine that
initiated the operation.

But I don't know of a way to do that. Even if you pass it to every single
coroutine as a parameter, you can't get it to where it needs to go: the
`await_suspend` call in the awaiter object.

The stack can't be in the coroutine promise, as each coroutine has its own
promise object. And there is no way to make different coroutine promise
objects talk to one another.

I don't see a way to avoid having a global "scheduler". And that sounds
like a *serious* flaw in the await model. If there has to be a scheduler
(or as I prefer to see it, a stack of coroutine_handles), then it should be
something that can be local.

I want to be able to have dozens of such stacks, all in various states of
operation. All of them should be completely independent of each other, but
they ought to be able to use the same types.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 21:23:09 UTC
Permalink
Post by Nicol Bolas
`initial_suspend` is not given a coroutine_handle. Indeed, I'm not sure
how a coroutine promise object is given access to its handle. You can
convert from a handle to its promise, but not the other way around.
Note that initial_suspend() returns awaitable. Awaitable is given a
coroutine_handle<> for the enclosing coroutine.

auto initial_suspend() {
struct awaiter {
bool await_ready() { return false; }
void await_suspend(coroutine_handle<> h) { scheduler.post(h); }
void await_resume();
};
return awaiter{};
}


In a simple case, initial_suspend() could be just

suspend_always initial_suspend() { return{}; } // for simple generator
or
suspend_never initial_suspend() { return{}; } // for fire and forget future
with no RAII

I want to be able to have dozens of such stacks, all in various states of
Post by Nicol Bolas
operation. All of them should be completely independent of each other, but
they ought to be able to use the same types.
I don't think I understand. For me, coroutine is just a fancy way of
providing a callback and a state. If you know how to write async code with
callbacks, you can write it with coroutines, since now the callback could
be just resume this coroutine at this point. And scheduler for me, I am
developing for Windows, is Windows threadpool.
In the world of boost, the scheduler is io_service and your handcrafted
threadpool you build by calling io_service::run() function.

P0055: On Interactions Between Coroutines and Networking Library shows how
async APIs can automatically return a proper awaitable that can have
"negative overhead" compared to regular callbacks.

With respect to coroutine return type, we don't have a good one yet.
Hopefully, somebody will come up with one. When coroutines are on the path
to C++17, I may focus on a good task/future type that will have
cancellation and be zero-overhead, unless somebody else beat me to it.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-12 01:08:46 UTC
Permalink
Post by Gor Nishanov
Post by Nicol Bolas
I want to be able to have dozens of such stacks, all in various states of
operation. All of them should be completely independent of each other, but
they ought to be able to use the same types.
I don't think I understand.
It's simple: I want to take your stackless coroutine system and build a
stackful one out of it. I want a full-fledged fiber, which can
cooperatively pause itself, allowing other processing to resume, and return
to exactly where it was. And it *does not matter* how many (awaiting) calls
are between the pausing and the unpausing; I want resuming it to
automatically resume the top-most function of the call stack.

I want to be able to write the C++ equivalent of a Lua coroutine
<http://www.lua.org/pil/9.html>. Good, extensible cooperative coroutines
that anyone can write and use. Without touching globals or having some
massive scheduler object that's shared or anything. I want a resumable
object representing a stackful coroutine that's a regular C++ object that I
can use like a function.

I know resumable functions are a stackless coroutine system, but it's a
low-level system. I would hope that the goal was to be something more than
just a feature to make threading a bit easier.
Post by Gor Nishanov
With respect to coroutine return type, we don't have a good one yet.
Hopefully, somebody will come up with one. When coroutines are on the path
to C++17, I may focus on a good task/future type that will have
cancellation and be zero-overhead, unless somebody else beat me to it.
When I said "use the same types", I mean that all of the stackful
coroutines would return the same type (or a template class, for return
values and such). The point being that, while they're all returning the
same (template) type, they have no other connections besides this. They
don't all take the same parameters or talk to some global or whatever.

So there would be some `coop_coroutine<T>` type which implements all of the
machinery needed for stackful coroutines. Is it possible to write such a
construct in your system?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 01:53:00 UTC
Permalink
Post by Nicol Bolas
It's simple: I want to take your stackless coroutine system and build a
stackful one out of it. I want a full-fledged fiber, which can
cooperatively pause itself, allowing other processing to resume, and return
to exactly where it was. And it *does not matter* how many (awaiting)
calls are between the pausing and the unpausing; I want resuming it to
automatically resume the top-most function of the call stack.
Would basing this on real fibers (execution context proposal by Nat and
Oliver P0099) be better? There is a saying: Need a fiber, use a fiber :-) .
Nothing can really beat

ADD RSP,X and SUB RSP,X as a way to allocate, deallocate function frame.

P0057 has cheaper suspend/resume and smaller memory footprint than P0099,
but, if you need is a stack, building it out of (stackless) coroutines may
not be the best idea. Real stack is really really efficient. You pay with
memory and higher context switching cost, but the function call is dirt
cheap.
Post by Nicol Bolas
I would hope that the goal was to be something more than just a feature to
make threading a bit easier.
P0057 is not a thread replacement, it is a callback replacement. It makes
it easier to express asynchronous state machine in the form of the
imperative control flow.

So there would be some `coop_coroutine<T>` type which implements all of the
Post by Nicol Bolas
machinery needed for stackful coroutines. Is it possible to write such a
construct in your system?
Sure. It is possible and you can have a specialized allocator that does
something like chained stacks. But depending on your workload it may or may
not be as efficient as if you used fibers in the first place. Hence my
position is that you need both. Coroutines and Fibers. Neither of them is a
complete replacement of each other.
You mix and match according to what problems you are trying to solve.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 02:09:27 UTC
Permalink
Post by Gor Nishanov
P0057 is not a thread replacement, it is a callback replacement. It makes
it easier to express asynchronous state machine in the form of the
imperative control flow.
Let me expand this a little bit. P0057 supply syntactic sugar and a library
hookup that allows:
1) When using to solve async I/O problems, to express async state machine
as imperative control flow
2) When using it to produce lazily a sequence of values, to express sync
state machine as imperative control flow
3) If used with monadic types, helps to provide clean happy path and
automatic "exceptional" path propagation
4) We don't really know what uses other people will find. It is a little
bit like templates. I am not sure that Bjarne was thinking about computing
prime numbers at compile time when template were introduced, but people did
find creative use for them.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-12 04:23:47 UTC
Permalink
I would hope that the goal was to be something more than just a feature to
Post by Gor Nishanov
Post by Nicol Bolas
make threading a bit easier.
P0057 is not a thread replacement, it is a callback replacement. It makes
it easier to express asynchronous state machine in the form of the
imperative control flow.
I don't know. I was just surprised by how much has been said about the
feature (I didn't know that stackful coroutines were still being pursued by
the committee until you mentioned P0099), yet it is just so... limited.

Highly useful, to be sure. But it just seems odd that the most limited and
restrictive "coroutine" proposal is the one that requires explicit language
support and has so many hooks, conditions, and so forth.

P0099 is just two things: a stack/executor, and a way to suspend/resume
(unfortunately, not asymmetrically. There's always something...). Simple,
to the point, and easily understood. P0057 says and does much more... just
to be able to hook a thread callback into half of a function's execution.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 05:06:47 UTC
Permalink
Post by Nicol Bolas
I don't know. I was just surprised by how much has been said about the
feature (I didn't know that stackful coroutines were still being pursued by
the committee until you mentioned P0099), yet it is just so... limited.
I am sorry you fill that way. :-)
Here is my view:

Thread
State: User-mode stack + kernel mode stack + context
Run by an OS scheduler
Unit of suspension: entire thread, CPU is free to run something else
Context: ~ entire register file +

Fiber (aka User-Mode-Scheduled-Thread, stackful coro)
State: User-mode stack + Context
Run by some thread
Unit of suspension: fiber, underlying thread is free to run
Context: ABI mandated non-volatile regs +

Coroutine (Stackless, aka Generalized Function)
State: Local variables + Context
Run by some thread or fiber
Unit of suspension: coroutine, underlying thread/fiber is free to run
Context: ~ 4 bytes +

Coroutine is strictly less powerful than a thread or a fiber. Either of
those can be used to emulate it, but, it has unmatched efficiency for the
problems it is good at solving:

The design goals for P0057 and its predecessors cannot be matched by thread
or fiber:

* Scalable (to billions of concurrent coroutines)
* Efficient (resume and suspend operations comparable in cost to a function
call overhead)
* Seamless interaction with existing facilities with no overhead
* Open ended coroutine machinery allowing library designers to develop
coroutine libraries exposing various high-level semantics, such as
generators, goroutines, tasks and more.
* Usable in environments where exception are forbidden or not available

All three abstractions I mentioned have their uses. You use appropriate
tool to solve the problem you have.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 05:07:16 UTC
Permalink
Post by Gor Nishanov
I am sorry you fill that way. :-)
fill => feel
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Evgeny Panasyuk
2015-10-12 06:02:19 UTC
Permalink
Post by Gor Nishanov
Thread
State: User-mode stack + kernel mode stack + context
Run by an OS scheduler
Unit of suspension: entire thread, CPU is free to run something else
Context: ~ entire register file +
Fiber (aka User-Mode-Scheduled-Thread, stackful coro)
State: User-mode stack + Context
Run by some thread
Unit of suspension: fiber, underlying thread is free to run
Context: ABI mandated non-volatile regs +
Coroutine (Stackless, aka Generalized Function)
State: Local variables + Context
Run by some thread or fiber
Unit of suspension: coroutine, underlying thread/fiber is free to run
Context: ~ 4 bytes +
Coroutine is strictly less powerful than a thread or a fiber. Either of
those can be used to emulate it, but, it has unmatched efficiency for
Actually stackless coroutines can be used to implement things which are
not possible (practically) for threads and fibers.

For instance:
* Generators which are Forward Iterators, not just single pass Input
Iterators.
* Implementation of List monad: small live demo
http://coliru.stacked-crooked.com/a/465f5bcb59c8b0b3
* Serialization of coroutine and even migration over network

For instance, this is possible with value-type stackless coroutines
proposed by Christopher Kohlhoff.
To some extent this approach can be implemented with current compilers
based on macros.
--
Evgeny Panasyuk
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Oliver Kowalke
2015-10-12 08:03:22 UTC
Permalink
Post by Gor Nishanov
Fiber (aka User-Mode-Scheduled-Thread, stackful coro)
State: User-mode stack + Context
Run by some thread
Unit of suspension: fiber, underlying thread is free to run
Context: ABI mandated non-volatile regs +
+ user-land scheduler
Post by Gor Nishanov
Coroutine (Stackless, aka Generalized Function)
State: Local variables + Context
Run by some thread or fiber
Unit of suspension: coroutine, underlying thread/fiber is free to run
Context: ~ 4 bytes +
== stackless Coroutine (allocation of activation record/frame for each
instance)

+ stackful Coroutines
Post by Gor Nishanov
The design goals for P0057 and its predecessors cannot be matched by
* Scalable (to billions of concurrent coroutines)
* Efficient (resume and suspend operations comparable in cost to a
function call overhead)
* Seamless interaction with existing facilities with no overhead
* Open ended coroutine machinery allowing library designers to develop
coroutine libraries exposing various high-level semantics, such as
generators, goroutines, tasks and more.
* Usable in environments where exception are forbidden or not available
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 09:20:48 UTC
Permalink
Post by Gor Nishanov
Post by Nicol Bolas
I don't know. I was just surprised by how much has been said about the
feature (I didn't know that stackful coroutines were still being pursued by
the committee until you mentioned P0099), yet it is just so... limited.
I am sorry you fill that way. :-)
Thread
State: User-mode stack + kernel mode stack + context
Run by an OS scheduler
Unit of suspension: entire thread, CPU is free to run something else
Context: ~ entire register file +
Fiber (aka User-Mode-Scheduled-Thread, stackful coro)
State: User-mode stack + Context
Run by some thread
Unit of suspension: fiber, underlying thread is free to run
Context: ABI mandated non-volatile regs +
Coroutine (Stackless, aka Generalized Function)
State: Local variables + Context
Run by some thread or fiber
Unit of suspension: coroutine, underlying thread/fiber is free to run
Context: ~ 4 bytes +
Please, let's not co-opt terms. Coroutine is really the generic concept.
When used unqualified it historically referred to stackful, usually
asymmetric, coroutines (lua, icon, boost, the original invention from
Conway. Historically fibers are just symmetric stackful coroutines. In the
last decade or two, 'generator' is the term that has been used most often
to refer to asymmetric stackless coroutines.
Post by Gor Nishanov
Coroutine is strictly less powerful than a thread or a fiber. Either of
those can be used to emulate it, but, it has unmatched efficiency for the
I assume you mean Generator here.
Post by Gor Nishanov
The design goals for P0057 and its predecessors cannot be matched by
* Scalable (to billions of concurrent coroutines)
I agree it is hard to match the efficiency of only allocating a frame
instead of a full stack, but with compiler support for full coroutines,
this could be matched.
Post by Gor Nishanov
* Efficient (resume and suspend operations comparable in cost to a
function call overhead)
that's already the case for an optimized implementation of stackful
coroutines. The win with generators is that they would allow direct
inlining of the callee into the caller.
Post by Gor Nishanov
* Seamless interaction with existing facilities with no overhead
that's actually a feature of stackful coroutines. Most common
implementations of stackless coroutines lead to the red or blue function
problem:
http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/.
Post by Gor Nishanov
* Open ended coroutine machinery allowing library designers to develop
coroutine libraries exposing various high-level semantics, such as
generators, goroutines, tasks and more.
goroutines are stackful. They are trivially emulable on top of fibers, not
so much on top of stackless generators.
Post by Gor Nishanov
* Usable in environments where exception are forbidden or not available
How is that an advantage of stackles over stackful coroutines?
Post by Gor Nishanov
All three abstractions I mentioned have their uses. You use appropriate
tool to solve the problem you have.
It would be better to only have an abstraction: stackful symmetric
coroutines with compiler support for eliding the side stack if all yield
points are lexically contained in the function at the bottom of the ideal
stack.

-- gpd
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Oliver Kowalke
2015-10-12 10:04:25 UTC
Permalink
2015-10-12 11:20 GMT+02:00 Giovanni Piero Deretta <***@gmail.com>:

It would be better to only have an abstraction: stackful symmetric
Post by Giovanni Piero Deretta
coroutines with compiler support for eliding the side stack if all yield
points are lexically contained in the function at the bottom of the ideal
stack.
+1
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 10:43:36 UTC
Permalink
Post by Giovanni Piero Deretta
It would be better to only have an abstraction: stackful symmetric
Post by Giovanni Piero Deretta
coroutines with compiler support for eliding the side stack if all yield
points are lexically contained in the function at the bottom of the ideal
stack.
+1
And BTW, I think this is pretty much the same analysis that is required to
elide the memory allocation of the generator frame required by the current
stackless coroutine proposal. The possible optimizations are like the
following:

A) in the callee, if the caller continuation does not escape, the callee
stack can be elided. Only the stack frame need to be explicitly allocated:
the caller stack can be reused for any sub routine call as we know that the
caller won't be possibly resumed as the callee control its continuation.
B) additionally, in the caller, if the callee continuation also doesn't
escape, we can stack allocate the callee stack frame instead of heap
allocating it as its lifetime can be determined statically.

We can't profitably do B if we can't do A, because we would need to
allocate a whole stack instead of just one frame inside the caller stack.

Note that 'Escape' means that the compiler is not able to statically track
all pointers to the continuation. This means that it would still possible
for the compiler to do the 'stackless' optimization even if, on the callee
side, the continuation is passed to a sub routine and yielded from there,
as long as the compiler can 'see' inside the subroutine (via inlining or
interprocedural analysis).

As it is desirable to be able to statically verify the stackless property,
a set of optional annotations can be added to annotate both continuations
and functions. Passing a continuation to an annotated function guarantee
that this action by itself won't make the continuation escaped. Annotated
functions must of course be inline. Annotating a continuation prevent its
address to be taken and prevents passing it to non annotated functions.

Note that the annotations can be used for either (or both) guaranteeing the
stackless property (by annotating the caller continuation in the callee)
and the freedom from allocation (by annotating the callee continuation in
the caller).

-- gpd
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 12:24:56 UTC
Permalink
On Monday, October 12, 2015 at 2:20:48 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
Please, let's not co-opt terms. Coroutine is really the generic concept.
When used unqualified it historically referred to stackful, usually
asymmetric, coroutines (lua, icon, boost, the original invention from
Conway. Historically fibers are just symmetric stackful coroutines. In the
last decade or two, 'generator' is the term that has been used most often
to refer to asymmetric stackless coroutines.
I fully agree. Let's not co-opt terms and go with historical definitions.

Historically (going back to the end of 50s), coroutines meant a generalized
subroutine, or as Knuth defines a subroutine is special-case of a
coroutine. Nowhere there was a discussion that subroutines and coroutines
must carry a call stack around with them.

The term was corrupted afterwards as the frequently used emulation of
coroutines was done via manipulating stacks of the currently running
thread/process. Hence the mix-up between coroutines and light-weight
threads.

The term fiber was first appeared in NT4 (end of 90s) with the OS facility
that provided user-mode stack manipulation APIs that allowed implementing
cooperative multitasking. Scheduler was not part of the fiber APIs. It had
facilities with which you can implement a scheduler. By that historical
definition Oliver's execution_context is a fiber.

I like this terminology not necessarily because it is historically
accurate, but, because it give short, crisp memorable words to orthogonal
concepts without the need to disambiguate with various adjectives.

Thread? which one, heavy-weight one or light-weight one like fiber?
Fiber?, do you mean like in NT (no scheduler included), or as the one
proposed to Boost that does include the scheduler?
Coroutine?, do you mean stackful (meaning either a fiber or coroutine
emulation via fiber) or stackless and if stackless, do you mean like Python
stackless (meaning does not use OS stack or stackless stackless where there
is no stack of any kind? or Python async function and generator which are
stackless stackless coroutines?)

That mess needs to be cleaned up. Thus, the set of definitions I use.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 12:52:06 UTC
Permalink
Post by Gor Nishanov
On Monday, October 12, 2015 at 2:20:48 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
Please, let's not co-opt terms. Coroutine is really the generic concept.
When used unqualified it historically referred to stackful, usually
asymmetric, coroutines (lua, icon, boost, the original invention from
Conway. Historically fibers are just symmetric stackful coroutines. In the
last decade or two, 'generator' is the term that has been used most often
to refer to asymmetric stackless coroutines.
I fully agree. Let's not co-opt terms and go with historical definitions.
Historically (going back to the end of 50s), coroutines meant a
generalized subroutine, or as Knuth defines a subroutine is special-case of
a coroutine. Nowhere there was a discussion that subroutines and coroutines
must carry a call stack around with them.
The term was corrupted afterwards as the frequently used emulation of
coroutines was done via manipulating stacks of the currently running
thread/process. Hence the mix-up between coroutines and light-weight
threads.
The call stack as a concept exist distinctly from its actual physical
realization; similarly the 'stackful/stackless' terms have nothing to do
with the classic physical C stack. A language implementation that heap
allocates each individual function frame and has call/cc can trivially
implement stackful coroutines even if it has nothing like the C stack (for
example stackless python or many scheme interpreters). The terms simply
refer to the ability to yield from a nested call, which was a property of
coroutines as originally defined or otherwise they wouldn't have been a
generalization of subroutines.

The terms stackful and stackless come from "Revisiting Coroutines" by Ana L
́cia de Moura and Roberto Ierusalimschy", which is pretty much the
definitive treatment of the topic. I believe I introduced these definitions
to the C++ community when I used them in the original Boost.Coroutine GSOC
in 2006 as that paper had a huge influence in my design.

-- gpd
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 13:17:05 UTC
Permalink
On Monday, October 12, 2015 at 5:52:07 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
The terms stackful and stackless come from "Revisiting Coroutines" by Ana
L ́cia de Moura and Roberto Ierusalimschy", which is pretty much the
definitive treatment of the topic.
I am well aware of this paper. They surveyed the messy field and tried try
to clean up the existing terminology mess. They were tame in their attempts
to clean up the terminology and went with "let's pile up more adjectives
path". The conclusion is questionable too. They came to the conclusion that
(in their terminology) stackful asymmetric coroutine are the best.

Well they sacrificed performance on two fronts.

If they want stackfullness, they should have chosen fibers. Context switch
is expensive in the fiber, thus you want to minimize number of switches you
do. Going with asymmetric, they doubled the cost, since now when we want to
do direct transfer between two fibers, we have to first go back to the
scheduler.

And, of course, by favoring "stackfulness" they left a lot of performance
on the floor.

We control the language we use in C++. Whatever terms we chose can
influence the rest of the industry.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 13:37:53 UTC
Permalink
Post by Gor Nishanov
On Monday, October 12, 2015 at 5:52:07 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
The terms stackful and stackless come from "Revisiting Coroutines" by Ana
L ́cia de Moura and Roberto Ierusalimschy", which is pretty much the
definitive treatment of the topic.
I am well aware of this paper. They surveyed the messy field and tried try
to clean up the existing terminology mess. They were tame in their attempts
to clean up the terminology and went with "let's pile up more adjectives
path". The conclusion is questionable too. They came to the conclusion that
(in their terminology) stackful asymmetric coroutine are the best.
Well they sacrificed performance on two fronts.
If they want stackfullness, they should have chosen fibers. Context switch
is expensive in the fiber, thus you want to minimize number of switches you
do. Going with asymmetric, they doubled the cost, since now when we want to
do direct transfer between two fibers, we have to first go back to the
scheduler.
agree about this; I believe that symmetric coroutines are better.
Post by Gor Nishanov
And, of course, by favoring "stackfulness" they left a lot of performance
on the floor.
... but of course disagree about this. As I discussed previously, I believe
that the stackless coroutines can be implemented as a compiler optimization
on top of stackful coroutines; the loss of expressiveness is otherwise too
great.

-- gpd
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 13:46:40 UTC
Permalink
On Monday, October 12, 2015 at 6:37:53 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
... but of course disagree about this. As I discussed previously, I
believe that the stackless coroutines can be implemented as a compiler
optimization on top of stackful coroutines;
That would be nice. I don't know how to do this efficiently. But if it is
possible I am for it.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 14:07:48 UTC
Permalink
Post by Gor Nishanov
On Monday, October 12, 2015 at 6:37:53 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
... but of course disagree about this. As I discussed previously, I
believe that the stackless coroutines can be implemented as a compiler
optimization on top of stackful coroutines;
That would be nice. I don't know how to do this efficiently. But if it is
possible I am for it.
I'm not a compiler writer, but it seems to me that this requires exactly
the same analysis that is required to remove allocations of the generator
state:

f1 is the caller, f2 is the callee. When f1 instantiates a new coroutine
with f2, it gets a continuation object (or context_handler or or
coroutine_handle or whatever you want to call it). This object represent a
pointer to the halted coroutine state (a single frame for a stackless
generator, a full call stack for a stackful coroutine). Now, you guys have
already implemented an analysis that, if, in f2, no pointer to the state of
the coroutine running f2 ever escapes, you can simply allocate the statein
the stack of f1.

But you can apply the same analysis f2 as well: when it is resumed it gets
(implicitly or explicitly) a continuation to the now suspended f1. If
*between to resumption points in f2*, this continuation does not escape, we
know that f1 will not be resumed outside of f2 control, so we can
temporarily use its stack for every subroutine stack. Only the frame of f2
need to be explicitly allocated, as we know it is the only live frame at
the point the coroutine is suspoended.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 14:18:09 UTC
Permalink
On Monday, October 12, 2015 at 7:07:48 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
f1 is the caller, f2 is the callee. When f1 instantiates a new coroutine
with f2, it gets a continuation object (or context_handler or or
coroutine_handle or whatever you want to call it). This object represent a
pointer to the halted coroutine state (a single frame for a stackless
generator, a full call stack for a stackful coroutine). Now, you guys have
already implemented an analysis that, if, in f2, no pointer to the state of
the coroutine running f2 ever escapes, you can simply allocate the statein
the stack of f1.
I understand what you are saying, but to me, it is not stackful suspension.
It is an illusion of stackful suspension. It is only possible for cases
where the call sequence has static nesting (including the cases where tail
recursion elimination optimizaiton is applied).

Triple-A suggestion from P0054 (automatically awaited awaitables) will
extend the illusion even further supporting dynamic stack structure
wherever we cannot do the elision, and, combined with specialized allocator
that does some kind of segmented stacks, you may be able to achieve
something close to stackful in appearance.

But, I think that there still be workloads when fibers will be more
efficient than what I described above. Thus I view fibers and coroutines
complementary, neither one can be a complete replacement of each other, as
there will be case when using one will be more appropriate than the other.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 14:45:32 UTC
Permalink
Post by Gor Nishanov
On Monday, October 12, 2015 at 7:07:48 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
f1 is the caller, f2 is the callee. When f1 instantiates a new coroutine
with f2, it gets a continuation object (or context_handler or or
coroutine_handle or whatever you want to call it). This object represent a
pointer to the halted coroutine state (a single frame for a stackless
generator, a full call stack for a stackful coroutine). Now, you guys have
already implemented an analysis that, if, in f2, no pointer to the state of
the coroutine running f2 ever escapes, you can simply allocate the statein
the stack of f1.
I understand what you are saying, but to me, it is not stackful
suspension. It is an illusion of stackful suspension. It is only possible
for cases where the call sequence has static nesting (including the cases
where tail recursion elimination optimizaiton is applied).
The objective is being no worse than a purely stackless generator
implementation, which doesn't have stackful suspension by definition, for
all use cases in which a generator is sufficient, while still allowing
stackful suspension if desired. Do you have an example where a stackless
coroutine is sufficient but the above analysis won't be able to infer the
"stackless-ness"?

-- gpd
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 18:15:31 UTC
Permalink
On Monday, October 12, 2015 at 7:45:33 AM UTC-7, Giovanni Piero Deretta
Do you have an example where a stackless coroutine is sufficient but the
above analysis won't be able to infer the "stackless-ness"?
I have an example of the opposite. It is an example when putting the
following function on a fiber, as opposed to making it into a coroutine
with all optimizations enabled (tail recursion, heap elision, custom
allocator), the fiber should beat the coroutine for at least some values of
to and from.

void f(int from, int to) {
auto diff = to - from;
if (diff == 0)
return;

if (diff == 1) {
yield(from);
return;
}

auto mid = (from + to) / 2;

f(from, mid);
f(mid, to);
}


Since the tail recursion will eliminate only one of the recursive calls, we
will be comparing two bucket of operations.
1 fiber creation/destruction versus N coroutine creations/destructions.
N coroutine suspensions vs N fiber suspensions.
N function calls/returns N coroutine calls/returns. ~ equivalent

Fiber creation is more expensive than coroutine creation, but we are doing
just one of them, versus N for coroutines.
Coroutine suspension/resumption is cheaper than fiber suspension/resumption.

So, depending on N and cost of the context switch, fibers may be still
faster than super optimized attempt to build a fiber out of stackless
coroutines.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Giovanni Piero Deretta
2015-10-12 20:40:24 UTC
Permalink
Post by Gor Nishanov
On Monday, October 12, 2015 at 7:45:33 AM UTC-7, Giovanni Piero Deretta
Do you have an example where a stackless coroutine is sufficient but the
above analysis won't be able to infer the "stackless-ness"?
I have an example of the opposite. It is an example when putting the
following function on a fiber, as opposed to making it into a coroutine with
all optimizations enabled (tail recursion, heap elision, custom allocator),
the fiber should beat the coroutine for at least some values of to and from.
void f(int from, int to) {
auto diff = to - from;
if (diff == 0)
return;
if (diff == 1) {
yield(from);
return;
}
auto mid = (from + to) / 2;
f(from, mid);
f(mid, to);
}
well, I wanted an example expressible with a stackless coroutine that
is not inferrable by the compiler, i.e. an example where the compiler
wouldn't be able to prove that allocating a single frame is sufficient
while the programmer can . This is not it.
Post by Gor Nishanov
Since the tail recursion will eliminate only one of the recursive calls, we
will be comparing two bucket of operations.
1 fiber creation/destruction versus N coroutine creations/destructions.
N coroutine suspensions vs N fiber suspensions.
N function calls/returns N coroutine calls/returns. ~ equivalent
Fiber creation is more expensive than coroutine creation, but we are doing
just one of them, versus N for coroutines.
Coroutine suspension/resumption is cheaper than fiber suspension/resumption.
I do not see why that would be the case, the same optimisations
available for stackless coroutines are applicable to stackful
coroutines. Sure a pure library stackful coroutine implementation
might (or not) be slower than an optimized compiler assisted stackless
generator, but in principle there are no differences, other than
allocating a single stack vs N separate frames.

Also note that if the compiler doesn't have a bound on the recursion
depth, it might have an hard time stack allocating the N separate
frames.
Post by Gor Nishanov
So, depending on N and cost of the context switch, fibers may be still
faster than super optimized attempt to build a fiber out of stackless
coroutines.
exactly.

-- gpd
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-12 16:54:39 UTC
Permalink
Post by Gor Nishanov
On Monday, October 12, 2015 at 5:52:07 AM UTC-7, Giovanni Piero Deretta
Post by Giovanni Piero Deretta
The terms stackful and stackless come from "Revisiting Coroutines" by Ana
L ́cia de Moura and Roberto Ierusalimschy", which is pretty much the
definitive treatment of the topic.
I am well aware of this paper. They surveyed the messy field and tried try
to clean up the existing terminology mess. They were tame in their attempts
to clean up the terminology and went with "let's pile up more adjectives
path".
...
We control the language we use in C++. Whatever terms we chose can
influence the rest of the industry.
Which is why we shouldn't take words that the industry already has firm
definitions for and make them mean something totally different. That only
creates needless confusion.

If a bunch of sources, from lots of different people who are experts at the
field, define a word one way, then it is wrong for us to arbitrarily define
it another way. If most people agree that "coroutine" means "function who's
execution can be halted and resumed at the point where it was halted,
regardless of where in the call stack the halting/resuming happened", then
it is wrong for us to define C++ coroutines to be "resumable functions."

I understand the desire to what to call your feature "coroutines". But when
the functionality is explained to most people who know them from other
languages, they simply will not agree with your definition.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 17:21:55 UTC
Permalink
Post by Nicol Bolas
I understand the desire to what to call your feature "coroutines".
Do not attempt to read my mind. The proposals I brought to Lenexa

https://isocpp.org/files/papers/N4402.pdf - overview
https://isocpp.org/files/papers/N4403.pdf - wording

I specifically used resumable functions, as not to use coroutine so as not
to confuse people.
That was the change from post-Urbana mailining. I requested a vote to
approve the rename and there was no consensus.
So the rename was not approved and they stayed coroutines.

After the Lenexa vote, I decided to embrace the word coroutines. And since
my preference is for short descriptive words, from now one, when I say
coroutine, thread and fiber, I mean:

first class object language supported stackless asymmetric coroutine or
generalized function => coroutine
first class object stackful symmetric coroutine or user-mode cooperative
scheduled thread => fiber
first class object heavy-weight thread equivalent to OS thread => thread

Of course, I did not mind that terminology before Lenexa vote, but, I was
not insisting on it, since resumable function is short and descriptive
enough.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Oliver Kowalke
2015-10-12 07:58:20 UTC
Permalink
Post by Nicol Bolas
P0099 is just two things: a stack/executor, and a way to suspend/resume
(unfortunately, not asymmetrically. There's always something...). Simple,
to the point, and easily understood. P0057 says and does much more... just
to be able to hook a thread callback into half of a function's execution.
std::execution_context (N4397/N4398/P0099R0) is proposed as a low-level API
- used as a building-block for higher-level abstractions like (asymmetric)
coroutines and fiber (aka uer-land threads).
is uses symmetric context switching which is more efficient than the
asymmetric counterpart (prevents additional switch back).
asymmetric coroutines and fibers can be build upon this API as already
proved with boost.coroutine2 and boost.fiber.

in its essence a 'execution_context' is a pointer to a stack address, that
means if an execution_context gets suspended,
CPU registers (defined by the ABI) are pushed to the stack and the
execution_context stores the address of the last push-op.
if the context gets resumed, the address is assigned to the stack pointer
and the content of the registers is poped from the stack.
(at least that is how execution_context from boost.context works)
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Oliver Kowalke
2015-10-12 07:50:25 UTC
Permalink
Post by Nicol Bolas
So there would be some `coop_coroutine<T>` type which implements all of
the machinery needed for stackful coroutines. Is it possible to write such
a construct in your system?
proposed in N3985
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Oliver Kowalke
2015-10-11 05:10:06 UTC
Permalink
Post by TONGARI J
A `coroutine` owns the "execution-context", not itself, it can transfer
Post by TONGARI J
the context to others.
I don't know what you mean by "execution context".
If you mean "all of the information needed to be able to resume
execution", we have a word for that: "coroutine". It's a function, a piece
of memory representing its stack variables, and whatever other bits of
information are needed to That's all a coroutine is: what is needed to
resume execution after execution is paused. So if that's what you mean by
"execution context", then we seem to be in agreement.
what about this definition:

execution context == single/individual path of execution inside a program;
an application might have more than one paths
an execution context can be characterized by instruction pointer + set of
general CPU registers and the stack (stackless coroutines own a degenerated
form == activation record, used to store local variables)

a coroutine can be understood as the interaction of two execution contexts,
a coroutine consists of a caller and a callee, which are thigh coupled. the
caller execution context resumes only the execution context of the callee,
while the callee can only suspend to the caller. data might be transferred
in both directions.
of course an execution context can play multiple roles, for instance the
execution context of the callee might play the role of a caller for a
stacked coroutine (-> coroutine launched inside a coroutine). then the
execution context of the callee from the external coroutine becomes that
caller execution context for the internal/stacked coroutine.
generators are a special kind of coroutines - data flows only in one
direction (callee -> caller).

fibers can be understood a loosely coupled execution contexts - a fiber
does not know its predecessor and successor. data is not directly exchanged
between fibers.

delimited continuations could be defined in this way too (with a more
complex pattern of interactions/data transfer).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-11 06:25:27 UTC
Permalink
Post by Nicol Bolas
I'm aware of that. I understand that, somewhere in some library code,
movement took place. `std::move` was called.
My point is that this is *not apparent to the user*. They don't know it
happened without looking it up in documentation.
And my point is "the user doesn't need to know". Typically the user doesn't
even know there's a coroutine behind the scene, he just see the
coroutine-return-type. The case that the return-type is the `coroutine` is
the exception. An end-user typically doesn't deal with the coroutine
directly.
Post by Nicol Bolas
In normal code, if you want to move something, the person who currently
own it must agree to the move, typically by using `std::move`. If you have
a sequence of functions that all take `unique_ptr`, each and every one of
them must std::move their value into each function call. Thus making it
clear that ownership is being transferred at each call site.
We agree that the function who called the coroutine owns it immediately
after this call. And you want the function who owns the coroutine to lose
ownership of it, simply by having resumed it.
That is not a transferal of ownership. In a transfer of ownership, the
source and destination both agree that the source is losing ownership and
the destination is gaining it.
What you want to do is not *give* ownership; you want to *steal* it. You
had the coroutine arbitrarily decide that the caller no longer has the
right to call it. This code cannot be statically inspected to know that
this has happened, not without the coroutine function's implementation.
I would consider this to be *perfidy*; code willfully lying to other code.
It's not lying, it the protocol between the caller and callee - the caller
says to the callee "take me if you want to execute the continuation", and
the callee can make its decision to take it or leave it as is.
Post by Nicol Bolas
If ownership means "right to call", and the caller of the coroutine had
Post by TONGARI J
Post by Nicol Bolas
that right, why should that right be lost without the caller *knowing*
unique_ptr<T> t = make_unique<T>(...);
t->TransferOwnership();
//t is now empty... somehow.
This doesn't work with smart pointers. And I don't know why you want it
to work with coroutines. Either way, it seems excessively dangerous.
It's more like this: `t->TransferOwnership(t);`
No, that's different. Because you gave it a unique_ptr as an explicit
parameter, there is at least the possibility of a transfer of ownership.
Unlike the `coro` case, where there is no apparent reason for `coro` to
become empty. I'm not saying you couldn't implement it that way; I'm saying
that it's not *apparent* and is therefore not good code.
```c++
template<class T>
void TransferOwnership(unique_ptr<T>& t)
{
t->TransferOwnership(t);
}
```

It's strange to me that you think `TransferOwnership(t)` is different from
`t->TransferOwnership(t)`.
Post by Nicol Bolas
Also, you missed the `std::move`. Unless you're moving from a reference,
which is a bad idea, as previously discussed (and hopefully will become a
core guideline <https://github.com/isocpp/CppCoreGuidelines/issues/316>).
In my case, it may or may not be moved, depending on the awaiter.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-11 14:38:16 UTC
Permalink
Post by TONGARI J
Post by Nicol Bolas
I'm aware of that. I understand that, somewhere in some library code,
movement took place. `std::move` was called.
My point is that this is *not apparent to the user*. They don't know it
happened without looking it up in documentation.
And my point is "the user doesn't need to know". Typically the user
doesn't even know there's a coroutine behind the scene, he just see the
coroutine-return-type.
Define "typically".

If you're executing an asynchronous process, you usually need to know that
it's asynchronous. The caller needs to make a decision about how to
interact with this "coroutine-return-type", since the whole point of an
async process is that it's not necessarily available yet. If it's not
available, does he wait for it to finish, blocking the current execution?
Does he `await` for it to become available (thus putting the burden on his
caller)? Or does he poll an object to see if the value is available, doing
other processing in the meantime if it isn't?

Even resumable expressions don't make these decisions transparent.

The case that the return-type is the `coroutine` is the exception. An
Post by TONGARI J
end-user typically doesn't deal with the coroutine directly.
The point of a coroutine is that it can stop execution and be resumed
later. A function is made a coroutine for one of two reasons (which can be
combined):

1) The function will repeatedly return values, transferring control to its
caller between them.

2) The function will not be able to actually return its value immediately
(or otherwise signal that it's finished its task).

Neither of these is something that the function directly invoking the
coroutine can completely ignore.

You can devise a special return value object that hides these details. But
even then, it's clear to the user that they're playing with something
special, not a normal function call. So I don't buy the idea that the user
can be entirely ignorant of a given function being a coroutine.

In normal code, if you want to move something, the person who currently own
Post by TONGARI J
Post by Nicol Bolas
it must agree to the move, typically by using `std::move`. If you have a
sequence of functions that all take `unique_ptr`, each and every one of
them must std::move their value into each function call. Thus making it
clear that ownership is being transferred at each call site.
We agree that the function who called the coroutine owns it immediately
after this call. And you want the function who owns the coroutine to lose
ownership of it, simply by having resumed it.
That is not a transferal of ownership. In a transfer of ownership, the
source and destination both agree that the source is losing ownership and
the destination is gaining it.
What you want to do is not *give* ownership; you want to *steal* it. You
had the coroutine arbitrarily decide that the caller no longer has the
right to call it. This code cannot be statically inspected to know that
this has happened, not without the coroutine function's implementation.
I would consider this to be *perfidy*; code willfully lying to other code.
It's not lying, it the protocol between the caller and callee - the caller
says to the callee "take me if you want to execute the continuation", and
the callee can make its decision to take it or leave it as is.
Then we're going to have to agree to disagree about that, because I don't
believe that ownership of anything should be transferred without consent.
*Explicit* consent. If the caller ever owned that coroutine, then it's on
the caller to transfer that ownership elsewhere.

That being said, I don't believe there is anything in the current design
which actively *prevents* you from creating a promise type/awaitable that
behaves this way. If you want to implement such a transfer of ownership,
the design does not prevent you from doing so.
Post by TONGARI J
If ownership means "right to call", and the caller of the coroutine had
Post by Nicol Bolas
Post by TONGARI J
Post by Nicol Bolas
that right, why should that right be lost without the caller *knowing*
unique_ptr<T> t = make_unique<T>(...);
t->TransferOwnership();
//t is now empty... somehow.
This doesn't work with smart pointers. And I don't know why you want it
to work with coroutines. Either way, it seems excessively dangerous.
It's more like this: `t->TransferOwnership(t);`
No, that's different. Because you gave it a unique_ptr as an explicit
parameter, there is at least the possibility of a transfer of ownership.
Unlike the `coro` case, where there is no apparent reason for `coro` to
become empty. I'm not saying you couldn't implement it that way; I'm saying
that it's not *apparent* and is therefore not good code.
```c++
template<class T>
void TransferOwnership(unique_ptr<T>& t)
{
t->TransferOwnership(t);
}
```
It's strange to me that you think `TransferOwnership(t)` is different from
`t->TransferOwnership(t)`.
I didn't say it was. I said that `t->TransferOwnership(t)` is different
from `t->TransferOwnership()`, which was what you first proposed. In one
case, you pass the owning object as a parameter. In the other case, you
snatch ownership via... well, means that are impossible in C++.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 14:51:43 UTC
Permalink
Post by Nicol Bolas
I didn't say it was. I said that `t->TransferOwnership(t)` is different
from `t->TransferOwnership()`, which was what you first proposed. In one
case, you pass the owning object as a parameter. In the other case, you
snatch ownership via... well, means that are impossible in C++.
Nicol, you have too much faith in goodness of the developer. Some of them
could be truly evil and code up the following:

T::TransferOwnership() const {

g_some_var = std::move(*const_cast<T*>(this));

}

Sorry, could not resist :-P
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 14:22:23 UTC
Permalink
Hi Tongari:

First, I want to say that I find your contributions to await design highly
valuable and as you may have noticed, since N4402 you are on the
acknowledgment list. I did notice your act and CO2 libraries and consider
them to be a valuable source of design insights. So, thank you for doing
this work.

*RAII on coroutine handle:*

Consider task objects with RAII semantics. They attempt to destroy / cancel
the coroutine from their destructor. I consider that an owning use.
When await gives coroutine handle to an async operation it is an observer
use.

In NT, for example, a thing is controlled by two reference counts. One is
for the lifetime, this is usually a handle count, how many owners the
object has, that determines if there is an interest in the object. As soon
as the handle count goes to zero, the object is going to be deleted.
However, it cannot be deleted immediately, since there could be multiple
observers (async I/O in flight), cancel will be initiated and only when
observer count goes to zero.

Neither shared_ptr semantic, nor unique_ptr semantics can capture this
behavior. But let's consider something even simpler that has no async
involved. Let's take the generator example. Look at
<experimental/resumable> or the one shown in N4402.

There is one coroutine_handle in the generator object. Another is in the
iterator. Now let's make coroutine_handle<> to have RAII semantics a la
unique_ptr.
An iterator now has to store a pointer to a coroutine_handle<> as opposed
to just coroutine_handle. Now every ++ and deref operation has to do an
extra dereference to get the coroutine state.

By not having any smart semantics included in coroutine_handle<>, means it
does not impose any overhead. Higher level semantics can be done in a safe
manner in abstractions build on top of it.

I am a strong believer that a low-level API should focus on efficiency, not
user-friendliness. I observed may cases over the years when attempts to
make it easier to use low-level APIs resulted in inefficiencies that could
have been avoided if low-level just focused on efficiency and high-level
C++ wrappers focused on exposing safe high-level abstractions.

*initial_suspend/final_suspend*

Remember a few month back we had a discussion that suspend actually
consists of two parts. PrepareForSuspend and Suspend.
In await expansion, it looks like this:

if (!await_ready()) {
PrepareForSuspend();
await_suspend()
Suspend();
}

If initial_suspend returns bool where do I put PrepareForSuspend?

Option 1:

if (initial_suspend()) {
PrepareForSuspend();
Suspend();
}

That does not work, for explanation see P0054.
Okay, how do we fix it:

Option 2:

PrepareForSuspend();
if (initial_suspend()) {
Suspend();
}

Good. Race is fixed, but now we are inefficient. PrepareForSuspend() is now
done whether we need to suspend or not, so we need some way to ask
initial_suspend are your intending to suspend or not before, so that we can
bypass PrepareForSuspend. Voila, we just reinvented what await does.

*unwind*

If we make return_value awaitable, it will behave as:

await $p.return_value(expr);

If $p.return_value(expr) does a direct call to the consumer (say activating
the parent coroutine, there is no difference whether return_value is
awaitable or not).
However, consider the case, that coroutine is suspended and post resumption
of the parent on a threadpool. Then, the consumer will get the value
directly from the temporary on a coroutine frame. Without the ability for
return_value to request a suspend point the second case would not be
possible.

<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>*auto
await alternatives*

At the moment I strongly believe that unobservable injection of suspend
points in the caller code will result in "expensive maintenance nightmare".
I showed how it can potentially be added in the future if need to.

The syntax proposed has been in use for a number of years in several
languages. It is loved by the users. And more and more languages are
getting the same syntax. Python (last May), EcmaScript 7, Dart sometime in
the last 12 month. I view it as an evidence that the syntax is sound.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 14:30:22 UTC
Permalink
clarified incomplete sentence:

... cancel will be initiated and only when observer count goes to zero
<added>*the object will be destroyed*.</added>
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-10 18:35:48 UTC
Permalink
Hi Gor,
Post by Gor Nishanov
First, I want to say that I find your contributions to await design highly
valuable and as you may have noticed, since N4402 you are on the
acknowledgment list. I did notice your act and CO2 libraries and consider
them to be a valuable source of design insights. So, thank you for doing
this work.
Yeah, I know I'll be a daily user of coroutine, so I just expressed the
concerns I have, with hope to have a better tool in future :)
Post by Gor Nishanov
*RAII on coroutine handle:*
Consider task objects with RAII semantics. They attempt to destroy /
cancel the coroutine from their destructor. I consider that an owning use.
When await gives coroutine handle to an async operation it is an observer
use.
In NT, for example, a thing is controlled by two reference counts. One is
for the lifetime, this is usually a handle count, how many owners the
object has, that determines if there is an interest in the object. As soon
as the handle count goes to zero, the object is going to be deleted.
However, it cannot be deleted immediately, since there could be multiple
observers (async I/O in flight), cancel will be initiated and only when
observer count goes to zero.
Neither shared_ptr semantic, nor unique_ptr semantics can capture this
behavior.
It sounds like exactly what shared_ptr does - a use-count & a weak-count.

Note that my proposed `coroutine` won't delete the coroutine-frame on
destruction, it only unwinds the stack (i.e. calling the local variables'
dtor), the entire coroutine-frame is still alive, meaning that the
observers can still access the frame(including the promise), they just
can't execute the coroutine anymore.
Post by Gor Nishanov
But let's consider something even simpler that has no async involved.
Let's take the generator example. Look at <experimental/resumable> or the
one shown in N4402.
There is one coroutine_handle in the generator object. Another is in the
iterator. Now let's make coroutine_handle<> to have RAII semantics a la
unique_ptr.
An iterator now has to store a pointer to a coroutine_handle<> as opposed
to just coroutine_handle. Now every ++ and deref operation has to do an
extra dereference to get the coroutine state.
True, that's exactly how I do it in my implementation.
Post by Gor Nishanov
By not having any smart semantics included in coroutine_handle<>, means it
does not impose any overhead. Higher level semantics can be done in a safe
manner in abstractions build on top of it.
I am a strong believer that a low-level API should focus on efficiency,
not user-friendliness. I observed may cases over the years when attempts to
make it easier to use low-level APIs resulted in inefficiencies that could
have been avoided if low-level just focused on efficiency and high-level
C++ wrappers focused on exposing safe high-level abstractions.
I think it's important to get the semantic right. Indeed, the raw-semantic
is more efficient in some cases, but not always. In other cases if you want
to have the correct semantic, it tends to be harder and you'll end up with
a less efficient design.
Post by Gor Nishanov
*initial_suspend/final_suspend*
Remember a few month back we had a discussion that suspend actually
consists of two parts. PrepareForSuspend and Suspend.
if (!await_ready()) {
PrepareForSuspend();
await_suspend()
Suspend();
}
If initial_suspend returns bool where do I put PrepareForSuspend?
if (initial_suspend()) {
PrepareForSuspend();
Suspend();
}
That does not work, for explanation see P0054.
PrepareForSuspend();
if (initial_suspend()) {
Suspend();
}
Good. Race is fixed, but now we are inefficient. PrepareForSuspend() is
now done whether we need to suspend or not, so we need some way to ask
initial_suspend are your intending to suspend or not before, so that we can
bypass PrepareForSuspend. Voila, we just reinvented what await does.
Admittedly I'm not sure about initial_suspend, but what's the difference in
the final_suspend example?
In my understanding, the order of `PrepareForSuspend` matters only if
`await_suspend` tends to execute the continuation before it returns, but in
the final_suspend example, it's not the case.

Note that I'm not against the idea to return Awaitable, I just don't
understand the final_suspend example you gave.
Post by Gor Nishanov
*unwind*
await $p.return_value(expr);
If $p.return_value(expr) does a direct call to the consumer (say
activating the parent coroutine, there is no difference whether
return_value is awaitable or not).
However, consider the case, that coroutine is suspended and post
resumption of the parent on a threadpool. Then, the consumer will get the
value directly from the temporary on a coroutine frame. Without the ability
for return_value to request a suspend point the second case would not be
possible.
I'm not sure I understand, you now have to store the value in the
awaitable, what's the benefit?

<https://gist.github.com/jamboree/611b8c6551f853956f7d#auto-await-alternatives>*auto
Post by Gor Nishanov
await alternatives*
At the moment I strongly believe that unobservable injection of suspend
points in the caller code will result in "expensive maintenance nightmare".
I showed how it can potentially be added in the future if need to.
The syntax proposed has been in use for a number of years in several
languages. It is loved by the users. And more and more languages are
getting the same syntax. Python (last May), EcmaScript 7, Dart sometime in
the last 12 month. I view it as an evidence that the syntax is sound.
Not that I don't like the syntax, I just want to explore a way to 1) not
having await & yield as keywords. 2) make them as the building-blocks for
libraries, hiding them from direct usage.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 20:37:01 UTC
Permalink
Hi Tongari:

Low level APIs need to worry about efficiency, high level APIs need to
worry about beauty of abstraction and safety while using low level APIs to
do it efficiently.
It is possible to give coroutine_handle a unique_ptr like semantic, but,
now we pessimized performance in at least three areas:

1) mandatory extra dereference in coroutine observers
2) ownership transfer. Now there is always a = nullptr whenever I move it
around
3) now there must be a mandatory branch in the destructor if (_Ptr)
coro.destroy()

Moreover, we gave the higher-level semantics to the coroutine_handle. If
that does not match what the library wants to do, it now needs to add code
to suppress that behavior, (for example if it does not want ownership
semantic, it should zero out the handle before the destruction).

coroutine_handle is not for the end user. It is a sharp tool for the
library writer. Moreover it is a compiler provided tool. If I don't like
the library, I can write my own. If I don't like what compiler gives me, I
am not going to write a new compiler.

Currently, it is a raw pointer. You cannot get any more efficient than
that. It is fully devoid of higher level semantics. As a library writer you
decide what it means. You decide what trade-offs to do and what efficiency
you want to sacrifice for ease of use. I as a compiler don't want to make
any trade-offs. I give pretty syntactic sugar to the end-user and a bunch
of sharp knives to the library developer.

I am open to any suggestions how to make the library adaptation layers more
library writer friendly as long as I don't have to sacrifice performance in
space or time.

*On keywords:*

It is not about keywords, correct? P0071 offers a magic std::await and
std::yield functions as an alternative.
Your concern is with design point that P0054 requires that every suspend
point in a function needs to be explicitly marked whether with a keyword or
magic function.

I have no objections to exploring whether in the future we may get
"automatically awaited awaitables" or async functions as you suggested in
your proposal on the github. Sure thing. Go right ahead. Should we wait and
not standardize coroutines while this exploration is going on?

There reason I added the section about it in P0054 was to show that such
exploration is possible in the future. It should not stop progress on this
proposal.

*On awaitable return_value*

I store a reference to the expression (lvalue or temporary on the coroutine
frame) and give a chance to consumer, potentially running on a different
thread to observe it before semicolon at the end of full expression
destroys the value (or leaving the scope and stopping at final suspend
destroys the lvalue that I was trying to return)

*On final_suspend*

I am afraid I cannot answer that without pasting here what I've written in
P0054. If one of the steps described in P0054 is unclear. Ask me, why Y
follows from X. Otherwise, I am not sure how I can help.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-11 05:48:47 UTC
Permalink
Hi Gor,
Post by Gor Nishanov
Low level APIs need to worry about efficiency, high level APIs need to
worry about beauty of abstraction and safety while using low level APIs to
do it efficiently.
It is possible to give coroutine_handle a unique_ptr like semantic, but,
1) mandatory extra dereference in coroutine observers
2) ownership transfer. Now there is always a = nullptr whenever I move it
around
3) now there must be a mandatory branch in the destructor if (_Ptr)
coro.destroy()
All true.
Post by Gor Nishanov
Moreover, we gave the higher-level semantics to the coroutine_handle. If
that does not match what the library wants to do, it now needs to add code
to suppress that behavior, (for example if it does not want ownership
semantic, it should zero out the handle before the destruction).
coroutine_handle is not for the end user. It is a sharp tool for the
library writer. Moreover it is a compiler provided tool. If I don't like
the library, I can write my own. If I don't like what compiler gives me, I
am not going to write a new compiler.
Currently, it is a raw pointer. You cannot get any more efficient than
that. It is fully devoid of higher level semantics. As a library writer you
decide what it means. You decide what trade-offs to do and what efficiency
you want to sacrifice for ease of use. I as a compiler don't want to make
any trade-offs. I give pretty syntactic sugar to the end-user and a bunch
of sharp knives to the library developer.
I am open to any suggestions how to make the library adaptation layers
more library writer friendly as long as I don't have to sacrifice
performance in space or time.
Bear in mind that the coroutine-promises and the awaitables could be
written by different people in different libraries, there must exist a
protocol for them to collaborate safely without knowing each other.
Something like "generator" is really a special case, where the promise also
controls the awaitable (i.e. yield_value). For other async-result, we need
the safe semantic anyway.

Consider my earlier example:

```c++
std::future<void> f()
{
Resource res(...);
await std::suspend_always{};
}
```

Do you really think that it's better to have f() leak memory & resource by
default? I don't think so.
And that's not a contrived example, I do do things like that for
cascading-cancellation of tasks in dependency-graph.
With RAII, the cancellation in one node propagates to its dependants
automatically, without exceptions.
Without RAII, to provide the similar behavior, you have to throw exception
that will be catch by its dependants and then throw it again and catch that
in the dependants-of-dependants and so on...which is less performant.
Post by Gor Nishanov
*On keywords:*
It is not about keywords, correct? P0071 offers a magic std::await and
std::yield functions as an alternative.
Your concern is with design point that P0054 requires that every suspend
point in a function needs to be explicitly marked whether with a keyword or
magic function.
I have no objections to exploring whether in the future we may get
"automatically awaited awaitables" or async functions as you suggested in
your proposal on the github. Sure thing. Go right ahead. Should we wait and
not standardize coroutines while this exploration is going on?
There reason I added the section about it in P0054 was to show that such
exploration is possible in the future. It should not stop progress on this
proposal.
What if the future exploration is not compatible with the current design?
that's my concern.

*On awaitable return_value*
Post by Gor Nishanov
I store a reference to the expression (lvalue or temporary on the
coroutine frame) and give a chance to consumer, potentially running on a
different thread to observe it before semicolon at the end of full
expression destroys the value (or leaving the scope and stopping at final
suspend destroys the lvalue that I was trying to return)
I kinda get the idea, but how can the consumer notify the promise that it
takes the value?

```c++
auto val = await future;
```

How can the future know the value is now assigned to val so the coroutine
can go on and destroy?
Are you relying on copy-ellision here and the await_resume must return the
value and not a rv-ref to it?
Post by Gor Nishanov
*On final_suspend*
I am afraid I cannot answer that without pasting here what I've written in
P0054. If one of the steps described in P0054 is unclear. Ask me, why Y
follows from X. Otherwise, I am not sure how I can help.
It was pasted in my reply to Nicol Bolas, here it is again:

What is the difference between this:
```c++
auto final_suspend()
{
struct awaiter
{
promise_type * me;
bool await_ready() { return false; }
void await_resume() {}
bool await_suspend(coroutine_handle<>) {
auto need_suspending = (me->decrement_refcount() > 0);
return need_suspending;
}
};
return awaiter{this};
}
```
and this:
```c++
bool final_suspend()
{
auto need_suspending = (decrement_refcount() > 0);
return need_suspending;
}
```

? In my understanding, it only matters if `await_suspend` tends to execute
the continuation before it returns, but in the example, it's not the case,
I don't see why making the coroutine resumable matters before calling
`await_suspend` here.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 13:17:41 UTC
Permalink
Hi Tongari:

I do not understand how decision whether coroutine_handle<> has RAII
semantic or not has any bearing on the example you shown.
The behavior of

std::future<void> f() {

Resource res(...);

await std::suspend_always{};

}

depends solely on what semantics of the future and suspend_always is.

If future returned here has RAII semantics, like the future produced by
std::async, then we will have deadlock as the future destructor will block
forever in its destructor. If future has "leak async" semantic like the
future returned from promise.get_future(), it will result in a leak. In
C++17 or later, I would like to have a better vocabulary type that supports
cancellation, let's call it STLv2 future, in that case, it will cancel the
coroutine in its destructor, thus avoiding the leak.

suspend_always have the semantics implied by its name. Unconditional
suspend at this point. If customer requested this, coroutine will suspend.
Period. If you disagree with semantics of suspend_always or would like it
to have more obscure name that is fine. Also, I consider suspend_always to
be a library class. It is not special in any way form compiler perspective.
I included it in the proposal, because it is something that I felt people
will be writing themselves anyways, so putting it in the standard library
will allow to avoid the repetition and give some common vocabulary name.
(It may be not the best name, I am open to suggestions. Earlier it was
suspend_now).

In both cases, we discuss what semantics a library writer wants to impart
to the future and suspend_always. That behavior is what library writer
designs. How coroutine_handle behaves has no bearing on that decision.

*On future evolution*

There reason I added the section about it in P0054 was to show that such
exploration is possible in the future. It should not stop progress on this
proposal.
Post by TONGARI J
What if the future exploration is not compatible with the current design?
that's my concern.
I think both the "automatically awaited awaitables" idea of P0054 and your
idea about async mofified on a function are compatible and can be built on
top of P0057.

*On awaitable return_value*
Post by TONGARI J
Post by Gor Nishanov
I store a reference to the expression (lvalue or temporary on the
coroutine frame) and give a chance to consumer, potentially running on a
different thread to observe it before semicolon at the end of full
expression destroys the value (or leaving the scope and stopping at final
suspend destroys the lvalue that I was trying to return)
I kinda get the idea, but how can the consumer notify the promise that it
takes the value?
Consumer will resume the coroutine when it is consumed the value.

*On final_suspend*
Post by TONGARI J
Post by Gor Nishanov
```c++
auto final_suspend()
{
struct awaiter
{
promise_type * me;
bool await_ready() { return false; }
void await_resume() {}
bool await_suspend(coroutine_handle<>) {
auto need_suspending = (me->decrement_refcount() > 0);
return need_suspending;
}
};
return awaiter{this};
}
```
```c++
bool final_suspend()
{
auto need_suspending = (decrement_refcount() > 0); ((<-- HERE))
return need_suspending;
}
```
Just like with initial_suspend, you need to think where PrepareForSuspend
goes relative to a call to final_suspend.
Note, that as soon as you do (decrement_refcount() > 0), a racing thread
that has an owning handle to a coroutine will decrement the refcount,
observe that it is zero now and destroys the coroutine while the current
thread is still at the semicolon marked with HERE tag in the example above.

Thus, just like with initial_suspend we explore the cases:

Option 1: (Busted, due to the race described above and in P0054)

if (final_suspend()) {
PrepareForSuspend();
DoSuspend();
}

Option 2: (Works but suboptimal, since now we do prepare for suspend even
when we never suspend)

PrepareForSuspend();
if (final_suspend()) {
DoSuspend();
}

Option 3:

if (is_final_suspend_means_to_suspend()) {
PrepareForSuspend();
if (final_suspend()) {
DoSuspend();
}
}

or in a simpler form

await final_suspend()
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-11 13:28:21 UTC
Permalink
Quick typo fixes:

... your idea about async *mofified* on a function (though I really like
the word mofified, I meant *modifier).*

And also I want to inject myself into your subthread with Nicol and repeat
what I stated before.

Semantic of the examples you have shown to Nicol, should be based on what
is the designed behavior for those primitives.
You can achieve the designed behavior irrespectively of whether
coroutine_handle behaves like prescribed by P0057 or has the semantics you
desire.
coroutine_handle is a low level facility to build higher level constructs
from it.

You design what behavior you want high-level construct to have.
coroutine_handle is there so that your design can be implemented in most
efficient way
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-11 16:15:53 UTC
Permalink
Hi Gor,
Post by Gor Nishanov
I do not understand how decision whether coroutine_handle<> has RAII
semantic or not has any bearing on the example you shown.
The behavior of
std::future<void> f() {
Resource res(...);
await std::suspend_always{};
}
depends solely on what semantics of the future and suspend_always is.
If future returned here has RAII semantics, like the future produced by
std::async, then we will have deadlock as the future destructor will block
forever in its destructor. If future has "leak async" semantic like the
future returned from promise.get_future(), it will result in a leak. In
C++17 or later, I would like to have a better vocabulary type that supports
cancellation, let's call it STLv2 future, in that case, it will cancel the
coroutine in its destructor, thus avoiding the leak.
suspend_always have the semantics implied by its name. Unconditional
suspend at this point. If customer requested this, coroutine will suspend.
Period. If you disagree with semantics of suspend_always or would like it
to have more obscure name that is fine. Also, I consider suspend_always to
be a library class. It is not special in any way form compiler perspective.
I included it in the proposal, because it is something that I felt people
will be writing themselves anyways, so putting it in the standard library
will allow to avoid the repetition and give some common vocabulary name.
(It may be not the best name, I am open to suggestions. Earlier it was
suspend_now).
I have no problem with suspend_always, I like it and I use it. What I was
trying to say is that when it is used with `std::future`, given the current
raw-semantic, the behavior is dangerous, the memory & resource leak, and if
you wait on it, it hangs. But given unique-semantic and ownership-transfer,
the coroutine's dtor will unwind the stack, throw an exception if you wait
on it (i.e. broken promise), which is a much safer behavior IMO.
Post by Gor Nishanov
In both cases, we discuss what semantics a library writer wants to impart
to the future and suspend_always. That behavior is what library writer
designs. How coroutine_handle behaves has no bearing on that decision.
The author of coroutine-promise has no knowledge about what the user will
await in the function, how could he ensure that something like `await
std::suspend_always{}` would never happen, or how could he make it safe if
that happens?

And the author of the awaitable has no knowledge about inside what
coroutine will it be used, how could he ensure that the coroutine-promise
will handle it in in a safe manner? for example, std::suspend_always just
suspends the coroutine, but how could it tell the coroutine-promise that it
should cancel the coroutine afterwards?

For them to collaborate safely, there must be a protocol in-between, and
that is ownership-transfer.
Post by Gor Nishanov
*On future evolution*
There reason I added the section about it in P0054 was to show that such
exploration is possible in the future. It should not stop progress on this
proposal.
Post by TONGARI J
What if the future exploration is not compatible with the current design?
that's my concern.
I think both the "automatically awaited awaitables" idea of P0054 and your
idea about async mofified on a function are compatible and can be built on
top of P0057.
Not quite, my idea requires a manual "async" mark before the function body,
to distinguish the normal context and resumable context for context-based
overloading. Your design doesn't require that (which in itself is a good
thing), so they're incompatible in some aspects.
Post by Gor Nishanov
*On awaitable return_value*
Post by TONGARI J
Post by Gor Nishanov
I store a reference to the expression (lvalue or temporary on the
coroutine frame) and give a chance to consumer, potentially running on a
different thread to observe it before semicolon at the end of full
expression destroys the value (or leaving the scope and stopping at final
suspend destroys the lvalue that I was trying to return)
I kinda get the idea, but how can the consumer notify the promise that it
takes the value?
Consumer will resume the coroutine when it is consumed the value.
Does it look like this?
```c++
T await_resume()
{
T ret(get_rv_ref_frome_the_promise());
resume_the_coroutine();
return ret;
}
```

So my guess that this trick relies on RVO and copy-ellision and cannot
return a T&& is correct, right?
Post by Gor Nishanov
*On final_suspend*
Post by TONGARI J
Post by Gor Nishanov
```c++
auto final_suspend()
{
struct awaiter
{
promise_type * me;
bool await_ready() { return false; }
void await_resume() {}
bool await_suspend(coroutine_handle<>) {
auto need_suspending = (me->decrement_refcount() > 0);
return need_suspending;
}
};
return awaiter{this};
}
```
```c++
bool final_suspend()
{
auto need_suspending = (decrement_refcount() > 0); ((<-- HERE))
return need_suspending;
}
```
Just like with initial_suspend, you need to think where PrepareForSuspend
goes relative to a call to final_suspend.
Note, that as soon as you do (decrement_refcount() > 0), a racing thread
that has an owning handle to a coroutine will decrement the refcount,
observe that it is zero now and destroys the coroutine while the current
thread is still at the semicolon marked with HERE tag in the example above.
So the problem is the race condition in PrepareForSuspend() while other
thread is destroying the coroutine?
Makes sense then, sorry I didn't see it because in my implementation the
PrepareForSuspend() for final_suspend is just a noop.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-10-11 13:39:20 UTC
Permalink
Post by TONGARI J
Hi Gor,
Post by Gor Nishanov
Moreover, we gave the higher-level semantics to the coroutine_handle. If
that does not match what the library wants to do, it now needs to add code
to suppress that behavior, (for example if it does not want ownership
semantic, it should zero out the handle before the destruction).
coroutine_handle is not for the end user. It is a sharp tool for the
library writer. Moreover it is a compiler provided tool. If I don't like
the library, I can write my own. If I don't like what compiler gives me, I
am not going to write a new compiler.
Currently, it is a raw pointer. You cannot get any more efficient than
that. It is fully devoid of higher level semantics. As a library writer you
decide what it means. You decide what trade-offs to do and what efficiency
you want to sacrifice for ease of use. I as a compiler don't want to make
any trade-offs. I give pretty syntactic sugar to the end-user and a bunch
of sharp knives to the library developer.
I am open to any suggestions how to make the library adaptation layers
more library writer friendly as long as I don't have to sacrifice
performance in space or time.
Bear in mind that the coroutine-promises and the awaitables could be
written by different people in different libraries, there must exist a
protocol for them to collaborate safely without knowing each other.
Something like "generator" is really a special case, where the promise also
controls the awaitable (i.e. yield_value). For other async-result, we need
the safe semantic anyway.
```c++
std::future<void> f()
{
Resource res(...);
await std::suspend_always{};
}
```
Do you really think that it's better to have f() leak memory & resource by
default? I don't think so.
I'm not exactly sure how this constitutes a memory leak.

If coroutine_traits uses the default case for `f`, then it will attempt to
create `future<void>::promise_type` for the coroutine promise. So either
`future` has been updated to have such a promise, or it has not.

If it has not, then this is a compiler error. If it has, then it's up to
the promise type to ultimately destroy the coroutine_handle. And therefore,
there would only be a memory leak if `future<void>::promise_type` was
*broken*.

The analogy Gor and I have been using with pointers is apt. A pointer has
no ownership semantics; you have to manually manage memory for them. But a
smart pointer does; that's why we use them. `coroutine_handle` has no
ownership semantics. But the `promise_type` *does*.

Just as RAII lives in `unique_ptr<T>` rather than T*, RAII lives at the
level of the promise rather than `coroutine_handle`.

And that's not a contrived example, I do do things like that for
Post by TONGARI J
cascading-cancellation of tasks in dependency-graph.
With RAII, the cancellation in one node propagates to its dependants
automatically, without exceptions.
Without RAII, to provide the similar behavior, you have to throw exception
that will be catch by its dependants and then throw it again and catch that
in the dependants-of-dependants and so on...which is less performant.
Post by Gor Nishanov
*On keywords:*
It is not about keywords, correct? P0071 offers a magic std::await and
std::yield functions as an alternative.
Your concern is with design point that P0054 requires that every suspend
point in a function needs to be explicitly marked whether with a keyword or
magic function.
I have no objections to exploring whether in the future we may get
"automatically awaited awaitables" or async functions as you suggested in
your proposal on the github. Sure thing. Go right ahead. Should we wait and
not standardize coroutines while this exploration is going on?
There reason I added the section about it in P0054 was to show that such
exploration is possible in the future. It should not stop progress on this
proposal.
What if the future exploration is not compatible with the current design?
that's my concern.
And what if future exploration of adding template implementation checks to
concepts is not compatible with the current function-based design of the
concepts TS? That certainly didn't stop them from writing that up.

We should not arrest important developments because we're afraid that some
functionality *might* not work out in the future (especially if its
functionality we aren't sure we necessarily want). We should certainly look
at such possibilities, but so long as they seem feasible to implement, we
don't need to make absolutely sure before moving forward.

I understand your concern here, but vague "what ifs" should not be used to
derail a proposal. We need coroutines in the language, ASAP. And this
proposal is a mature, well-founded concept (based on similar behavior in
many other languages) with solid implementation and optimization experience.

If you want to argue that the current design is not compatible with
implicit awaiting, then I feel the burden of proof is on you. You should
provide evidence of that, not vague concerns about what may or may not
happen.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-10 20:46:46 UTC
Permalink
Missed one question.
Post by Gor Nishanov
In NT, for example, a thing is controlled by two reference counts. One is
Post by Gor Nishanov
for the lifetime, this is usually a handle count, how many owners the
object has, that determines if there is an interest in the object. As soon
as the handle count goes to zero, the object is going to be deleted.
However, it cannot be deleted immediately, since there could be multiple
observers (async I/O in flight), cancel will be initiated and only when
observer count goes to zero.
Neither shared_ptr semantic, nor unique_ptr semantics can capture this
behavior.
It sounds like exactly what shared_ptr does - a use-count & a weak-count.
It may sound similar, but, it is not. Usage protocol is different. With
shared_ptr/weak_ptr, there is an agreement that I am not going to touch
your memory before I successfully lock the weak_ptr. Network card, on the
other hand, is not going lock weak_ptr before blasting the memory with data.

If there is an outstanding observer that can touch the memory of the
object, I cannot let the memory go away. The observer could be a different
machine using RDMA to access the memory of my object, for example.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-12 13:25:51 UTC
Permalink
Hi Gor,

Is there noexcept requirement on initial_suspend/final_suspend?
{
P p;
await-keyword p.initial_suspend(); // initial suspend point
F’
await-keyword p.final_suspend(); // final suspend point
}
where local variable p is defined for exposition only and F’ is F if P
does not define a set_-
exception member function, and
try { F } catch(...) { p .set_exception(std::current_exception()); }
otherwise.
And there are 2 ways to call the coroutine: 1) `operator()` and 2)
`resume`, where the former is noexcept and the latter can throw.

In the above definition, `initial_suspend` is not surrounded in the
try-block, does that mean if it throws, when called by `operator()` the
program will terminate?
and when called by `resume`, will it bypass final_suspend? will it reclaim
the coroutine-frame?

final_suspend is similar, but I believe it should be noexcept. If it
throws, when called by `operator()`, the program will terminate anyway; when
called by `resume`, you can't decide whether to suspend it or not.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Gor Nishanov
2015-10-12 13:56:26 UTC
Permalink
Hi Tongari:

And there are 2 ways to call the coroutine: 1) `operator()` and 2)
Post by TONGARI J
`resume`, where the former is noexcept and the latter can throw.
That is a defect. They should have identical signatures and they both
should not be noexcept. Thank you for reporting this. I will fix the
wording.

In the above definition, `initial_suspend` is not surrounded in the
Post by TONGARI J
try-block, does that mean if it throws, when called by `operator()` the
program will terminate?
If the initial suspend throws, we are still trying to create a coroutine
and it is treated similarly if promise constructor throws. Essentially the
caller called a function and it threw into its face.

and when called by `resume`, will it bypass final_suspend? will it reclaim
Post by TONGARI J
the coroutine-frame?
For final_suspend, we can can go one of two ways:
1) throwing from final_suspend or any of the await_xxx from its awaitable
is undefined behavior
2) or say that they must be marked noexcept

I will defer for LEWG/Core guidance on this matter.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
TONGARI J
2015-10-12 14:20:57 UTC
Permalink
Hi Gor,
Post by TONGARI J
And there are 2 ways to call the coroutine: 1) `operator()` and 2)
Post by TONGARI J
`resume`, where the former is noexcept and the latter can throw.
That is a defect. They should have identical signatures and they both
should not be noexcept. Thank you for reporting this. I will fix the
wording.
Actually P0057R0 declares both of them without noexcept, I was refering
to <experimental/resumable> shipped with MSVC where operator() is noexcept
and resume is not, which makes sense to me. I think operator() should be
noexcept so it was a doc mistake in P0057R0, but now you're saying they
both should not be noexcept? then why 2 names for the same thing?
Post by TONGARI J
In the above definition, `initial_suspend` is not surrounded in the
Post by TONGARI J
try-block, does that mean if it throws, when called by `operator()` the
program will terminate?
If the initial suspend throws, we are still trying to create a coroutine
and it is treated similarly if promise constructor throws. Essentially the
caller called a function and it threw into its face.
and when called by `resume`, will it bypass final_suspend? will it
Post by TONGARI J
reclaim the coroutine-frame?
The above questions are not resolved. Any hint?
Post by TONGARI J
1) throwing from final_suspend or any of the await_xxx from its awaitable
is undefined behavior
2) or say that they must be marked noexcept
I will defer for LEWG/Core guidance on this matter.
1) Why not a defined behavior that calls std::terminate?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Loading...