{"id":105135,"date":"2021-04-21T07:00:00","date_gmt":"2021-04-21T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105135"},"modified":"2021-04-22T06:02:44","modified_gmt":"2021-04-22T13:02:44","slug":"20210421-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210421-00\/?p=105135","title":{"rendered":"C++ coroutines: Cold-start coroutines"},"content":{"rendered":"<p>So far, our coroutine promise has implemented a so-called <i>hot-start<\/i> coroutine, which is one that begins running as soon as it is created. Another model for coroutines is the so-called <i>cold-start<\/i> coroutine, which is one that doesn&#8217;t start running until it is awaited.<\/p>\n<p>C# and JavaScript use the hot-start model: Creating the coroutine runs the coroutine body synchronously to its first suspension point, and only after the coroutine suspends for the first time is the coroutine returned to the caller. The usual usage pattern is to create the coroutine, and then go do other stuff while the coroutine is running, on the assumption that the synchronous portion of the coroutine is brief, and the expensive portion runs asynchronously. The hot-start model makes it easy to start multiple coroutines in parallel, and await the combined result.<\/p>\n<p>Python uses cold-start coroutines: The coroutine doesn&#8217;t start running until you await it. With cold-start coroutines, you need other machinery if you want to do work in parallel with the await, although that machinery could be made relative simply, like Python&#8217;s <code>create_task<\/code> that runs a coroutine in an event loop. Cold-start coroutines have simpler bookkeeping since the <i>running<\/i> and <i>awaiting<\/i> states are identical, which makes a lot of state transitions impossible.<\/p>\n<p>You could also create a hybrid model where the coroutine is cold-start, but can be manually started. Mind you, doing so reintroduces the state transitions you thought you had simplified away.<\/p>\n<p>The C++ language doesn&#8217;t take a position on whether coroutines are hot-start or cold-start, or some hybrid of the two. It just provides the underlying infrastructure, and it&#8217;s up to you to decide what you want to build on top of it.<\/p>\n<p>If we define the initial state as <i>cold<\/i>, then our valid state transitions are as follows:<\/p>\n<ul>\n<li>cold \u2192 running \u2192 completed \u2192 abandoned: This is the common case where the task is awaited and then runs to completion.<\/li>\n<li>cold \u2192 abandoned: This is the case where the coroutine is abandoned without ever starting.<\/li>\n<\/ul>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"position: relative;\">\n<div style=\"position: absolute; bottom: 0;\">From<\/div>\n<div style=\"position: absolute; top: 0; right: 0;\">To<\/div>\n<\/td>\n<td>running<\/td>\n<td>completed<\/td>\n<td>abandoned<\/td>\n<\/tr>\n<tr>\n<td>cold<\/td>\n<td>Resume coroutine<\/td>\n<td style=\"background-color: #808080;\">\u00a0<\/td>\n<td>Destroy promise<\/td>\n<\/tr>\n<tr>\n<td>running<\/td>\n<td style=\"background-color: #808080;\">\u00a0<\/td>\n<td>Resume awaiter<\/td>\n<td style=\"background-color: #808080;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td>completed<\/td>\n<td style=\"background-color: #808080;\">\u00a0<\/td>\n<td style=\"background-color: #808080;\">\u00a0<\/td>\n<td>Destroy promise<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The nice thing about cold-start coroutines is that there are very few transitions, and none of them are contended. Furthermore, the state is completely implied by the actions of the task, so we don&#8217;t even need to keep track of it explicitly.<\/p>\n<p>Here&#8217;s a sketch of the changes we can make to convert our hot-start coroutine promise to cold-start. Don&#8217;t incorporate these yet, for reasons we&#8217;ll see next time.<\/p>\n<pre>    template&lt;typename T&gt;\r\n    struct simple_promise_base\r\n    {\r\n        ...\r\n\r\n        <span style=\"color: blue;\">std::experimental::coroutine_handle&lt;&gt; m_waiting{ nullptr };<\/span>\r\n        simple_promise_result_holder&lt;T&gt; m_holder;\r\n\r\n        ...\r\n\r\n        void abandon()\r\n        {\r\n            <span style=\"color: blue;\">destroy();<\/span>\r\n        }\r\n\r\n        <span style=\"color: blue;\">std::experimental::suspend_always<\/span> initial_suspend() noexcept\r\n        {\r\n            return {};\r\n        }\r\n\r\n        auto final_suspend() noexcept\r\n        {\r\n            struct awaiter : std::experimental::suspend_always\r\n            {\r\n                simple_promise_base&amp; self;\r\n                void await_suspend(\r\n                    std::experimental::coroutine_handle&lt;&gt;)\r\n                    const noexcept\r\n                {\r\n                    <span style=\"color: blue;\">self.m_waiting();<\/span>\r\n                }\r\n            };\r\n            return awaiter{ {}, *this };\r\n        }\r\n\r\n        bool client_await_ready()\r\n        {\r\n            <span style=\"color: blue;\">return false;<\/span>\r\n        }\r\n\r\n        auto client_await_suspend(\r\n            std::experimental::coroutine_handle&lt;&gt; handle)\r\n        {\r\n            m_waiting = handle;\r\n            <span style=\"color: blue;\">as_handle().resume();<\/span>\r\n        }\r\n\r\n        ...\r\n    };\r\n<\/pre>\n<p>What makes this a cold-start coroutine is the fact that the <code>initial_<wbr \/>suspend<\/code> is a <code>suspend_<wbr \/>always<\/code> rather than a <code>suspend_<wbr \/>never<\/code>. This means that the coroutine body doesn&#8217;t start until the coroutine is explicitly <code>resume()<\/code>d.<\/p>\n<p>The other state transitions are significantly simplified. Destroying the coroutine doesn&#8217;t need to check whether the coroutine is running, because it happens either before the coroutine even starts, or after it has completed, never when the coroutine is runinng. Completing the coroutine can always resume the <code>m_waiting<\/code> coroutine, since the awaiter registers completion before resuming, so the resumption handle is known to be valid by this point.<\/p>\n<p>The other wrinkle about cold-start coroutines is that the awaiter is responsible for starting it, which we do by calling <code>resume()<\/code>.<\/p>\n<p>You may have noticed an inefficiency here: If the coroutine completes synchronously,\u00b9 then we end up calling into the coroutine&#8217;s <code>resume()<\/code>, and the completion calls back into the awaiter&#8217;s <code>resume()<\/code>. This accumulates stack frames, which is a problem for a coroutine that awaits other synchronously-completing coroutines in a loop, since each time through the loop uses another level of stack.<\/p>\n<p>We&#8217;ll address this problem <a title=\"C++ coroutines: Improving cold-start coroutines which complete synchronously\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210422-00\/?p=105139\"> next time<\/a>, and it will require us to bring back some of the code we deleted, which is why I warned you not to incorporate it yet.<\/p>\n<p>\u00b9 &#8220;But why bother making it a coroutine if it completes synchronously?&#8221; The operation might complete synchronously under certain conditions, but asynchronously under other conditions. For example, the operation might complete asynchronously if a helper object needs to be started, but synchronously if the helper object is already up and running.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>On your mark, get set, wait for it!<\/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-105135","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>On your mark, get set, wait for it!<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105135","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=105135"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105135\/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=105135"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105135"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105135"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}