{"id":106858,"date":"2022-07-12T07:00:00","date_gmt":"2022-07-12T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106858"},"modified":"2022-11-30T06:52:51","modified_gmt":"2022-11-30T14:52:51","slug":"20220712-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220712-00\/?p=106858","title":{"rendered":"Processing a ValueSet or PropertySet even in the face of possible mutation, part 1"},"content":{"rendered":"<p>We&#8217;ve been looking at how you can non-intrusively monitor changes to a ValueSet or PropertySet. Typically this is so that you can take any changes made by the client and propagate them somewhere.<\/p>\n<p>Let&#8217;s say that you want to take the modified collection and save the whole thing to disk. How can you do this?<\/p>\n<p>Well, your first attempt might be to do this:<\/p>\n<pre>void MyPropertySet::Save()\r\n{\r\n    SomeKindOfDataBuffer buffer;\r\n    for (auto [key, value] : m_propertySet) {\r\n        buffer.AddKeyAndValue(key, value);\r\n    }\r\n    SaveToFile(buffer);\r\n}\r\n<\/pre>\n<p>Iterating over the wrapped collection and saving the results is a good start, but the iterator will throw <code>hresult_<wbr \/>changed_<wbr \/>state<\/code> if the collection changes during the iteration.<\/p>\n<p>What we need to do is capture the collection, and when we succeed in capturing it all, we can save it. The copy can&#8217;t mutate since we haven&#8217;t given anybody else access to it, so iterating over it is safe from an <code>hresult_<wbr \/>changed_<wbr \/>state<\/code> exception.<\/p>\n<pre>void MyPropertySet::Save()\r\n{\r\n    <span style=\"color: #08f;\">auto copy = CapturePropertySet(m_propertySet);<\/span>\r\n\r\n    SomeKindOfDataBuffer buffer;\r\n    for (auto [key, value] : <span style=\"color: #08f;\">copy<\/span>) {\r\n        buffer.AddKeyAndValue(key, value);\r\n    }\r\n    SaveToFile(buffer);\r\n}\r\n<\/pre>\n<p>I&#8217;m assuming here that converting the property set to some kind of data buffer is a slow operation, which is why it&#8217;s done as a separate pass over the captured data.<\/p>\n<p>One way to capture the property set would be to transfer it into another property set:<\/p>\n<pre>auto CapturePropertySet(winrt::PropertySet const&amp; propertySet)\r\n{\r\n    winrt::PropertySet copy;\r\n    for (auto [key, value] : m_propertySet) {\r\n        copy.Insert(key, value);\r\n    }\r\n    return copy;\r\n}\r\n<\/pre>\n<p>Alternatively, since we really just want to capture the key\/value pairs, we could just save the key\/value pairs:<\/p>\n<pre>auto CapturePropertySet(winrt::PropertySet const&amp; propertySet)\r\n{\r\n    return std::vector(begin(propertySet), end(propertySet));\r\n}\r\n<\/pre>\n<p>Okay, so we ensured that the collection doesn&#8217;t change while we&#8217;re saving it, but what if it mutates while we&#8217;re copying it? In that case, the <code>hresult_<wbr \/>changed_<wbr \/>state<\/code> exception occurs, and the <code>Save()<\/code> fails with an exception.<\/p>\n<p>You probably don&#8217;t want to propagate this exception back to the caller, because they have no idea that this is even happening. They added a property to the property set, and on another thread, they added another property to the same property set, and somehow the first thread gets a <code>hresult_<wbr \/>changed_<wbr \/>state<\/code> exception. What state changed? What did they do wrong?<\/p>\n<p>They didn&#8217;t do anything wrong. The problem is in your <code>Save<\/code> code.<\/p>\n<p>Let&#8217;s catch the exception and quietly abandon the <code>Save<\/code> operation. The idea here is that the <code>hresult_<wbr \/>changed_<wbr \/>state<\/code> exception occurs if another thread updated the property set after we started saving it. In that case, we should abandon our attempt to save the property set and let that other thread save it.<\/p>\n<pre>void MyPropertySet::Save()\r\n{\r\n    winrt::PropertySet copy{ nullptr };\r\n    try {\r\n        copy = CapturePropertySet(m_propertySet);\r\n    } catch (winrt::hresult_changed_state const&amp;) {\r\n        \/\/ Abandon the operation.\r\n        \/\/ The mutating thread will do its own Save.\r\n        return;\r\n    }\r\n\r\n    SomeKindOfDataBuffer buffer;\r\n    for (auto [key, value] : copy) {\r\n        buffer.AddKeyAndValue(key, value);\r\n    }\r\n    SaveToFile(buffer);\r\n}\r\n<\/pre>\n<p>As I mentioned earlier, I&#8217;m assuming here that we are converting the property set to a data buffer as a separate pass because it is slow. If the conversion is fast, you may as well do it while iterating:<\/p>\n<pre>void MyPropertySet::Save()\r\n{\r\n    SomeKindOfDataBuffer buffer;\r\n    try {\r\n        for (auto [key, value] : m_propertySet) {\r\n            buffer.AddKeyAndValue(key, value);\r\n        }\r\n    } catch (winrt::hresult_changed_state const&amp;) {\r\n        \/\/ Abandon the operation.\r\n        \/\/ The mutating thread will do its own Save.\r\n        return;\r\n    }\r\n    SaveToFile(buffer);\r\n}\r\n<\/pre>\n<p>There is still a problem here, though. Consider this sequence of events:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: 1px gray; border-style: none solid solid none;\">Thread 1<\/td>\n<td style=\"border-bottom: solid 1px gray;\">Thread 2<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Insert<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Save<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Build the buffer<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Insert<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Save<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Build the buffer<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>SaveToFile<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Save returns<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">SaveToFile<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Save returns<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Both attempts to capture the data in the property set succeed because the property set did not change during the capture of the property set into the buffer. However, the second capture was able to race ahead of the first one, which means that the latest saved copy from Thread 2 gets overwritten by the stale copy in Thread 1.<\/p>\n<p>One idea here is have the <code>Save<\/code> function make one last check before saving that what it saved is still the latest copy. To avoid the race between the final check and the <code>SaveToFile<\/code>, we will need a lock.<\/p>\n<pre>void MyPropertySet::Save()\r\n{\r\n    SomeKindOfDataBuffer buffer;\r\n    try {\r\n        auto it = m_propertySet.First();\r\n        if (it.HasCurrent()) {\r\n            do {\r\n                auto current = it.Current();\r\n                buffer.AddKeyAndValue(current.Key(), current.Value());\r\n            } while (it.MoveNext());\r\n        }\r\n\r\n        auto guard = m_lock.lock();\r\n\r\n        \/\/ verify that the collection is still unchanged before saving\r\n        std::ignore = it.HasCurrent();\r\n        SaveToFile(buffer);\r\n    } catch (winrt::hresult_changed_state const&amp;) {\r\n        \/\/ Abandon the operation.\r\n        \/\/ The mutating thread will do its own Save.\r\n        return;\r\n    }\r\n\r\n}\r\n<\/pre>\n<p>After we build the results in the data buffer, we enter the lock and make one final check that the collection hasn&#8217;t changed. The return value is not what we are interested in, since we know that it will return <code>false<\/code> if it returns at all, seeing as we iterated to the end of the collection in the preceding loop. What we are interested in is checking whether it will throw an exception due to the collection having been mutated. Assigning to <code>std::ignore<\/code> is the same as throwing the value away, except it avoids a <code>[[nodiscard]]<\/code> warning and is arguably clearer that discarding the value is intentional.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: 1px gray; border-style: none solid solid none;\">Thread 1<\/td>\n<td style=\"border-bottom: solid 1px gray;\">Thread 2<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Insert<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Save<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Build the buffer<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Insert<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Save<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Build the buffer<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">enter lock<\/td>\n<td>enter lock<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If Thread 1 wins the race to enter the lock, then the final <code>HasCurrent()<\/code> check will throw <code>hresult_<wbr \/>changed_<wbr \/>state<\/code>, and the <code>SaveToFile<\/code> will not happen. Thread 2 then gets a chance to save, and its test of <code>HasCurrent()<\/code> will not throw, so it is the one that gets to perform the <code>SaveToFile<\/code>.<\/p>\n<p>On the other hand, if Thread 2 wins the race to enter the lock, then Thread 2&#8217;s <code>HasCurrent()<\/code> will not throw, so it will perform <code>SaveToFile<\/code>. And then Thread 1 gets the lock and checks <code>HasCurrent()<\/code>, which throws, so Thread 1 does not save its now-outdated data.<\/p>\n<p>There is also a race condition where there is a redundant save:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td style=\"border: 1px gray; border-style: none solid solid none;\">Thread 1<\/td>\n<td style=\"border-bottom: solid 1px gray;\">Thread 2<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Insert<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Insert<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Save<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Save<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">Build the buffer<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>Build the buffer<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">enter lock<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">HasCurrent does not throw<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">SaveToFile<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">exit lock<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>enter lock<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>HasCurrent does not throw<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>SaveToFile<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>exit lock<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Since Thread 1 got off to a late start, it started building the data buffer after Thread 2 already snuck in and changed the property set, so it unwittingly created an up-to-date copy. At least here the race condition is harmless, albeit perhaps inefficient.<\/p>\n<p>The model here is that each <code>Save<\/code> operation tries to save as fast as it can, but bails out if it discovers that it is not the winner. This means that the Save method&#8217;s running time is basically the time it takes to serialize and save the property set once.<\/p>\n<p>Next time, we&#8217;ll look at another solution to the concurrency problem which has its own separate advantages and disadvantages.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Watching out for iterator invalidation.<\/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-106858","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Watching out for iterator invalidation.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106858","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=106858"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106858\/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=106858"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106858"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106858"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}