{"id":105095,"date":"2021-04-14T07:00:00","date_gmt":"2021-04-14T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105095"},"modified":"2021-04-13T08:37:43","modified_gmt":"2021-04-13T15:37:43","slug":"20210414-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210414-00\/?p=105095","title":{"rendered":"C++ coroutines: Making it impossible to co_await a task twice"},"content":{"rendered":"<p>One design limitation of <a title=\"C++ coroutines: Basic implementation of a promise type\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210330-00\/?p=105019\"> the coroutine implementation we&#8217;ve been developing<\/a> is that it supports only one <code>co_await<\/code> client. We enforce this with a runtime assertion, but what if the problem occurs in the release build?<\/p>\n<p>If the two <code>co_await<\/code> clients are concurrent, then the second one overwrites the <code>m_waiting<\/code> member that was set by the first. When the coroutine completes, only the second one is woken, and the first one remains suspended forever.<\/p>\n<p>If the two <code>co_await<\/code> clients are sequential, so that the second call occurs after the coroutine has completed, then the second call bypasses the suspend and goes straight to <code>await_<wbr \/>return<\/code>, where it receives the contents of a moved-from variable. Depending on what kind of object that variable represents, it could mean that the second caller gets a copy (if the object doesn&#8217;t have a move copy constructor), or it could mean that the second caller gets an empty object (if the moved-from object is left empty), or it could mean that the second caller gets some sort of garbage (unlikely for move copy constructor, but technically legal).<\/p>\n<p>Both of these errors are hard to diagnose. In the first case, one of the tasks just stops and makes no further progress. In the second case, one of the tasks gets unreliable results.<\/p>\n<p>So let&#8217;s make it easier to diagnose.<\/p>\n<p>We made the mistake of making the task copyable. It wraps a reference-counted pointer, and copying increases the reference count. But really, when some code has a task and it passes the task to another component, it needs to coordinate ownership of the task with the other component so that only one <code>co_await<\/code> is ultimately peformed.<\/p>\n<p>In C++, ownership is typically represented by a move-only object like a <code>unique_ptr<\/code>. If you want to transfer ownership, you <code>std::move<\/code> the object to the recipient.<\/p>\n<p>So let&#8217;s make our task a move-object object. This makes it impossible to have two references to the same promise, which would otherwise tempt you to <code>co_await<\/code> twice. What&#8217;s more, let&#8217;s make the <code>co_await<\/code> operation a destructive operation by having it consumes the move-only object, leaving it empty. Once you await the task, the task object becomes null, and a subsequent <code>co_await<\/code> on the same task object will crash immediately.<\/p>\n<p>First, let&#8217;s fix our definition of <code>promise_ptr<\/code>. As a nice side-effect, it involves deleting a lot of code because our custom <code>promise_ptr<\/code> disappears.<\/p>\n<pre>    struct promise_deleter\r\n    {\r\n        void operator()(simple_promise_base&lt;T&gt;* promise) const noexcept\r\n        {\r\n            promise-&gt;decrement_ref();\r\n        }\r\n    };\r\n\r\n    template&lt;typename T&gt;\r\n    using promise_ptr = std::unique_ptr&lt;simple_promise_base&lt;T&gt;, promise_deleter&lt;T&gt;&gt;;\r\n<\/pre>\n<p>Our <code>promise_ptr<\/code> is now just a <code>unique_ptr<\/code> with a custom deleter which calls <code>decrement_ref<\/code>.<\/p>\n<p>Making <code>promise_ptr<\/code> a move-only object causes the <code>simple_task<\/code> to become a move-only object since contains a <code>promise_ptr<\/code> as a member.<\/p>\n<p>And then we make the <code>co_await<\/code> operator require an rvalue reference, so that it consumes the promise rather than merely referencing it.<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_task\r\n    {\r\n        ...\r\n\r\n        <span style=\"color: blue;\">auto operator co_await &amp;&amp;<\/span>\r\n        {\r\n            ...\r\n        }\r\n        ...\r\n    };\r\n<\/pre>\n<p>Suffixing the function declaration with <code>&amp;&amp;<\/code> means that it applies only to rvalue references.<\/p>\n<pre>simple_task&lt;Result&gt; task = SomeFunctionReturningSimpleTask();\r\nDoSomethingElseInTheMeantime();\r\nco_await task; \/\/ does not compile\r\n<\/pre>\n<p>The error message is the somewhat baffling<\/p>\n<pre style=\"white-space: pre-wrap;\">error C3312: no callable 'await_resume' function found for type 'simple_task&lt;Result&gt;'\r\n<\/pre>\n<p>And that&#8217;s if you&#8217;re lucky. If you are performing the <code>co_await<\/code> from a coroutine provided by some other library, then the error message will depend on the library (for reasons we will learn later). For example, if you are doing this from a C++\/WinRT <code>IAsync\u00adAction<\/code> or <code>IAsync\u00adOperation<\/code>, you get<\/p>\n<pre>error C2672: 'get_awaiter': no matching overloaded function found\r\n<\/pre>\n<p>The compiler is trying to figure out how to <code>co_await<\/code> a <code>simple_task<\/code> lvalue, and it can&#8217;t find anything.<\/p>\n<p>We once again enter the weird world of compiler error message metaprogramming.<\/p>\n<p>I came up with this:<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_task\r\n    {\r\n        ...\r\n        <span style=\"color: blue;\">struct cannot_await_lvalue_use_std_move {};\r\n        cannot_await_lvalue_use_std_move operator co_await() &amp; = delete;<\/span>\r\n        ...\r\n    };\r\n<\/pre>\n<p>If we provide no way to await an lvalue, then the compiler will report an error based on where in the evaluation process it finally got stuck. So let&#8217;s make it get stuck at a predictable place, with a name we get to control.<\/p>\n<pre style=\"white-space: pre-wrap;\">error C3312: no callable 'await_resume' function found for type 'simple_task&lt;Result&gt;::<wbr \/>cannot_await_lvalue_use_std_move'\r\n<\/pre>\n<p>The C++\/WinRT custom awaiter error message remains, however. We can hack around this by fooling C++\/WinRT into thinking that we are awaitable, and then get the compiler to generate the error message that contains our custom error message disguised as a class name.<\/p>\n<pre>        struct cannot_await_lvalue_use_std_move { <span style=\"color: blue;\">void await_ready() {}<\/span> };\r\n<\/pre>\n<p>The error message is now<\/p>\n<pre style=\"white-space: pre-wrap;\">error C2039: 'await_resume': is not a member of 'simple_task&lt;Result&gt;::<wbr \/>cannot_await_lvalue_use_std_move'\r\n<\/pre>\n<p>That&#8217;s a little better.<\/p>\n<p>Okay, so I left a bunch of <code>...<\/code> inside the body of <code>operator co_await &amp;&amp;<\/code>. We need to move the <code>promise_ptr<\/code> into the awaiter, and that means having to do some restructuring of the promise&#8217;s awaiter code. Nothing essential has changed; we just need to appease the compiler.<\/p>\n<p>To avoid a circular reference between the <code>simple_<wbr \/>promise<\/code> and the <code>promise_<wbr \/>ptr<\/code>, I&#8217;ll pull the awaiter out into a separate class and have it forward its methods back into the promise. The order of declaration is<\/p>\n<ul>\n<li><code>simple_promise_base<\/code><\/li>\n<li><code>promise_ptr<\/code><\/li>\n<li><code>promise_awaiter<\/code><\/li>\n<\/ul>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_promise_base\r\n    {\r\n        ...\r\n\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">auto get_awaiter()<\/span><\/span>\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">{ ... }<\/span><\/span>\r\n\r\n        ...\r\n    };\r\n\r\n    struct promise_deleter\r\n    {\r\n        void operator()(simple_promise_base&lt;T&gt;* promise) const noexcept\r\n        {\r\n            promise-&gt;decrement_ref();\r\n        }\r\n    };\r\n\r\n    <span style=\"color: blue;\">template&lt;typename T&gt;\r\n    struct promise_awaiter\r\n    {\r\n        promise_ptr&lt;T&gt; self;\r\n\r\n        bool await_ready()\r\n        {\r\n            return self-&gt;client_await_ready();\r\n        }\r\n\r\n        auto await_suspend(std::experimental::coroutine_handle&lt;&gt; handle)\r\n        {\r\n            return self-&gt;client_await_suspend(handle);\r\n        }\r\n\r\n        T await_resume()\r\n        {\r\n            return self-&gt;client_await_resume();\r\n        }\r\n    };<\/span>\r\n<\/pre>\n<p>We take the <code>get_awaiter<\/code> anonymous awaiter and give it a name: <code>promise_awaiter<\/code>. This <code>promise_awaiter<\/code> is a separate class (avoiding the circular reference), and it retains its <code>self<\/code> in the form of a <code>promise_ptr<\/code>. This causes the promise to be released when the awaiter destructs at the end of the <code>co_await<\/code>.<\/p>\n<p>Now we can fill in those missing dots.<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_task\r\n    {\r\n        ...\r\n\r\n        auto operator co_await &amp;&amp;\r\n        {\r\n            <span style=\"color: blue;\">return details::promise_awaiter&lt;T&gt;\r\n                { std::move(promise) };<\/span>\r\n        }\r\n        ...\r\n    };\r\n<\/pre>\n<p>We can now write<\/p>\n<pre>simple_task&lt;Result&gt; task = SomeFunctionReturningSimpleTask();\r\nDoSomethingElseInTheMeantime();\r\nco_await std::move(task); \/\/ explicit move\r\n<\/pre>\n<p>The explicit <code>std::move<\/code> makes it clear that you are giving the task to <code>co_await<\/code>, and that the task is no longer usable after that point. Furthermore, if you try to <code>co_await<\/code> it, you will take a null pointer exception since the task is now empty. We used to have a mysterious bug where <code>co_await<\/code> sometimes seemed to hang, or sometimes produced incorrect results. Now we have an immediate crash, which is much easier to diagnose.<\/p>\n<p>Next time, we&#8217;ll get rid of the mutex that protects the <code>coroutine_handle&lt;&gt;<\/code> which records the continuation.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Always keep moving: Make the <CODE>co_await<\/CODE> consume the task.<\/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-105095","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Always keep moving: Make the <CODE>co_await<\/CODE> consume the task.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105095","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=105095"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105095\/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=105095"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105095"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105095"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}