{"id":109710,"date":"2024-04-30T07:00:00","date_gmt":"2024-04-30T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109710"},"modified":"2024-04-30T09:52:45","modified_gmt":"2024-04-30T16:52:45","slug":"20240430-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240430-00\/?p=109710","title":{"rendered":"Awaiting a set of handles with a timeout, part 1: Starting with two"},"content":{"rendered":"<p>Suppose you want a C++ coroutine that waits for a bunch of kernel handles to be signaled, and gives up if a timeout is reached. And you want the coroutine to tell you which handles were signaled and which timed out.<\/p>\n<p>Let&#8217;s start by using something we already have, namely the C++\/WinRT <code>resume_<wbr \/>on_<wbr \/>signal<\/code> coroutine that awaits a single handle with a timeout. Maybe we can use that.<\/p>\n<pre>#include &lt;array&gt;\r\n#include &lt;winrt\/Windows.Foundation.h&gt;\r\n#include &lt;wil\/coroutine.h&gt;\r\n\r\nwil::task&lt;std::array&lt;bool, 2&gt;&gt;\r\n    resume_on_both_signaled(HANDLE h1, HANDLE h2,\r\n        winrt::Windows::Foundation::TimeSpan timeout = {})\r\n{\r\n    auto await1 = winrt::resume_on_signal(h1, timeout);\r\n    auto await2 = winrt::resume_on_signal(h2, timeout);\r\n    co_return std::array&lt;bool, 2&gt;\r\n        { co_await await1, co_await await2 };\r\n}\r\n<\/pre>\n<p>The idea is that we call <code>resume_<wbr \/>on_<wbr \/>signal<\/code> for all of the handles before we start <code>co_await<\/code>ing, because we want the timeout for all of the awaits to begin at the time that <code>resume_<wbr \/>on_<wbr \/>both_<wbr \/>signaled<\/code> is called.<\/p>\n<p>&nbsp;<\/p>\n<p>Unfortunately, it doesn&#8217;t work.<\/p>\n<p>The awaiter returned by <code>resume_<wbr \/>on_<wbr \/>signal<\/code> doesn&#8217;t start the timeout timer until you <code>co_await<\/code> it, so what we end up doing is waiting for the first handle to become signaled with a timeout of <code>timeout<\/code>, and then only after that happens do we wait for the second handle to become signaled, also with a timeout of <code>timeout<\/code>, and the second timeout doesn&#8217;t start until the first one finishes.<\/p>\n<p>So it wasn&#8217;t any improvement over<\/p>\n<pre>    co_return std::array&lt;bool, 2&gt;\r\n        { co_await winrt::resume_on_signal(h1, timeout),\r\n          co_await winrt::resume_on_signal(h2, timeout) };\r\n<\/pre>\n<p>Another problem is that the <code>resume_<wbr \/>on_<wbr \/>signal<\/code> returns an awaiter that expects to be immediately-awaited, so we&#8217;re taking a chance by saving it into a local variable and awaiting it later.<\/p>\n<p>Some awaiters save references to their parameters to avoid a copy, assuming that the awaiter will be awaited immediately before the parameters are destructed, so we have to make sure that all of the parameters we pass have their lifetime extended past the <code>co_await<\/code>.<\/p>\n<p>Furthermore, some awaiters may not function properly if you move them. For example, the awaiter may register a callback function and pass its own <code>this<\/code> as the callback data. If you move the awaiter, then when the callback runs, it will try to access the awaiter at the location it <i>used to be<\/i>, which is a use-after-free bug.<\/p>\n<p>This is a hazard of awaiters, since they are written with the expectation that they will be passed directly to <code>co_await<\/code>, and the possibility that they will be saved and copied or moved may not have occurred to the authors.<\/p>\n<p>Let&#8217;s try to repair these problems next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s see by seeing if we can do it with just two.<\/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-109710","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Let&#8217;s see by seeing if we can do it with just two.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109710","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=109710"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109710\/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=109710"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109710"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109710"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}