{"id":107779,"date":"2023-02-02T07:00:00","date_gmt":"2023-02-02T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107779"},"modified":"2023-02-01T20:26:15","modified_gmt":"2023-02-02T04:26:15","slug":"20230202-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230202-00\/?p=107779","title":{"rendered":"Inside C++\/WinRT: Coroutine completions: Avoiding reentrant completion"},"content":{"rendered":"<p>If a Windows Runtime asynchronous operation has already completed at the point the <code>Completed<\/code> delegate is assigned, the implementation is permitted to invoke the delegate before returning from the assignment.<\/p>\n<p>The way we have set things up so far, it means that the awaiting an already-completed Windows Runtime asynchronous operation results in a chain of calls:<\/p>\n<pre>MyAwesomeCoroutine::DoSomethingAsync$Resu\r\ncoroutine_handle&lt;&gt;::resume\r\nresume_apartment\r\ndisconnect_aware_handler::Complete\r\nProvider::FireCompletion\r\nProvider::put_Completed\r\nIAsyncOperation::put_Completed\r\nawait_adapter::await_suspend\r\nMyAwesomeCoroutine::DoSomethingAsync$Resu\r\n<\/pre>\n<p>All those stack frames between the two <code>My\u00adAwesome\u00adCoroutine::<wbr \/>Do\u00adSomething\u00adAsync<wbr \/>$Resu<\/code> frames are unnecessary, and if you are <code>co_await<\/code>&#8216;ing in a loop, the stack usage accumulates and can result in unexpected stack exhaustion.<\/p>\n<p>What we can do is detect that the completion handler is running before <code>put_Completed<\/code> has returned, and in that case, we merely remember that the coroutine needs to resume, but without actually resuming it immediately. We allow execution to unwind back to <code>await_<wbr \/>suspend<\/code> and then return <code>false<\/code> to tell the coroutine infrastructure to resume the coroutine when it unwinds.<\/p>\n<pre>template&lt;typename Awaiter&gt;\r\nstruct disconnect_aware_handler\r\n{\r\n    \u301a ... \u301b\r\n\r\n    void Complete()\r\n    {\r\n        <span style=\"color: #08f;\">if (m_awaiter-&gt;suspending\r\n            .exchange(false, std::memory_order_release))\r\n        {\r\n            \/\/ resumption has been deferred to await_suspend\r\n            m_handle = nullptr;\r\n        }\r\n        else\r\n        {<\/span>\r\n            resume_apartment(m_context.context,\r\n                std::exchange(m_handle, {}),\r\n                &amp;m_awaiter-&gt;failure);\r\n        <span style=\"color: #08f;\">}<\/span>\r\n    }\r\n};\r\n<\/pre>\n<p>If the awaiter says that it&#8217;s still suspending, then don&#8217;t resume immediately. Instead, reset the <code>suspending<\/code> to <code>false<\/code> to tell <code>await_suspend<\/code> to cancel the suspension.<\/p>\n<pre>template&lt;typename Async&gt;\r\nstruct await_adapter\r\n{\r\n    await_adapter(Async const&amp; async) : async(async) { }\r\n\r\n    Async const&amp; async;\r\n    int32_t failure = 0;\r\n    <span style=\"color: #08f;\">std::atomic&lt;bool&gt; suspending = true;<\/span>\r\n\r\n    \u301a ... \u301b\r\n\r\n    <span style=\"color: #08f;\">auto<\/span> await_suspend(coroutine_handle&lt;&gt; handle) const\r\n    {\r\n        <span style=\"color: #c65353;\"> \/\/ <span style=\"text-decoration: line-through;\">auto extend_lifetime = async;<\/span><\/span>\r\n        async.Completed(\r\n            disconnect_aware_handler(this, handle));\r\n        <span style=\"color: #08f;\">return suspending.exchange(false, std::memory_order_acquire);<\/span>\r\n    }\r\n\r\n    \u301a ... \u301b\r\n};\r\n<\/pre>\n<p>The other half of the communication is in the <code>await_<wbr \/>adapter<\/code>&#8216;s <code>await_<wbr \/>suspend<\/code> method. After setting the <code>Completed<\/code> handler, we reset <code>suspending<\/code> to <code>false<\/code>, and return the previous value. The previous value is <code>true<\/code> if the completion handler hasn&#8217;t run yet, and returning <code>true<\/code> from <code>await_<wbr \/>suspend<\/code> allows the suspension to proceed. But if the completion handler has already run, then the previous value is <code>false<\/code>, and returning handler hasn&#8217;t run yet, and returning <code>false<\/code> from <code>await_<wbr \/>suspend<\/code> tells the coroutine infrastructure to abandon the suspension and resume the coroutine.<\/p>\n<p>Note that we use atomic operations on both sides, because the completion handler might run on another thread and race against <code>await_<wbr \/>suspend<\/code>. In particular, we need to watch out for the case where the completion handler is called immediately after <code>await_<wbr \/>suspend<\/code> sets the <code>Completed<\/code> property and checks the <code>suspending<\/code> variable, but before it returns. In that case, we need to resume the coroutine immediately from the completion handler, because the decision to allow the coroutine to suspend has already been made.<\/p>\n<p>The atomic operations use release semantics on the publishing side and acquire semantics on the consumption side so that any changes to objects immediately before completion are visible when the coroutine resumes.<\/p>\n<p>Now that we defer the resumption of the coroutine until after <code>async.<wbr \/>Completed()<\/code> returns, we don&#8217;t need to extend its lifetime to protect against premature resumption: We never resume the coroutine while <code>async.<wbr \/>Completed()<\/code> is still running.<\/p>\n<p>As of this writing, C++\/WinRT still supports Visual C++&#8217;s experimental coroutine support. Older versions of that coroutine support have a code generation bug (which <a title=\"Debugging coroutine handles: Looking for the source of a one-byte memory corruption\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220930-00\/?p=107233\"> I noted some time ago<\/a> is <a href=\"https:\/\/devblogs.microsoft.com\/cppblog\/cpp20-coroutine-improvements-in-visual-studio-2019-version-16-11\/\"> fixed in versions 16.11 and 17.0<\/a>), so we need to work around that. The way to detect the experimental coroutine support is to check for the preprocessor symbol <code>_RESUMABLE_<wbr \/>FUNCTIONS_<wbr \/>SUPPORTED<\/code>:<\/p>\n<pre>template&lt;typename Async&gt;\r\nstruct await_adapter\r\n{\r\n    \u301a ... \u301b\r\n\r\n    auto await_suspend(coroutine_handle&lt;&gt; handle) const\r\n    {\r\n        async.Completed(\r\n            disconnect_aware_handler(this, handle));\r\n<span style=\"color: #08f;\">#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED\r\n        if (!suspending.exchange(false, std::memory_order_acquire))\r\n        {\r\n            handle.resume();\r\n        }\r\n#else<\/span>\r\n        return suspending.exchange(false, std::memory_order_acquire);\r\n<span style=\"color: #08f;\">#endif<\/span>\r\n    }\r\n\r\n    \u301a ... \u301b\r\n};\r\n<\/pre>\n<p>If we are being compiled with experimental coroutine support, then we avoid the code generation bug by resuming the handle explicitly rather than returning a <code>bool<\/code>. This does consume a little bit or stack, but not as much as before.<\/p>\n<p><b>Bonus chatter<\/b>: The workaround is used if experimental coroutine support is detected, regardless of the Visual C++ compiler version. That&#8217;s because the experimental coroutines are all ABI compatible, and I don&#8217;t want to take the risk of an ODR violation if people link together object files compiled with different versions of experimental coroutine support. <a title=\"Debugging coroutine handles: The Microsoft Visual C++ compiler, clang, and gcc\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211007-00\/?p=105777\"> Visual C++ took an ABI breaking change for standard coroutines<\/a>, so C++\/WinRT uses that as its own signal to switch to the <code>bool<\/code> version of <code>await_<wbr \/>suspend<\/code>. That way, there won&#8217;t be any ODR violation in C++\/WinRT caused by linking together object files with experimental and standard coroutines: If you try, you get <!-- backref: What does it mean when I get a mismatch from MSVC for <CODE>_COROUTINE_ABI<\/CODE>? --> a mismatch from MSVC for <code>_COROUTINE_ABI<\/code>. Combining experimental and standard coroutines never worked anyway, and we rely on the compiler to check for us.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Resuming the coroutine directly, rather than consuming yet more stack.<\/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-107779","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Resuming the coroutine directly, rather than consuming yet more stack.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107779","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=107779"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107779\/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=107779"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107779"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107779"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}