3

AFAICT there is no builtin way in C++20 or C++23 to globally query the promise or handle for the coroutine running on the current thread. However any of the functions that let you return a coroutine to trigger running it, e.g. initial_suspend, final_suspend, await_suspend etc would know what coroutine is about to run and could set a thread_local to the coroutine_handle they are returning before returning. Is that sufficient or are there more cases to worry about? This is assuming that in typical usage people are doing co_await on their task types and not manually running their own loop calling handle.resume() over and over (and if there was an executor/execution-agent switching between stored top-level coroutines that it would cooperate with the scheme).

2
  • 2
    It is possible to use a custom awaitable to get the coroutine_handle of the current coroutine, see this answer for an example - that does not require manually setting a thread_local variable on every potential suspension / resume point. Note though that the coroutine_handle itself is pretty useless while its coroutine is not suspended (calling most fuctions on it like resume() / destroy() etc... will result in UB if the coroutine is still executing) Commented Jan 16 at 5:48
  • If "awaitable" is just doing await_ready() { return true; } - then there is no call to await_suspend and no cost of things you are mentioning in your question Commented Jan 16 at 8:34

1 Answer 1

4

I had the same problem some time ago - I tried various approaches - also the "co_await"-way mentioned in comments - but the best with performance in mind is to use "co_yield". Just create some get_handle empty class to tag-dispatch to your yield_value method. And from this yield_value(get_handle) method return awaitable type derived from std::suspend_never - so no "suspend" - and overload await_resume to return handle to the caller from the actual coroutine body.

#include <coroutine>

struct get_handle{}; // just tag type
struct coro
{
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    struct promise_type
    {
        int some_value = 42; // for presentation purposes

        auto initial_suspend() noexcept { return std::suspend_never{}; }
        auto final_suspend() noexcept { return std::suspend_never{}; }
        coro get_return_object() { return {}; }
        void unhandled_exception() {}
        void return_void() {}

        auto yield_value(get_handle)
        {
            struct return_handle : std::suspend_never
            {
                handle_type handle;

                handle_type await_resume() const
                {
                    return handle;
                }
            };

            return return_handle{.handle = handle_type::from_promise(*this)};
        }

    };
};

and proof it works:

coro foo()
{
    auto handle = co_yield get_handle{};
    std::cout << handle.promise().some_value;
}



int main() {

    foo();
}

But maybe most important - you can use "co_yield" (and co_await with await_transform too) to communicate with your promise object - so, actually, in most cases you shall not need to get full handle - so if you can redesign your implementation in the way to not need this handle inside coroutine body - that would be the best probably.

Sign up to request clarification or add additional context in comments.

6 Comments

Hmm, can I use this from a non-coroutine function?
For context the reason I'm asking is it would be nice to make the allocator used for my coroutines viral -- if coroutine A creates another coroutine B, it would be nice if the allocation of B used the same allocator that was used for A (which is already stored in A's promise type). But operator new isn't a coroutine so it can't co_yield. Also for instrumentation would be nice to be able to graph coroutine child/parent relationships (who created who).
You can pass to promise_type whatever you want during construction - by adding extra parameters to promise_type and to coroutine itself. Like: class coro... class promise_type(auto allocator) { ...} ... and in coroutine: ` coro bar(SomeAllocator); coro foo(SomeAllocator a) { .... bar(a); ...}`. But maybe you should ask another question with details what you want to achieve.
Yeah the extra parameters to the coroutine are what I am trying to avoid. The idea is if the parent coroutine used some allocator, then the child could detect it and use it without needing the user to explicitly pass an allocator to the coroutine for passing into operator new.
I do not think there is a way to know, by coroutine promise_type - if it is started from another coroutine. I think you either just pass extra parameters to promise_type constructor - or really use some thread_local stack variable with current "coroutines callstack" -- but there is a small problem - coroutines might be freely move between threads when suspended - I mean - you might resume coroutines in thread-2 - when it was suspended in thread-1 - and this is pretty nornal to do it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.