Criticism of resumable functions proposal (N3858)

839 views
Skip to first unread message

Maciej Gajewski

unread,
Jan 24, 2014, 5:30:10 AM1/24/14
to std-pr...@isocpp.org

Hi everyone


I would love to see resumable functions/coroutines introduced into C++ standard. It would make my life, and lives of many other software developers, easier. It would solve many problems, encountered in many kinds of projects.


Unfortunately, I find the resumable/await proposal (N3858) flawed. I’ll describe the flaws below, and in another message I will present an alternative idea.


Problems with the resumable functions/await:


1. It does not solve real-life problems


As a programmer, I would like to use resumable functions to:


a. Implement generators,

b. Convert my multi-threaded, IO-heavy application into a single-threaded one, preserving the logical flow of functions, but using asynchronous IO,

c. Re-write my single-threaded applications using asynchronous IO (for instance boost.asio) to use coroutines, so that the performance gain from async io is preserved, while the code is cleaner, easier to read and maintain.


resumable/await would not help me with any of it.  Writing generators is awkward (even authors of the proposal admit it chapter 5.2). As for asynchronous IO: there is no concrete example how to integrate resumable/await with any existing async IO solutions; instead, the proposal vaguely mentions a “scheduling logic inside the runtime”.


2. It’s high level solution


Resumable functions are high-level mechanisms implemented as language features. All the features provided by the solution could be implemented as a library instead, given the appropriate low-level primitives are available.


3. ‘await’ can only be used in resumable function.


The ‘await’ operation can only be used at the level of resumable function. In other words: the decision to suspend execution has to be taken in the function’s body, it can’t be delegated to another function.

I realise that this is by design, to allow for rewriting of the function body by the compiler, but this makes integration with existing code impossible.


Consider the following code:


void handle_http_request(std::istream& input, std::ostream& output)
{
    Poco::Net::HTTPRequest request;
    request
.read(input);
    // ... read request body here
    // ... produce and write response here
}


This code could be either synchronous (using blocking IO) or asynchronous (using coroutines). The iostreams provide sufficient abstraction.


In the asynchronous case, the decision to suspend execution can only be made several levels below: below Poco’s HTTP, below std::iostream, below std::streambuf, at the point when the actual IO operation is performed.


4. std::future is required to suspend execution


future/promise may be nice, high-level idioms for asynchronous operations, but making them a requirement makes some use cases awkward or even impossible to implement. Generators are difficult to implement, and existing code would have to be wrapped or re-written.


5. Resumable functions are ‘special’


They have to be decorated, they have certain restrictions put on them, exceptions use is limited. Normal functions, functors or lambdas can’t be used as a resumable functions, just as they can be used as thread routines.


6. Many questions remain unanswered


We, C++ programmers like to know what’s going on under the covers. And the resumable functions proposal leaves too many questions unanswered:


a. What is the lifetime of objects created on stack in resumable functions?

b. When are the resources required to create such a function (side stack) released?

c. How are these functions resumed?

d. If get() is called on a future returned by resumable function, how will it block? What device will be used to block and how can it be controlled from the outside?






Evgeny Panasyuk

unread,
Jan 28, 2014, 4:50:14 PM1/28/14
to std-pr...@isocpp.org
24 January 2014, 14:30:10 UTC+4 Maciej Gajewski :

Unfortunately, I find the resumable/await proposal (N3858) flawed. I’ll describe the flaws below, and in another message I will present an alternative idea.



I share most of your concerns.

It seems that limitations ["3. ‘await’ can only be used in resumable function."] and ["5. Resumable functions are ‘special’"] came from leaving possibility for implementor to build them based on stackless coroutines. I.e. "Implementation#2: Heap - allocated activation frames".

Stackless Coroutine basically transforms function with yield/await into object finite-state machine, where function's local variables are move into object fields.
In general, stackless coroutines (not N3858) have some advantages over stackfull. For possible implementation refer http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/overview/core/coroutine.html .
For instance:
1) Size of required storage for object is known in advance.
2) Fast generators. As function transformed into object with no heap allocations, with no context switches on yield, with good opportunity for inlining - I presume that small generators will be faster than alternative based on stackful coroutines.
3) As generated finite-state machine is just ordinary C++ object, it can be copied or moved. For instance it is possible to copy generator in order to save state or "fork" (like in stackless coroutines of Boost.Asio). This is also required for implementation of something like List Monad.

But, first of all - there is no ready implementation of such approach. As far as I understand, VS2013 __resumable/__await are implemented based on stackful coroutines (this is what I understood from http://channel9.msdn.com/Events/GoingNative/2013/Bringing-await-to-Cpp ).
Second, N3858 does not expose API required to take advantages of stackless-ness - for instance you can't copy or move state (correct me if I wrong).
So, if there is no way to take advantages of stackless-ness and there is no proof-of-concept implementation - why bring its disadvantages?

Stackful coroutines can be implemented in library form, just like std::thread. There is no need to introduce new special kind of functions. There is no need to limit await to "one level".
There is ready library implementation of stackful coroutines - Boost.Coroutine http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/index.html. It works without changes to language, it can yield from arbitrary levels, it allows to implement both generators and await.
There is proof-of-concept implementation of exact await syntax based on Boost.Coroutine: https://github.com/panaseleus/await_emu
int bar(int i)
{
   
// await is not limited by "one level" as in C#
   
auto result = await async([i]{ return reschedule(), i*100; });
   
return result + i*10;
}

int foo(int i)
{
   
cout << i << ":\tbegin" << endl;
   
cout << await async([i]{ return reschedule(), i*10; }) << ":\tbody" << endl;
   
cout << bar(i) << ":\tend" << endl;
   
return i*1000;
}

void async_user_handler()
{
   
vector<future<int>> fs;

   
// instead of `async` at function signature, `asynchronous` should be
   
// used at the call place:
   
for(auto i=0; i!=5; ++i)
       
fs.push_back( asynchronous([i]{ return foo(i+1); }) );

   
for(auto &&f : fs)
       
cout << await f << ":\tafter end" << endl;
}

P.S. Stackful and stackless coroutines have some overlap in use cases, both have unique advantages and disadvantages. N3858 tries to allow both to be used as underlying implementation, killing all unique advantages and bringing all unique disadvantages of stackless and stackfull coroutines.

Bjorn Reese

unread,
Jan 31, 2014, 4:42:24 AM1/31/14
to std-pr...@isocpp.org
On 01/28/2014 10:50 PM, Evgeny Panasyuk wrote:

> Stackful coroutines can be implemented in library form, just like
> std::thread. There is no need to introduce new special kind of
> functions. There is no need to limit await to "one level".
> There is ready library implementation of stackful coroutines -
> Boost.Coroutine
> http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html/index.html.

See also N3708 "A proposal to add coroutines to the C++ standard
library"

http://isocpp.org/files/papers/n3708.pdf
Reply all
Reply to author
Forward
0 new messages