{"id":108098,"date":"2023-04-26T07:00:00","date_gmt":"2023-04-26T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108098"},"modified":"2023-04-26T09:39:44","modified_gmt":"2023-04-26T16:39:44","slug":"20230426-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230426-00\/?p=108098","title":{"rendered":"One way to defer work when a re-entrant call is detected"},"content":{"rendered":"<p>A customer had a single-threaded COM object that was experiencing unexpected re-entrancy.<\/p>\n<pre>enum WidgetState;\r\n\r\nstruct MyObject : public IWidgetStateListener\r\n{\r\n    \/* ... *\/\r\n\r\n    \/\/ IWidgetStateListener\r\n    HRESULT OnStateChanged(WidgetState newState); \r\n\r\n    \/* ... *\/\r\n    WidgetState m_widgetState = WidgetState::Normal;\r\n    WidgetStateMonitor m_monitor;\r\n};\r\n\r\nHRESULT MyObject::OnStateChanged(WidgetState newState)\r\n{\r\n    m_monitor.Disconnect();\r\n\r\n    m_widgetState = newState;\r\n\r\n    m_monitor.Connect(newState);\r\n\r\n    return S_OK;\r\n}\r\n<\/pre>\n<p>The catch was that disconnecting the monitor involved a cross-thread COM call.<\/p>\n<p>When a single-threaded COM apartment makes a call to another thread, it allows inbound calls to be made while waiting for the call to complete. This is necessary so that if the recipient of the outbound call tries to call back, the call can come back in. For example, the thread might call <code>IPersistStream::<wbr \/>Load<\/code> on an external object. As part of the <code>IPersistStream::<wbr \/>Load<\/code>, the external object will naturally read from the stream. If the call to <code>IStream::<wbr \/>Read<\/code> were not allowed through, then the system would deadlock.<\/p>\n<p>However, in this case, the inbound call was not coming from the <code>m_monitor<\/code> object. Rather, while waiting for that external object to finish disconnecting, the widget state changed <i>again<\/i>. This caused <code>OnStateChanged<\/code> to be called while a previous call was still outstanding, resulting in re-entrancy and mass confusion.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td><code style=\"padding-left: 0em;\">On\u00adState\u00adChanged(state1)<\/code><br \/>\n<code style=\"padding-left: 1em;\">m_monitor.Disconnect();<\/code><br \/>\n<span style=\"padding-left: 2em;\">re-entrant call while waiting for <code>Disconnect<\/code><\/span><\/td>\n<\/tr>\n<tr>\n<td><code style=\"padding-left: 2em;\">OnStateChanged(state2);<\/code><br \/>\n<code style=\"padding-left: 3em;\">m_monitor.Disconnect();<\/code> \u2190 Oops, double-disconnect<br \/>\n<code style=\"padding-left: 3em;\">m_widgetState = state2;<\/code><br \/>\n<code style=\"padding-left: 3em;\">m_monitor.Connect(state2);<\/code><br \/>\n<code style=\"padding-left: 2em;\">OnStateChanged(state2)<\/code> returns<\/td>\n<\/tr>\n<tr>\n<td><code style=\"padding-left: 1em;\">m_widgetState = state1;<\/code><br \/>\n<code style=\"padding-left: 1em;\">m_monitor.Connect(state1);<\/code> \u2190 Oops, forgot to disconnect state2<br \/>\n<code style=\"padding-left: 0em;\">OnStateChanged(state1)<\/code> returns<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Okay, so how can we fix this?<\/p>\n<p>It so happens that the nature of the state changes is that the <code>My\u00adObject<\/code> cares only about the final state of the widget. This means that multiple state change notifications can be coalesced, which simplifies our solution.<\/p>\n<p>The last bit that is important is that the <code>My\u00adObject<\/code> does not have to complete all its processing immediately upon receiving a state change notification. As long as the final state is eventually processed, the object will work okay.<\/p>\n<p>When re-entrancy is detected, we don&#8217;t want to start processing the new change notification immediately, because the old one is still active. Instead, we&#8217;ll just record the value for later processing when things are safe.<\/p>\n<pre>struct MyObject : public IWidgetStateListener\r\n{\r\n    \/* ... *\/\r\n\r\n    \/\/ IWidgetStateListener\r\n    HRESULT OnStateChanged(WidgetState newState); \r\n\r\n    \/* ... *\/\r\n    WidgetState m_widgetState = WidgetState::Normal;\r\n\r\n    <span style=\"color: #08f;\">\/\/ New members\r\n    bool m_updatingState = false;\r\n    bool m_updatePostponed = false;\r\n    WidgetState m_postponedState;<\/span>\r\n};\r\n\r\nHRESULT MyObject::OnStateChanged(WidgetState newState)\r\n{\r\n    <span style=\"color: #08f;\">if (m_updatingState) {\r\n        m_updatePostponed = true;\r\n        m_postponedState = newState;\r\n        return S_OK;\r\n    }\r\n\r\n    m_updatingState = true;<\/span>\r\n\r\n    m_monitor.Disconnect();\r\n\r\n    m_widgetState = newState;\r\n\r\n    m_monitor.Connect(newState);\r\n\r\n    <span style=\"color: #08f;\">m_updatingState = false;\r\n    \/\/ Not finished yet<\/span>\r\n\r\n    return S_OK;\r\n}\r\n<\/pre>\n<p>The other half of the solution is arranging that the already-running copy of <code>On\u00adState\u00adChanged<\/code>, after it finishes it work, checks whether an update occurred while it was working. If so, then go back and process that postponed update.<\/p>\n<pre>HRESULT MyObject::OnStateChanged(WidgetState newState)\r\n{\r\n    if (m_updatingState) {\r\n        m_updatePostponed = true;\r\n        m_postponedState = newState;\r\n        return S_OK;\r\n    }\r\n\r\n    m_updatingState = true;\r\n\r\n    <span style=\"color: #08f;\">do {\r\n        m_updatePostponed = false;<\/span>\r\n\r\n        m_monitor.Disconnect();\r\n\r\n        m_widgetState = newState;\r\n\r\n        m_monitor.Connect(newState);\r\n\r\n        <span style=\"color: #08f;\">newState = m_postponedState;\r\n    } while (m_updatePostponed);<\/span>\r\n    m_updatingState = false;\r\n\r\n    return S_OK;\r\n}\r\n<\/pre>\n<p>After setting the <code>m_updatingState<\/code> flag to <code>true<\/code>, we clear the <code>m_updatePostponed<\/code> state, and then start doing our work. If re-entrancy occurs during our work, the re-entrant call will see that <code>m_updatingState<\/code> is <code>true<\/code>, and instead of doing the work immediately, it sets the <code>m_updatePostponed<\/code> flag to <code>true<\/code> and remembers the postponed value.<\/p>\n<p>After the work completes, we check whether there was a postponed update. If so, we loop back and process that postponed update.<\/p>\n<p>If your policy is that all changes must be processed, rather than dropping intermediate changes, you&#8217;ll have to keep a queue of pending changes rather than just remembering the last one. I&#8217;ll leave that as an exercise.<\/p>\n<p>Note also that I&#8217;m assuming that this is a single-threaded object, so I don&#8217;t have to worry about concurrency or unfairness. Concurrency is not a problem since there is no multi-threaded access to <code>MyObject<\/code>. Unfairness is not a problem because the costs are all being paid by the same thread; all we did was shift the time the cost is paid.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Tying a string on your finger, as a reminder to yourself.<\/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-108098","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Tying a string on your finger, as a reminder to yourself.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108098","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=108098"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108098\/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=108098"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108098"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108098"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}