{"id":108389,"date":"2023-07-04T07:00:00","date_gmt":"2023-07-04T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108389"},"modified":"2023-07-31T11:09:48","modified_gmt":"2023-07-31T18:09:48","slug":"20230704-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230704-00\/?p=108389","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, memory allocation failure"},"content":{"rendered":"<p>Last time, <a title=\"How\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230703-00\/?p=108387\"> we used symmetric transfer to avoid stack build-up<\/a> when calling back and forth between the outer driver function <code>when_<wbr \/>all_<wbr \/>completed<\/code> and the coroutine created from our custom promise. But we lost sight of the original reason for using the custom promise, which was to allow us to deal with memory allocations.<\/p>\n<p>The concern we had was that if a memory allocation error occurred when allocating the coroutine frame, the runtime normally throws <code>std::<wbr \/>bad_alloc<\/code>, and the coroutine body never runs. Furthermore, we currently do not distinguish between an allocation failure in the creation of the coroutine, as opposed to an allocation failure that was thrown from the coroutine body.<\/p>\n<p>One way to deal with this is to catch the initial coroutine creation, separately from <code>co_await<\/code>ing it.<\/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    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto ensure_alloc = [&amp;](auto&amp; async) noexcept<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return capture_exception(async);         <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">};                                           <\/span>\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 <span style=\"border: solid 1px currentcolor;\">ensure_alloc<\/span>(asyncs)), ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>If a memory allocation failure occurs when trying to allocate the coroutine frame, then the <code>std::<wbr \/>bad_alloc<\/code> is thrown out of the initial call to <code>capture_exception()<\/code>. The <code>noexcept<\/code> on the <code>ensure_alloc<\/code> converts this exception into a <code>std::terminate<\/code>, and the program fails fast.<\/p>\n<p>We are unable to make forward progress, and throwing an exception from <code>when_<wbr \/>all_<wbr \/>completed<\/code> would be misinterpreted as propagating an exception from one of the awaitables. The only remaining option is to simply terminate the process.<\/p>\n<p>An alternate solution is to encode the &#8220;terminate on <code>std::<wbr \/>bad_alloc<\/code> trying to allocate the coroutine frame&#8221; in the promise itself. If the promise has a custom <code>operator new<\/code>, then that custom operator is used to allocate the coroutine frame. (Similarly, a custom <code>operator delete<\/code> is used to deallocate the coroutine frame.)<\/p>\n<p>Therefore, instead of modifying <code>when_<wbr \/>all_<wbr \/>completed<\/code>, we can modify the <code>all_<wbr \/>completed_<wbr \/>promise<\/code>:\u00b9<\/p>\n<pre>struct all_completed_promise\r\n{\r\n    ...\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">\/\/ Fail fast if unable to allocate the coroutine frame.<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">void* operator new(std::size_t n) try {                <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return ::operator new(n);                          <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">} catch (...) { std::terminate(); }                    <\/span>\r\n};\r\n<\/pre>\n<p>Another option might be to invent a special exception that means &#8220;Sorry, I was unable to perform the requested operation. The awaitables have not necessarily all run to completion.&#8221;<\/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 ensure_alloc = [&amp;](auto&amp; async) <span style=\"border: solid 1px currentcolor;\">try<\/span>\r\n    {\r\n        return capture_exception(async);\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">} catch (...) {                                     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    throw winrt::hresult_error(                     <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">        HRESULT_FROM_WIN32(ERROR_CAN_NOT_COMPLETE));<\/span>\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 ensure_alloc(asyncs)), ...);\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>Or, if you prefer to apply this policy in the promise:<\/p>\n<pre>struct all_completed_promise\r\n{\r\n    ...\r\n\r\n    void* operator new(std::size_t n) <span style=\"border: solid 1px currentcolor;\">try<\/span> {\r\n        return ::operator new(n);\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">} catch (...) {                                     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    throw winrt::hresult_error(                     <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">        HRESULT_FROM_WIN32(ERROR_CAN_NOT_COMPLETE));<\/span>\r\n    };\r\n};\r\n<\/pre>\n<p>And now you can document that <code>ERROR_<wbr \/>CAN_<wbr \/>NOT_<wbr \/>COMPLETE<\/code> is the error code that means, &#8220;Sorry, I was not able to perform the required duties.&#8221;<\/p>\n<p>I&#8217;m not sure this special exception is warranted, however, because there&#8217;s no way for the caller to recover. I mean, you call this function to wait for all of the operations to complete, and it says &#8220;Sorry, I couldn&#8217;t do that.&#8221; Now what are you going to do? The operations haven&#8217;t run to completion. You can&#8217;t perform the next step in your algorithm. But you can&#8217;t just give up now, because you want to make sure those operations are complete and won&#8217;t create race conditions.<\/p>\n<p>You&#8217;re stuck between the same rock and hard place that the <code>when_<wbr \/>all_<wbr \/>complete<\/code> function found itself in, and your options are the same: Either fail fast now, or tell the caller &#8220;Hi, so something is fatally, unrecoverably wrong, and there&#8217;s no point continuing because if you do, there is going to be memory corruption and race conditions and all sorts of badness.&#8221;<\/p>\n<p>And what&#8217;s your caller going to do? Either fail fast or pass the buck to its caller.<\/p>\n<p>Eventually, there will be nobody left to pass the buck to, and the program will terminate with an unhandled exception. Even worse, the termination will occur at a point far, far away from the root cause, so the poor developer who gets stuck with the unhandled exception is going to have quite an unwanted adventure trying to figure out what happened.<\/p>\n<p>May as well save everyone the trouble of trying to deal with a problem that they cannot recover from. Fail fast right away so you can identify the root cause quickly.<\/p>\n<p>Next time, we&#8217;ll try to take the dynamic memory allocation out of this entire scenario.<\/p>\n<p>\u00b9 You&#8217;d think you could write<\/p>\n<pre>    \/\/ Fail fast if unable to allocate the coroutine frame.\r\n    void* operator new(std::size_t n) <span style=\"border: solid 1px currentcolor;\">noexcept<\/span> {\r\n        return ::operator new(n);\r\n    }\r\n<\/pre>\n<p>so that any exception thrown by <code>::operator new<\/code> terminates the process. This does work, but a <code>noexcept<\/code> <code>operator new<\/code> tells the compiler that it will return <code>nullptr<\/code> on memory allocation failure, in which case the compiler will call a developer-provided <code>get_<wbr \/>return_<wbr \/>object_<wbr \/>on_<wbr \/>allocation_<wbr \/>failure()<\/code> function to produce the <code>all_<wbr \/>completed_<wbr \/>result<\/code> that will be returned by the (nonexistent) coroutine. Therefore, if we apply <code>noexcept<\/code> to our custom <code>operator new<\/code>, we also have to provide a <code>get_<wbr \/>return_<wbr \/>object_<wbr \/>on_<wbr \/>allocation_<wbr \/>failure()<\/code> function. Removing the <code>noexcept<\/code> from our <code>operator new<\/code> avoids this requirement.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There&#8217;s no good way to report the failure, so we just have to give up.<\/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-108389","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>There&#8217;s no good way to report the failure, so we just have to give up.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108389","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=108389"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108389\/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=108389"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108389"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108389"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}