{"id":108382,"date":"2023-06-30T07:00:00","date_gmt":"2023-06-30T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108382"},"modified":"2023-06-19T06:38:42","modified_gmt":"2023-06-19T13:38:42","slug":"20230630-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230630-00\/?p=108382","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, custom promise"},"content":{"rendered":"<p>Last time, <a title=\"How to wait for multiple C++ coroutines to complete before propagating failure, peeling away at a tuple\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230629-00\/?p=108380\"> we used a <code>std::tuple<\/code> and a recursive function to implement our <code>when_<wbr \/>all_<wbr \/>completed<\/code> function<\/a>. We noted that if during the recursion, the allocation of the coroutine frame fails, then a <code>std::<wbr \/>bad_<wbr \/>alloc<\/code> is thrown, and we end up returning via exception before awaiting the completion of all of the awaitables. We failed to do our stated job.<\/p>\n<p>One way to address this is to use a custom promise. If the custom promise implements <code>operator new<\/code>, then that is used to allocate the coroutine frame, and that&#8217;s our chance to deal with the failure.<\/p>\n<p>And if we&#8217;re going to use a custom promise, we may as well let the promise do all the work.<\/p>\n<p>Let&#8217;s write the coroutine promise that returns any observed exception. This custom promise also resumes its caller in the same apartment that the coroutine finished in. The apartment-switching behavior of C++\/WinRT&#8217;s <code>IAsyncAction<\/code> awaiter was one of the frustrating parts of our <code>when_<wbr \/>all_<wbr \/>completed<\/code> implementations.\u00b9<\/p>\n<pre>struct all_completed_promise;\r\n\r\nstruct all_completed_result\r\n{\r\n    all_completed_promise&amp; promise;\r\n    bool await_ready() noexcept { return false; }\r\n    void await_suspend(\r\n        std::coroutine_handle&lt;&gt; handle) noexcept;\r\n    std::exception_ptr await_resume() noexcept;\r\n};\r\n\r\nstruct all_completed_promise\r\n{\r\n    std::coroutine_handle&lt;&gt; awaiting_coroutine;\r\n    std::exception_ptr eptr;\r\n\r\n    all_completed_result get_return_object() noexcept {\r\n        return {*this};\r\n    }\r\n    std::suspend_always initial_suspend() noexcept { return {}; }\r\n\r\n    auto coroutine() {\r\n        return std::coroutine_handle&lt;all_completed_promise&gt;::\r\n            from_promise(*this);\r\n    }\r\n\r\n    void return_void() noexcept {}\r\n    void unhandled_exception() noexcept\r\n    { eptr = std::current_exception(); }\r\n\r\n    std::suspend_never final_suspend() noexcept {\r\n        awaiting_coroutine.resume();\r\n        return {};\r\n    }\r\n};\r\n\r\nvoid all_completed_result::\r\n    await_suspend(std::coroutine_handle&lt;&gt; handle)\r\n    noexcept\r\n{\r\n    promise.awaiting_coroutine = handle;\r\n    promise.coroutine().resume();\r\n}\r\n\r\nstd::exception_ptr all_completed_result::\r\n    await_resume() noexcept\r\n{\r\n    return promise.eptr;\r\n}\r\n\r\nnamespace std\r\n{\r\n    template&lt;typename...Args&gt;\r\n    struct coroutine_traits&lt;all_completed_result, Args...&gt;\r\n    {\r\n        using promise_type = all_completed_promise;\r\n    };\r\n}\r\n\r\n\/\/ example\r\nall_completed_result Sample()\r\n{\r\n    co_await do_something();\r\n}\r\n\r\nwinrt::fire_and_forget Caller()\r\n{\r\n    \/\/ eptr = nullptr if Sample ran without exception.\r\n    \/\/ Otherwise it holds the exception pointer which\r\n    \/\/ can be used to rethrow the exception.\r\n    std::exception_ptr eptr = co_await Sample();\r\n}\r\n<\/pre>\n<p>A coroutine that returns <code>all_<wbr \/>completed_<wbr \/>result<\/code> executes its body and reports whether it encountered an exception. If so, then that exception is returned when you <code>co_await<\/code>.<\/p>\n<p>First, let&#8217;s look at the promise.<\/p>\n<p>The promise holds only two things: The coroutine that is awaiting the result and the exception that occurred in the coroutine body.<\/p>\n<p>The promise implements a lazy-start coroutine. This avoids having to deal with race conditions if the coroutine body completes before the caller manages to <code>co_await<\/code> the result. Instead, we suspend the coroutine immediately, and resume it only when manually resumed.<\/p>\n<p>To make it easier to access the coroutine handle, we have a <code>coroutine()<\/code> function that recovers the <code>coroutine_handle<\/code> from the promise.<\/p>\n<p>When the caller awaits the <code>all_<wbr \/>completed_<wbr \/>result<\/code>, the <code>await_suspend<\/code> remembers the caller&#8217;s coroutine handle and then resumes the promise&#8217;s coroutine from its initial suspension point.<\/p>\n<p>If the coroutine runs to completion successfully, the compiler will call <code>return_void()<\/code>, which we ignore.<\/p>\n<p>If the coroutine encounters an exception, the compiler will call <code>unhandled_exception()<\/code>, which saves the exception in the <code>eptr<\/code> member.<\/p>\n<p>After the coroutine completes (either successfully or with an exception), the compiler awaits the <code>final_suspend<\/code>. We return a <code>suspend_never<\/code>, which allows the coroutine frame to be destroyed, but not before we resume the awaiting coroutine so it can see what exception occurred, if any.<\/p>\n<p>The <code>all_<wbr \/>completed_<wbr \/>result<\/code> contains a reference to the associated coroutine promise. When you <code>co_await<\/code> it, it starts the coroutine body (and remembers the awaiting coroutine&#8217;s handle for later resumption). After the coroutine completes, the compiler will call <code>await_resume()<\/code>, whhich returns the exception pointer that was saved in the promise.<\/p>\n<p>Given this, we can go back to our lambda-coroutine-based solution, since we got rid of the apartment-switching behavior of C++\/WinRTs <code>IAsyncAction<\/code> awaiter, and our custom coroutine promise just returns the exception directly.<\/p>\n<pre>template&lt;typename... T&gt;\r\nIAsyncAction when_all_complete(T... asyncs)\r\n{\r\n    std::exception_ptr eptr;\r\n\r\n    auto capture_exception = [](auto&amp; async)\r\n        -&gt; all_completed_result {\r\n        co_await std::move(async);\r\n    };\r\n\r\n    auto accumulate = [&amp;](std::exception_ptr e) {\r\n        if (eptr == nullptr) eptr = e;\r\n    };\r\n\r\n    (accumulate(co_await capture_exception(asyncs)), ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>Okay, now that we have our coroutine promise, we can start making refinements to it. We&#8217;ll start next time.<\/p>\n<p>\u00b9 The apartment-switching behavior is not inherent in C++ coroutines. Rather, it&#8217;s a design decision of the C++\/WinRT library. In theory, it would be possible to add a way to configure the C++\/WinRT awaiter to disable the apartment-switching behavior, say by doing something like<\/p>\n<pre>    co_await action.resume_any_apartment();\r\n<\/pre>\n<p>where a new <code>resume_any_apartment()<\/code> method returns a wrapper around the original <code>IAsyncAction<\/code> that triggers a different custom awaiter which does not perform an apartment switch.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Taking things into our own hands.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-108382","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Taking things into our own hands.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108382","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=108382"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108382\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=108382"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108382"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108382"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}