{"id":107772,"date":"2023-02-01T07:00:00","date_gmt":"2023-02-01T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107772"},"modified":"2023-01-30T19:55:39","modified_gmt":"2023-01-31T03:55:39","slug":"20230201-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230201-00\/?p=107772","title":{"rendered":"Inside C++\/WinRT: Coroutine completion handlers: Disconnection"},"content":{"rendered":"<p>C++\/WinRT relies on the <code>Completed<\/code> delegate to tell it when a Windows Runtime asynchronous operation is complete. However, it&#8217;s possible that the <code>IAsyncAction<\/code> or <code>IAsyncOperation<\/code> provider tears itself down without ever calling the <code>Completed<\/code> handler. This typically happens when the provider is running in another process that crashes (or at least <a title=\"Yo dawg, I hear you like COM apartments, so I put a COM apartment in your COM apartment so you can COM apartment while you COM apartment\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191126-00\/?p=103140\"> disconnects from you<\/a>). It never calls its completion handler, and the coroutine simple gets leaked.<\/p>\n<p>Here&#8217;s what you see in the debugger:<\/p>\n<pre style=\"white-space: pre-wrap;\">contoso!winrt::impl::implements_delegate&lt;AsyncActionCompletedHandler,lambda_xxxx&gt;::Release+0x64\r\ncombase!&lt;lambda_yyy&gt;::operator()+0xd7\r\ncombase!ObjectMethodExceptionHandlingAction&lt;&lt;lambda_yyy&gt; &gt;+0xe\r\ncombase!CStdIdentity::ReleaseCtrlUnk+0x64\r\ncombase!CStdMarshal::DisconnectWorker_ReleasesLock+0x6e7\r\ncombase!CStdMarshal::DisconnectAndReleaseWorker_ReleasesLock+0x35\r\ncombase!CStdMarshal::DisconnectForRundownIfAppropriate+0xc9\r\ncombase!CRemoteUnknown::RundownOidWorker+0x241\r\ncombase!CRemoteUnknown::RundownOid+0x65\r\nRPCRT4!Invoke+0x73\r\nRPCRT4!NdrStubCall2+0x3db\r\nRPCRT4!NdrStubCall3+0xee\r\ncombase!CStdStubBuffer_Invoke+0x6f\r\ncombase!InvokeStubWithExceptionPolicyAndTracing::__l6::&lt;lambda_zzz&gt;::operator()+0x22\r\ncombase!ObjectMethodExceptionHandlingAction&lt;&lt;lambda_zzz&gt; &gt;+0x4d\r\ncombase!InvokeStubWithExceptionPolicyAndTracing+0xe1\r\ncombase!DefaultStubInvoke+0x268\r\ncombase!SyncServerCall::StubInvoke+0x41\r\ncombase!StubInvoke+0x303\r\ncombase!ServerCall::ContextInvoke+0x517\r\ncombase!ComInvokeWithLockAndIPID+0x9a9\r\ncombase!ThreadInvokeReturnHresult+0x17b\r\ncombase!ThreadInvoke+0x193\r\nRPCRT4!DispatchToStubInCNoAvrf+0x22\r\nRPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1b4\r\nRPCRT4!RPC_INTERFACE::DispatchToStub+0xb3\r\nRPCRT4!RPC_INTERFACE::DispatchToStubWithObject+0x188\r\nRPCRT4!LRPC_SBINDING::DispatchToStubWithObject+0x23\r\nRPCRT4!LRPC_SCALL::DispatchRequest+0x14c\r\nRPCRT4!LRPC_SCALL::QueueOrDispatchCall+0x253\r\nRPCRT4!LRPC_SCALL::HandleRequest+0x996\r\nRPCRT4!LRPC_SASSOCIATION::HandleRequest+0x2c3\r\nRPCRT4!LRPC_ADDRESS::HandleRequest+0x17c\r\nRPCRT4!LRPC_ADDRESS::ProcessIO+0x939\r\nRPCRT4!LrpcIoComplete+0x109\r\nntdll!TppAlpcpExecuteCallback+0x157\r\nntdll!TppWorkerThread+0x72c\r\nKERNEL32!BaseThreadInitThunk+0x1d\r\nntdll!RtlUserThreadStart+0x28\r\n<\/pre>\n<p>The way we address this is to have the completion handler detect that it was never invoked. If that happens, then it simply invokes itself. On resumption, the coroutine will call <code>GetResults()<\/code> on the asynchronous operation, and that will throw the appropriate RPC error.<\/p>\n<p>Keeping track of whether the handler was invoked requires a custom destructor, so we&#8217;ll convert the lambda to a C++ class first, so that we can add a destructor. This conversion is mechanical.<\/p>\n<pre>\/\/ Original lambda\r\n[\r\n    handle,\r\n    this,\r\n    context = resume_apartment_context()\r\n](auto&amp;&amp; ...)\r\n{\r\n    resume_apartment(context.context, handle,\r\n        &amp;failure);\r\n});\r\n\r\n\/\/ Converted to explicit class\r\n\r\ntemplate&lt;typename Awaiter&gt;\r\nstruct disconnect_aware_handler\r\n{\r\n    disconnect_aware_handler(Awaiter* awaiter,\r\n        coroutine_handle&lt;&gt; handle) noexcept\r\n        m_awaiter(awaiter), m_handle(handle) {}\r\n\r\n    template&lt;typename...Args&gt;\r\n    void operator()(Args&amp;&amp;...)\r\n    {\r\n        resume_apartment(m_context.context, m_handle,\r\n            &amp;m_awaiter-&gt;failure);\r\n    }\r\n\r\nprivate:\r\n    Awaiter* m_awaiter;\r\n    coroutine_handle&lt;&gt; m_handle;\r\n    resume_apartment_context m_context;\r\n};\r\n\r\ntemplate&lt;typename Async&gt;\r\nstruct await_adapter\r\n{\r\n    \u301a ... \u301b\r\n\r\n    void await_suspend(coroutine_handle&lt;&gt; handle) const\r\n    {\r\n        auto extend_lifetime = async;\r\n        async.Completed(\r\n            <span style=\"color: #08f;\">disconnect_aware_handler(this, handle)<\/span>);\r\n    }\r\n\r\n    \u301a ... \u301b\r\n};\r\n<\/pre>\n<p>Okay, now we can add a destructor that calls the <code>operator()<\/code> if it had never been called. We&#8217;ll factor the body into a method <code>Complete()<\/code> and use the null-ness of the <code>m_handle<\/code> to tell us whether the operator has been invoked yet.<\/p>\n<pre>template&lt;typename Awaiter&gt;\r\nstruct disconnect_aware_handler\r\n{\r\n    disconnect_aware_handler(Awaiter* awaiter,\r\n        coroutine_handle&lt;&gt; handle) noexcept\r\n        m_awaiter(awaiter), m_handle(handle) {}\r\n\r\n    <span style=\"color: #08f;\">~disconnect_aware_handler()\r\n    {\r\n        if (m_handle) Complete();\r\n    }<\/span>\r\n\r\n    template&lt;typename...Args&gt;\r\n    void operator()(Args&amp;&amp;...)\r\n    {\r\n        <span style=\"color: #08f;\">Complete();<\/span>\r\n    }\r\n\r\nprivate:\r\n    Awaiter* m_awaiter;\r\n    coroutine_handle&lt;&gt; m_handle;\r\n    resume_apartment_context m_context;\r\n\r\n    <span style=\"color: #08f;\">void Complete()\r\n    {<\/span>\r\n        resume_apartment(m_context.context,\r\n            <span style=\"color: #08f;\">std::exchange(m_handle, {})<\/span>,\r\n            &amp;m_awaiter-&gt;failure);\r\n    <span style=\"color: #08f;\">}<\/span>\r\n};\r\n<\/pre>\n<p>If you try this, though, it fails miserably: The delegate constructor moves the functor into the newly-constructed delegate, but <code>coroutine_<wbr \/>handle<\/code>&#8216;s move constructor simply copies the coroutine handle. This means that when the delegate constructor moves the functor, the temporary functor destructs and says, &#8220;Oh no, I was never invoked! I must have been disconnected!&#8221;, and it resumes the coroutine. And then when the coroutine completes for real, the invoke occurs a second time, and we have resumed a running coroutine, which is illegal.<\/p>\n<p>We need custom move operators that null out the coroutine handle in the moved-from object. This is another case where we could have used the <a title=\"Making C++ primitive types meaningfully movable when they have sentinel values\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230116-00\/?p=107717\"> <code>movable_primitive<\/code> template type<\/a>, but C++\/WinRT just writes it out by hand.<\/p>\n<pre>    disconnect_aware_handler(disconnect_aware_handler&amp;&amp; other) noexcept\r\n        : m_context(std::move(other.m_context))\r\n        , m_awaiter(std::exchange(other.m_awaiter, {}))\r\n        , m_handle(std::exchange(other.m_handle, {})) { }\r\n<\/pre>\n<p>We null out the <code>m_awaiter<\/code> just for good measure.<\/p>\n<p>If you see a coroutine resumption from <code>disconnect_<wbr \/>aware_<wbr \/>handler<\/code>&#8216;s destructor when debugging, then that is a sign that the coroutine is resuming due to a disconnection from the Windows Runtime asynchronous operation provider.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When the other end hangs up without even saying good-bye.<\/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-107772","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>When the other end hangs up without even saying good-bye.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107772","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=107772"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107772\/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=107772"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107772"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107772"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}