{"id":108398,"date":"2023-07-06T07:00:00","date_gmt":"2023-07-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108398"},"modified":"2023-06-19T07:31:08","modified_gmt":"2023-06-19T14:31:08","slug":"20230706-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230706-00\/?p=108398\/","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, wrapping the awaitable"},"content":{"rendered":"<p>So far, we&#8217;ve been trying to intercept exceptions that propagate from the awaitable by wrapping it in a coroutine that catches exceptions. This seems like a reasonable approach, but we got bogged down dealing with the edge case where <a title=\"How to wait for multiple C++ coroutines to complete before propagating failure, preallocating the coroutine frame\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230705-00\/?p=108392\"> the wrapper coroutine itself throws a memory allocation exception<\/a> while creating the coroutine frame.<\/p>\n<p>But there&#8217;s another way to intercept the exception that doesn&#8217;t involve creating a new coroutine at all. Instead of wrapping the awaitable in a coroutine, we can wrap the awaitable inside another awaitable.<\/p>\n<p>Consider the simple case of an awaitable that is its own awaiter. (No <code>operator co_await<\/code>.) You can wrap the awaitable inside another awaitable that catches the exception. We&#8217;ll start with a wrapper that does nothing.<\/p>\n<pre>template&lt;typename Inner&gt;\r\nstruct wrapped_awaitable\r\n{\r\n    wrapped_awaitable(Inner&amp; inner) : m_inner(inner) {}\r\n\r\n    Inner&amp; m_inner;\r\n\r\n    bool await_ready()\r\n    { return m_inner.await_ready(); }\r\n\r\n    template&lt;typename Handle&gt;\r\n    auto await_suspend(Handle handle) {\r\n        return m_inner.await_suspend(handle);\r\n    }\r\n\r\n    auto await_resume()\r\n    { return m_inner.await_resume(); }\r\n};\r\n<\/pre>\n<p>This wrapper doesn&#8217;t do anything. It just forwards all of the awaiter methods to the wrapped object.<\/p>\n<p>But now that we have a framework, we can start adding features to it.<\/p>\n<pre>template&lt;typename Inner&gt;\r\nstruct wrapped_awaitable\r\n{\r\n    wrapped_awaitable(Inner&amp; inner) : m_inner(inner) {}\r\n\r\n    Inner&amp; m_inner;\r\n\r\n    bool await_ready()\r\n    { return m_inner.await_ready(); }\r\n\r\n    template&lt;typename Handle&gt;\r\n    auto await_suspend(Handle handle) {\r\n        return m_inner.await_suspend(handle);\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">std::exception_ptr<\/span> await_resume() <span style=\"border: solid 1px currentcolor;\">try<\/span> {\r\n        m_inner.await_resume();\r\n        return nullptr;\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">} catch (...) {                     <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    return std::current_exception();<\/span>\r\n    }\r\n};\r\n<\/pre>\n<p>We change <code>await_resume()<\/code> to return any exception that is thrown when inspecting the result of the <code>co_await<\/code>. If no exception occurs, then we return <code>nullptr<\/code>, indicating that no exception occurred.<\/p>\n<p>That&#8217;s the basic idea. The rest is filling in the gaps.<\/p>\n<p>For example, what happens if the awaiter throws an exception from one of the other two awaiter methods?<\/p>\n<p>Well, the <code>await_ready<\/code> happens before the coroutine suspends, so any exception that is thrown from there just goes straight to the containing coroutine&#8217;s <code>unhandled_exception<\/code>. Since our goal is to capture and return the exception rather than allowing it to be thrown, we have to include a <code>try<\/code>\/<code>catch<\/code> in our <code>await_ready<\/code> wrapper, and if an exception occurs, we need to bypass the <code>await_suspend<\/code> and go directly to <code>await_resume<\/code>. Furthermore, the <code>await_resume<\/code> should not call the wrapped awaiter; it should just produce the caught exception immediately.<\/p>\n<pre>template&lt;typename Inner&gt;\r\nstruct wrapped_awaitable\r\n{\r\n    wrapped_awaitable(Inner&amp; inner) : m_inner(inner) {}\r\n\r\n    Inner&amp; m_inner;\r\n    <span style=\"border: solid 1px currentcolor;\">std::exception_ptr m_eptr;<\/span>\r\n\r\n    bool await_ready() <span style=\"border: solid 1px currentcolor;\">try<\/span>\r\n    { return m_inner.await_ready(); }\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">catch (...) {                         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_eptr = std::current_exception();<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return true;                      <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                     <\/span>\r\n\r\n    template&lt;typename Handle&gt;\r\n    auto await_suspend(Handle handle) {\r\n        return m_inner.await_suspend(handle);\r\n    }\r\n\r\n    std::exception_ptr await_resume() try {\r\n        <span style=\"border: solid 1px currentcolor;\">if (m_eptr) return m_eptr;<\/span>\r\n        m_inner.await_resume();\r\n        return nullptr;\r\n    } catch (...) {\r\n        return std::current_exception();\r\n    }\r\n};\r\n<\/pre>\n<p>An exception in <code>await_suspend<\/code> is the trickiest one. According to the language specification, if an exception is thrown by <code>await_suspend<\/code>, the coroutine resumes but does <i>not<\/i> call <code>await_resume<\/code>. Instead, the exception thrown by <code>await_suspend<\/code> is immediately rethrown.<\/p>\n<p>This means that if we catch an exception from <code>await_suspend<\/code>, we should return it immediately from <code>await_resume<\/code> without calling <code>m_inner.await_resume()<\/code>.<\/p>\n<pre>template&lt;typename Inner&gt;\r\nstruct wrapped_awaitable\r\n{\r\n    wrapped_awaitable(Inner&amp; inner) : m_inner(inner) {}\r\n\r\n    Inner&amp; m_inner;\r\n    std::exception_ptr m_eptr;\r\n\r\n    bool await_ready() try\r\n    { return m_inner.await_ready(); }\r\n    catch (...) {\r\n        m_eptr = std::current_exception();\r\n        return true;\r\n    }\r\n\r\n    template&lt;typename Handle&gt;\r\n    auto await_suspend(Handle handle) <span style=\"border: solid 1px currentcolor;\">try<\/span> {\r\n        return m_inner.await_suspend(handle);\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">} catch (...) {                       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_eptr = std::current_exception();<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return \/* something *\/;           <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                     <\/span>\r\n\r\n    std::exception_ptr await_resume() try {\r\n        if (m_eptr) return m_eptr;\r\n        m_inner.await_resume();\r\n        return nullptr;\r\n    } catch (...) {\r\n        return std::current_exception();\r\n    }\r\n};\r\n<\/pre>\n<p>The next tricky part is the <code>return \/* something *\/<\/code>. We want to return something that means &#8220;Don&#8217;t suspend this coroutine after all.&#8221; But it also has to be compatible with the <code>return<\/code> statement inside the <code>try<\/code> block.<\/p>\n<p>Fortunately, symmetric transfer is a superset of the other types of return values from <code>await_suspend()<\/code>, so we can always return a coroutine handle and adapt the other return values accordingly.<\/p>\n<pre>template&lt;typename Inner&gt;\r\nstruct wrapped_awaitable\r\n{\r\n    wrapped_awaitable(Inner&amp; inner) : m_inner(inner) {}\r\n\r\n    Inner&amp; m_inner;\r\n    std::exception_ptr m_eptr;\r\n\r\n    bool await_ready() try\r\n    { return m_inner.await_ready(); }\r\n    catch (...) {\r\n        m_eptr = std::current_exception();\r\n        return true;\r\n    }\r\n\r\n    template&lt;typename Handle&gt;\r\n    <span style=\"border: solid 1px currentcolor;\">std::coroutine_handle&lt;&gt;<\/span>\r\n    await_suspend(Handle handle) try {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">using Ret = decltype(m_inner.await_suspend(handle));<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">if constexpr (std::is_same_v&lt;void, Ret&gt;) {          <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_inner.await_suspend(handle);                  <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    return std::noop_coroutine();                   <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">} else if constexpr (std::is_same_v&lt;bool, Ret&gt;) {   <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    return m_inner.await_suspend(handle) ?          <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">        static_cast&lt;std::coroutine_handle&lt;&gt;&gt;(       <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">            std::noop_coroutine()) :                <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">        handle;                                     <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">} else {                                            <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    return m_inner.await_suspend(handle);           <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                   <\/span>\r\n    } catch (...) {\r\n        m_eptr = std::current_exception();\r\n        <span style=\"border: solid 1px currentcolor;\">return handle;<\/span>\r\n    }\r\n\r\n    std::exception_ptr await_resume() try {\r\n        if (m_eptr) return m_eptr;\r\n        m_inner.await_resume();\r\n        return nullptr;\r\n    } catch (...) {\r\n        return std::current_exception();\r\n    }\r\n};\r\n<\/pre>\n<p>All right, we&#8217;ve wrapped an awaiter so that awaiting the wrapper reports any exception that may have resulted from awaiting the original awaiter.<\/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 accumulate = [&amp;](std::exception_ptr e) {\r\n        if (eptr == nullptr) eptr = e;\r\n    };\r\n\r\n    (accumulate(<span style=\"border: solid 1px currentcolor;\">co_await wrapped_awaitable(asyncs))<\/span>, ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>Next time, we&#8217;ll deal with <code>operator co_await<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Intercepting the exception on the front side.<\/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-108398","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Intercepting the exception on the front side.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108398","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=108398"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108398\/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=108398"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108398"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}