Home > Software design >  Is there a C standard-compliant way to obtain a reference to a known base class of a coroutine pro
Is there a C standard-compliant way to obtain a reference to a known base class of a coroutine pro

Time:12-26

In a couple of places in my C coroutine library I need to walk a chain of suspended coroutines that are waiting on each other.

For example, assume that Foo0 calls Foo1, which calls Foo2, which… calls FooN, each co_awaits the result of the next, and FooN is currently suspended. Their promise types store the handle of the awaiter to resume when they're done, so they form a singly-linked list from FooN to Foo0. I want to be able to iterate over the list, e.g. to clean up after cancellation or to give a nice async stack trace for profiling.

If all of the coroutines have the same promise type, this is easy as long as I have a handle for the last coroutine frame. I can implement it something like this:

void WalkCoroutineChain(std::coroutine_handle<Promise> h) {
  while (h) {
    // ... do something with h ...
    Visit(h);
    
    // Move on to the caller of h's coroutine.
    h = h.promise().awaiter;
  }
}

The problem is that in reality they don't all have the same promise type. They share a common promise type template, and even a single common base class for the promise type, but the promise type itself differs because it is templated on the type of result they return.


My question is: given a reference to a promise, is there any legal way to store it as something type-erased like void* or std::coroutine_handle<> while retaining the ability to do both of the following operations via that type-erased value?

  1. Resume or destroy its coroutine.
  2. Obtain a reference to its known base class. (If it helps, I can guarantee there is no multiple inheritance.)

This would allow me to solve my problem because I could put the awaiter member into a common untemplated base class that all of the promises inherit from, and walk a list of those.

I'm pretty sure in practice it would probably work out to just do this:

std::coroutine_handle<> type_erased = GetHandleSomehow();
auto base = std::coroutine_handle<PromiseBase>::from_address(type_erased.address());

But this is probably undefined behavior:

  • In the C 20 draft [coroutine.handle.export.import] lists as a precondition for std::coroutine_handle<Promise>::from_address "addr was obtained via a prior call to address", without saying anything about the type of the callee of address.

  • In the latest draft it says specifically "addr was obtained via a prior call to address on an object of type cv coroutine_­handle<Promise>", which wouldn't be true if Promise is actually a base class for the real promise type.

CodePudding user response:

I think the following is likely to work in practice, at least in the simple case where there is no multiple inheritance or virtual functions:

// Assume the promise inherits from a base class.
struct PromiseBase {
  // The handle to resume when done, also used for walking the linked list as
  // described in the question.
  std::coroutine_handle<PromiseBase> awaiter;
};

template <typename Result>
struct Promise : PromiseBase {
  std::coroutine_handle<PromiseBase> get_base_handle() {
    std::coroutine_handle<PromiseBase>::from_promise(*this);
  }
};

PromiseBase::awaiter can be used as the link to walk in a WalkCoroutineChain function, rather than std::coroutine_handle<Promise<T>>, since T may vary across the chain of coroutines.


I read both the C 20 draft and the latest draft of the standard as saying the implementation of get_base_handle is legal: the precondition for std::coroutine_handle<PromiseBase>::from_promise is only that the input reference is "a reference to a promise object of a coroutine". That is surely true of *this in that context, and presumably still true even when converted to a reference to PromiseBase.

I say this is "likely to work in practice" because I'm not 100% confident in the intent of the standard. Perhaps this is meant to be forbidden? But if so, it is certainly not clear. In the absence of clear wording it's also worth considering implementability, and I see no reason that this wouldn't work correctly given how coroutine handles are implemented in clang/libc today (a void* pointer to the coroutine frame with the promise at a known offset); this seems true at least in the absence of multiple inheritance and virtual functions.

If the intent of the standard is to say this is not legal, then I think the wording could be improved. I've filed an issue on the standard's GitHub repo to ask for clarification.

CodePudding user response:

There is no way to get a promise of any kind from a std::coroutine_handle<>. That is basically the entire point of that specialization: to interact with a coroutine without caring what type of promise it contains.

But this is probably undefined behavior:

That LWG issue 3460. It specifically makes it clear that coroutine_handle<T>::from_address requires that the address was obtained specifically from an instance of coroutine_handle<T>. This was applied as a defect of C 20, so it's part of the standard.

  • Related