{"id":109693,"date":"2024-04-22T07:00:00","date_gmt":"2024-04-22T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109693"},"modified":"2024-04-21T21:57:52","modified_gmt":"2024-04-22T04:57:52","slug":"20240422-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240422-00\/?p=109693","title":{"rendered":"Adding state to the update notification pattern, part 4"},"content":{"rendered":"<p>Last time, we developed <a title=\"Adding state to the update notification pattern, part 3\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240419-00\/?p=109689\"> a stateful but coalescing update notification<\/a>, and we noted that the UI thread was doing a lot of heavy lifting. What if you don&#8217;t have a UI thread to do implicit serialization for you?<\/p>\n<p>If there were no <code>resume_foreground(<wbr \/>Dispatcher())<\/code>, we would have a race if a <code>Text\u00adChanged<\/code> occurs after the worker has decided to exit, but before it has had a chance to mark itself as not busy. Here&#8217;s an alternate version that demonstrates the race.<\/p>\n<pre>class EditControl\r\n{\r\n    \u27e6 ... existing class members ... \u27e7\r\n\r\n    std::atomic&lt;bool&gt; m_busy;\r\n    std::mutex m_mutex;\r\n    std::optional&lt;string&gt; m_pendingText;\r\n};\r\n\r\nwinrt::fire_and_forget\r\nEditControl::TextChanged(std::string text)\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    ExchangePendingText(std::move(text));\r\n    if (m_busy.exchange(true)) {\r\n        co_return;\r\n    }\r\n\r\n    co_await winrt::resume_background();\r\n\r\n    while (auto pendingText = ExchangePendingText(std::nullopt);\r\n           pendingText) {\r\n        auto matches = BuildMatches(*pendingText);\r\n\r\n        if (matches) {\r\n            SetAutocomplete(*matches);\r\n        }\r\n    }\r\n    m_busy = false;\r\n}\r\n<\/pre>\n<p>In this alternate version, the <code>m_mutex<\/code> is critical because the background thread picks up the <code>m_pendingText<\/code> for the next iteration. But now there is a race window if a change to the pending text occurs immediately after we notice that there is no pending text and before we clear the busy flag.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<th style=\"border: solid 1px currentcolor; border-top: none; padding: 0 1ex;\">UI thread<\/th>\n<th style=\"border: solid 1px currentcolor; border-top: none; padding: 0 1ex;\">Background thread<\/th>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\"><tt>TextChanged(\"Bob\")<\/tt><br \/>\n<tt>ExchangePendingText(\"Bob\")<\/tt><br \/>\n<tt>m_busy = true;<\/tt><br \/>\n<tt>resume_background()<\/tt><\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\"><tt>BuildMatches(\"Bob\");<\/tt><br \/>\n<tt>SetAutocomplete(*matches)<\/tt><br \/>\nNo pending text, so exit <tt>while<\/tt> loop<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\"><tt>TextChanged(\"Alice\");<\/tt><br \/>\n<tt>ExchangePendingText(\"Alice\")<\/tt><br \/>\n<tt>m_busy<\/tt> already <tt>true<\/tt><br \/>\n<tt>co_return;<\/tt><\/td>\n<td style=\"padding: 0 1ex;\">\u00a0<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px currentcolor; padding: 0 1ex;\">\u00a0<\/td>\n<td style=\"padding: 0 1ex;\"><tt>m_busy = false;<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>To avoid this race, <code>m_busy<\/code> needs to move under the mutex. And once it&#8217;s moved under the mutex, it doesn&#8217;t need to be atomic any more. The need to extend the scope of the mutex means that our cute little helper functions won&#8217;t really cut it any more. We&#8217;ll have to manage the locks ourselves.<\/p>\n<pre>class EditControl\r\n{\r\n    \u27e6 ... existing class members ... \u27e7\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">bool m_busy = false;<\/span>\r\n    std::mutex m_mutex;\r\n    std::optional&lt;string&gt; m_pendingText;\r\n};\r\nwinrt::fire_and_forget\r\nEditControl::TextChanged(std::string text)\r\n{\r\n    auto lifetime = get_strong();\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">{                                         <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    auto lock = std::unique_lock(m_mutex);<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_pendingText = std::move(text);      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (std::exchange(m_busy, true)) {    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        co_return;                        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                     <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                         <\/span>\r\n\r\n    co_await winrt::resume_background();\r\n\r\n    while (true) {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">{                                         <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    auto lock = std::unique_lock(m_mutex);<\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (!m_pendingText) {                 <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">        m_busy = false;                   <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">        co_return;                        <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                     <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    text = std::move(*m_pendingText);     <\/span>\r\n        <span style=\"border: 1px currentcolor; border-style: none solid;\">    m_pendingText.release();              <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                         <\/span>\r\n\r\n        auto matches = BuildMatches(text);\r\n\r\n        if (matches) {\r\n            SetAutocomplete(*matches);\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Next time, we&#8217;ll solve the same problem using a different approach.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What if the UI thread isn&#8217;t there to save you?<\/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-109693","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>What if the UI thread isn&#8217;t there to save you?<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109693","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=109693"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109693\/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=109693"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109693"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109693"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}