Second Draft: Coroutines

Sebastiaan Koppe mail at skoppe.eu
Sat Jan 25 10:38:00 UTC 2025


On Friday, 24 January 2025 at 23:22:19 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
> On 25/01/2025 9:56 AM, Sebastiaan Koppe wrote:
>> On Thursday, 23 January 2025 at 23:09:42 UTC, Richard (Rikki) 
>> Andrew Cattermole wrote:
>>>
>>> On 24/01/2025 10:17 AM, Sebastiaan Koppe wrote:
>>>> On Thursday, 23 January 2025 at 20:37:59 UTC, Richard 
>>>> (Rikki) Andrew Cattermole wrote:
>>>> You don't need to describe how scheduling works, just the 
>>>> mechanism by which a scheduler gets notified when a 
>>>> coroutine is ready for resumption.
>>>>
>>>> Rust has a Waker, C++ has the await_suspend function, etc.
>>>
>>> Are you wanting this snippet?
>> 
>> No, not specifically. I am requesting the DIP to clarify the 
>> mechanism by which a scheduler is notified when a coroutine is 
>> ready for resumption, not the specific scheduling itself.
>
> If it doesn't work for you, do it a different way. The language 
> has no inbuilt knowledge of any of these types. It determines 
> everything that it needs from the operator overload and the 
> core.attributes attributes.

Well, then the DIP needs to be more explicit that the compiler is 
merely doing the code transformation, that the created a 
coroutine frame needs to be driven completely by library code, 
and that the types that are awaited on are opaque to the compiler 
and simply passed along to library code.

>> It seems that in your design coroutines are only able to wait 
>> for other coroutines. This means you need to model async 
>> operations as coroutines in order to suspend on them.
>
> It should be coroutines, but I left out the filtering for the 
> type that the ``await`` statement will accept. It'll chuck 
> whatever you want into the sumtype value. Its your job to 
> filter it. If you want to support other types and behaviors go 
> for it!

The name `GenericCoroutine` suggests there is type erasure, but 
if the library driving the coroutine can work with the direct 
types that are awaited on, that would work.

As an optimisation possibility it would be good if the coroutine 
frame could have some storage space for async operations, which 
would allow us to eliminate some heap allocations. The easiest 
way to support that is by having the compiler call a predefined 
function on the object in the await expression (say 
`getAwaiter`), whose returned object would be stored in the 
coroutine frame. This offers quite a bit of flexibility for 
library authors without putting any burden on the user.

In the Fiber support in my Sender/Receiver library there is only 
one single allocation per yield point. Would be good if we can 
get at least as few allocations.

> This is the C# approach.
>
> https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1298-await-expressions

 From that link:

"The operand of an await_expression is called the task. It 
represents an asynchronous operation that may or may not be 
complete at the time the await_expression is evaluated. The 
purpose of the await operator is to suspend execution of the 
enclosing async function until the awaited task is complete, and 
then obtain its outcome."

Note that `task` is a way better name than `Future`.

And:

"The task of an await_expression is required to be awaitable.
An expression t is awaitable if one of the following holds:
[...]
- t has an accessible instance or extension method called 
GetAwaiter
[...]
The purpose of the GetAwaiter method is to obtain an awaiter for 
the task.
[...]
The purpose of the INotifyCompletion.OnCompleted method is to 
sign up a “continuation” to the task; i.e., a delegate (of type 
System.Action) that will be invoked once the task is complete."

You see? It defines the mechanism by which to resume an awaitable.

> In saying all that, I find the dependency approach to be very 
> intuitive, and I was able to implement it purely off of first 
> principles. Whereas the other approaches including C++ is still 
> after much reading not in my mental model.

It is hard for me to see if there are any shortcomings at this 
point.
Is there an dmd implemention I could try to integrate with?

> > For one it allows the object you are awaiting
> > on to control the continuation directly.
>
> If you want that for your library to look at the ``waitingOn`` 
> variable for control over scheduling, go for it! Nothing in the 
> DIP currently should stop you from doing that.
>
> [...]
>
> The language defines none of this _on purpose_.

As mentioned above, this needs to be made more clear in the DIP.

One possible challenge with this flexibility is whether it isn't 
too flexible. It is not uncommon to have multiple eventloops in a 
program, potentionally coming from distinct libraries. Without a 
common mechanism to resume awaitables from each it might result 
in incompatibility galore.


More information about the dip.development mailing list