{"id":105063,"date":"2021-04-08T07:00:00","date_gmt":"2021-04-08T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105063"},"modified":"2021-04-06T21:50:40","modified_gmt":"2021-04-07T04:50:40","slug":"20210408-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210408-00\/?p=105063","title":{"rendered":"C++ coroutines: Awaiting the simple_task"},"content":{"rendered":"<p>Last time, we <a title=\"C++ coroutines: Accepting types via return_void and return_value\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210407-00\/?p=105061\"> accepted the parameter passed to <code>co_return<\/code> and stored it into our promise<\/a>. This time, we&#8217;ll deal with the consumption side and wait for the answer to appear.<\/p>\n<pre>        \/\/ \u27e6awaiter support methods\u27e7 \u2254\r\n        bool client_await_ready()\r\n        {\r\n            assert(m_waiting == nullptr);\r\n            return !m_holder.is_empty();\r\n        }\r\n\r\n        auto client_await_suspend(\r\n            std::experimental::coroutine_handle&lt;&gt; handle)\r\n        {\r\n            auto guard = std::lock_guard{ m_mutex };\r\n            if (!m_holder.is_empty()) return false;\r\n            m_waiting = handle;\r\n            return true;\r\n        }\r\n\r\n        T client_await_resume()\r\n        {\r\n            return m_holder.get_value();\r\n        }\r\n\r\n        auto get_awaiter() noexcept\r\n        {\r\n            \/\/ \u27e6return an awaiter that waits for the coroutine\r\n            \/\/ to complete\u27e7 \u2254\r\n            struct awaiter\r\n            {\r\n                simple_promise_base&amp; self;\r\n\r\n                bool await_ready()\r\n                {\r\n                    return self.client_await_ready();\r\n                }\r\n\r\n                auto await_suspend(\r\n                    std::experimental::coroutine_handle&lt;&gt; handle)\r\n                {\r\n                    return self.client_await_suspend(handle);\r\n                }\r\n\r\n                T await_resume()\r\n                {\r\n                    return self.client_await_resume();\r\n                }\r\n            };\r\n            return awaiter{ *this };\r\n        }\r\n<\/pre>\n<p>The real work happens in the <code>client_<\/code> methods in the <code>simple_<wbr \/>promise_<wbr \/>base<\/code>, and the awaiter just forwards everything to those methods, so I&#8217;m going to talk about the awaiter and the <code>client_<\/code> methods as if they were the same thing.<\/p>\n<p>Our awaiter&#8217;s <code>await_<wbr \/>ready<\/code> first asserts that nobody else is waiting for promise. We allow only one <code>co_await<\/code> because multiple <code>co_await<\/code> is not compatible with a move-only type: If the type is move-only, then you can&#8217;t return it more than once because returning it also gives it away. There&#8217;s nothing to return to the second <code>co_await<\/code>.<\/p>\n<p>Moving the value in response to <code>co_await<\/code> also avoids potentially-expensive copies.<\/p>\n<p>After the correctness check, we see if the awaited-for coroutine is still running by seeing if the result holder is still empty. If it&#8217;s not empty, then the coroutine has already produced a result; we return <code>true<\/code> to go straight to <code>await_<wbr \/>resume<\/code>.<\/p>\n<p>If <code>await_<wbr \/>ready<\/code> concludes that the awaited-for coroutine is still running, then the compiler will suspend the current coroutine and then call <code>await_<wbr \/>suspend<\/code>. We use a mutex for this, because we need to avoid a race between signing up for resumption and the awaited-for coroutine reaching its <code>final_<wbr \/>suspend<\/code> (which resumes the coroutine). We make one last check if the awaited-for coroutine is still running, to close the race window where the awaited-for coroutine finishes in between the <code>await_<wbr \/>ready<\/code> and the acquisition of the lock in <code>await_<wbr \/>suspend<\/code>.<\/p>\n<p>When the coroutine resumes, <code>await_<wbr \/>resume<\/code> produces the value or rethrows the exception. Note that we specify the return type explicitly as <code>T<\/code> rather than using <code>auto<\/code>. This is important in the case where <code>T<\/code> is a reference.<\/p>\n<p>The resumption occurs when the awaited-for coroutine reaches its <code>final_<wbr \/>suspend<\/code>.<\/p>\n<pre>        auto final_suspend() noexcept\r\n        {\r\n            \/\/ \u27e6return an awaiter that decrements the reference count\r\n            \/\/  and resumes any awaiting coroutine\u27e7 \u2254\r\n            struct awaiter : std::experimental::suspend_always\r\n            {\r\n                simple_promise_base&amp; self;\r\n                void await_suspend(std::experimental::coroutine_handle&lt;&gt;) const noexcept\r\n                {\r\n                    std::experimental::coroutine_handle&lt;&gt; handle;\r\n                    {\r\n                        auto guard = std::lock_guard{ self.m_mutex };\r\n                        handle = self.m_waiting;\r\n                    }\r\n                    self.decrement_ref();\r\n                    if (handle) handle.resume();\r\n                }\r\n            };\r\n            return awaiter{ {}, *this };\r\n        }\r\n<\/pre>\n<p>At final suspension, we first check to see if a coroutine is actively awaiting our result. This requires the mutex to avoid racing against the <code>get_awaiter<\/code> we saw above.<\/p>\n<p>Once we capture the awaiting coroutine&#8217;s handle (if any), we decrement our own reference count, since the coroutine is no longer running. The only reference count remaining belongs to the <code>simple_<wbr \/>task<\/code>. (If the caller threw away the <code>simple_<wbr \/>task<\/code> without awaiting it, then that decrement will destruct the coroutine state immediately.)<\/p>\n<p>And then we resume the awaiting coroutine, if any. When that awaiting coroutine destructs the <code>simple_<wbr \/>task<\/code>, that will drop the reference count to zero and destruct the coroutine state.<\/p>\n<p>The last missing piece is the reference count management. Sadly, this is the largest single piece of the entire coroutine infrastructure, and it&#8217;s almost entirely uninteresting! We&#8217;ll take up the boring details next time.<\/p>\n<p><b>Bonus chatter<\/b>: It&#8217;s important that we wait until <code>await_<wbr \/>suspend<\/code> to decrement the reference on the promise, rather than doing it eagerly in <code>await_<wbr \/>ready<\/code>. The <code>await_<wbr \/>ready<\/code> method is called while the coroutine is still in the executing state, and you cannot destruct an executing coroutine. On the other hand, <code>await_<wbr \/>suspend<\/code> is called after the coroutine has transitioned to the suspended state, at which point it is now safe to destroy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let me know when it&#8217;s ready.<\/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-105063","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Let me know when it&#8217;s ready.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105063","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=105063"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105063\/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=105063"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105063"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105063"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}