{"id":108373,"date":"2023-06-26T07:00:00","date_gmt":"2023-06-26T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108373"},"modified":"2023-06-19T06:20:49","modified_gmt":"2023-06-19T13:20:49","slug":"20230626-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230626-00\/?p=108373","title":{"rendered":"How to wait for multiple C++ coroutines to complete before propagating failure, initial plunge"},"content":{"rendered":"<p>I&#8217;ll start by repeating a very handy cheat sheet from <a title=\"The case of the memory corruption from a coroutine that already finished\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221014-00\/?p=107287\"> a debugging case study of memory corruption from a coroutine that already finished<\/a>.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Language<\/th>\n<th>Method<\/th>\n<th>Result<\/th>\n<th>If any fail<\/th>\n<\/tr>\n<tr>\n<td>C++<\/td>\n<td>Concurrency::when_all<\/td>\n<td><code>vector&lt;T&gt;<\/code><\/td>\n<td>fail immediately<\/td>\n<\/tr>\n<tr>\n<td>C++<\/td>\n<td>winrt::when_all<\/td>\n<td><code>void<\/code><\/td>\n<td>fail immediately<\/td>\n<\/tr>\n<tr>\n<td>C#<\/td>\n<td>Task.WhenAll<\/td>\n<td><code>T[]<\/code><\/td>\n<td>wait for others<\/td>\n<\/tr>\n<tr>\n<td>JavaScript<\/td>\n<td>Promise.all<\/td>\n<td><code>Array<\/code><\/td>\n<td>fail immediately<\/td>\n<\/tr>\n<tr>\n<td>JavaScript<\/td>\n<td>Promise.allSettled<\/td>\n<td><code>Array<\/code><\/td>\n<td>wait for others<\/td>\n<\/tr>\n<tr>\n<td>Python<\/td>\n<td>asyncio.gather<\/td>\n<td>List<\/td>\n<td>fail immediately by default<\/td>\n<\/tr>\n<tr>\n<td>Rust<\/td>\n<td>join!<\/td>\n<td>tuple<\/td>\n<td>wait for others<\/td>\n<\/tr>\n<tr>\n<td>Rust<\/td>\n<td>try_join!<\/td>\n<td>tuple<\/td>\n<td>fail immediately<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Python&#8217;s <code>asynchio.gather<\/code> lets you choose whether a failed coroutine causes <code>gather<\/code> to fail immediately or to wait for others before failing. The default is to fail immediately.<\/p>\n<p>The problem we saw was that the C++\/WinRT <code>when_all<\/code> fails immediately, but the code wanted it to wait for the others before failing. This is a potentially useful general pattern, so let&#8217;s try to write our own <code>when_<wbr \/>all_<wbr \/>completed<\/code> function.<\/p>\n<p>It&#8217;ll take a few tries to get there.<\/p>\n<p>The idea behind the function is simple. Here&#8217;s the pseudocode:<\/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    \/* Repeat for each element \"async\" of asyncs... *\/\r\n    try {\r\n        co_await async;\r\n    } catch (...) {\r\n        if (!eptr) {\r\n            eptr = std::current_exception();\r\n        }\r\n    }\r\n    ...\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>The idea is that we <code>co_await<\/code> each of the passed-in coroutines, but do so inside a <code>try<\/code>\/<code>catch<\/code> block. If an exception occurs, then we save it, assuming we don&#8217;t have an exception already: The first exception thrown is the one that is reported. (Naturally, you can remove the <code>if (!eptr)<\/code> if you want to report the last exception thrown.)<\/p>\n<p>The &#8220;repeat for&#8230;&#8221; part can be solved with <a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2019\/p1306r1.pdf\"> expansion statements<\/a>:<\/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    for... (auto&amp; async : async) {\r\n        try {\r\n            co_await async;\r\n        } catch (...) {\r\n            if (!eptr) {\r\n                eptr = std::current_exception();\r\n            }\r\n        }\r\n    }\r\n\r\n    if (eptr) std::rethrow_exception(eptr);\r\n}\r\n<\/pre>\n<p>But before we clap the dust off our hands, note that expansion statements failed to be completed in time for C++20 and were postponed to C++23. And even if it were available in C++20, it will take time for projects to migrate off of C++17, so we&#8217;ll have to find a solution that at least works on C++17.<\/p>\n<p>We&#8217;ll start our explorations next time.<\/p>\n<p>Warning: There will be a lot of failure. If you&#8217;re looking for an answer to be handed to you, you&#8217;ll have to skip ahead to the end of the series, whenever that ends up happening.<\/p>\n<p><b>Bonus chatter<\/b>: Also, there&#8217;s a frustrating edge case in the above code, but I don&#8217;t want to try to fix it yet.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Remembering the exception while finishing the other work.<\/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-108373","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Remembering the exception while finishing the other work.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108373","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=108373"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108373\/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=108373"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108373"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108373"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}