{"id":104439,"date":"2020-11-11T07:00:00","date_gmt":"2020-11-11T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104439"},"modified":"2020-11-11T07:55:15","modified_gmt":"2020-11-11T15:55:15","slug":"20201111-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201111-00\/?p=104439","title":{"rendered":"The hidden callout: The destructor"},"content":{"rendered":"<p>This is a general problem, but I&#8217;m going to give a specific example.<\/p>\n<p>C++ standard library collections do not support concurrent mutation. It is the caller&#8217;s responsibility to serialize mutation operations, and to ensure that no mutation occurs at the same time as any other operation. And the usual way of accomplishing this is to use a mutex of some kind.<\/p>\n<pre>class ThingManager\r\n{\r\nprivate:\r\n  std::mutex things_lock_;\r\n  std::vector&lt;std::shared_ptr&lt;Thing&gt;&gt; things_;\r\n\r\npublic:\r\n  void AddThing(std::shared_ptr&lt;Thing&gt; thing)\r\n  {\r\n    std::lock_guard guard(things_lock_);\r\n    things_.push_back(std::move(thing));\r\n  }\r\n\r\n  void RemoveThingById(int32_t id)\r\n  {\r\n    std::lock_guard guard(things_lock_);\r\n    auto it = std::find_if(things_.begin(), things_.end(),\r\n      [&amp;](auto&amp;&amp; thing)\r\n      {\r\n        return thing-&gt;id() == id;\r\n      });\r\n    if (it != things_.end()) {\r\n      things_.erase(it);\r\n    }\r\n  }\r\n};\r\n<\/pre>\n<p>The idea here is that you give the <code>Thing\u00adManager<\/code> a bunch of things, and then you can later remove them by providing the <code>Thing<\/code>&#8216;s ID. Presumably there are also methods to search for <code>Thing<\/code>s or to perform some operation across all <code>Thing<\/code>s, but those are just distractions from the exercise.<\/p>\n<p>This particular object wants to support concurrent operation, so it internally uses a mutex to ensure safe operation.<\/p>\n<p>Now, you can quibble about the use of <code>find_if<\/code> instead of <code>remove_if<\/code>, or using a <code>std::vector<\/code> instead of a <code>std::map<\/code>, but let&#8217;s set that aside.<\/p>\n<p>The question is: What&#8217;s wrong with this code?<\/p>\n<p>I sort of gave it away in the title: We are calling out to external code while holding our lock.<\/p>\n<p>You probably know not to call out to external code when holding an internal lock, and the act of invoking a method on an object may remind you of that fact. But destructors just run by themselves. You don&#8217;t typically write code the trigger the destruction of an object. The object just destructs when it destructs.<\/p>\n<p>Removing the <code>shared_ptr&lt;Thing&gt;<\/code> from our vector could result in the <code>Thing<\/code>&#8216;s destruction if this was the last strong reference to the <code>Thing<\/code>. And that destructor runs while the <code>things_lock_<\/code> is still locked.<\/p>\n<p>Now things get interesting.<\/p>\n<p>You may not know all that happens inside the <code>Thing<\/code> destructor. It may have been written by another team, or by you, six months ago. Or somebody could have derived from <code>Thing<\/code> and given you a shared pointer to the derived object.\u00b9 Or you might be given a shared pointer to a <code>Thing<\/code> embedded inside a larger object.<\/p>\n<p>Let&#8217;s do that thing with the derived type:<\/p>\n<pre>class SuperThing : Thing\r\n{\r\nprivate:\r\n  ThingManager&amp; manager_;\r\n  int32_t helper_id_ = 0;\r\n\r\npublic:\r\n  SuperThing(ThingManager&amp; manager) :\r\n    manager_(manager)\r\n  {\r\n    auto helper = std::make_shared&lt;Thing&gt;();\r\n    helper_id_ = helper-&gt;id();\r\n    manager_.AddThing(helper);\r\n  }\r\n\r\n  ~SuperThing()\r\n  {\r\n    manager_.RemoveThingById(helper_id_);\r\n  }\r\n};\r\n<\/pre>\n<p>The <code>Super\u00adThing<\/code> object is itself a <code>Thing<\/code>, but it also uses a helper thing. At construction, it creates a helper thing and registers it with the manager, retaining the ID. And at destruction, it removes its helper thing.<\/p>\n<p>And then this happens:<\/p>\n<pre>void test(ThingManager&amp; manager)\r\n{\r\n  auto s = std::make_shared&lt;SuperThing&gt;();\r\n  auto id = s-&gt;id();\r\n  manager.AddThing(s);\r\n  s = nullptr;\r\n\r\n  manager.RemoveThingById(id);\r\n}\r\n<\/pre>\n<p>This little test function creates a <code>Super\u00adThing<\/code>, adds it to the thing manager, and then immediately removes it.<\/p>\n<p>The <code>Remove\u00adThing\u00adBy\u00adId<\/code> function looks for a matching Id and finds it, so it erases the corresponding <code>Thing<\/code> from the vector. That erasure destroys the <code>shared_<\/code><code>ptr<\/code>, and since this is the last strong reference, the underlying <code>Thing<\/code> is also destroyed.<\/p>\n<p>This runs the destructor of our <code>Super\u00adThing<\/code>, which tries to remove its helper <code>Thing<\/code>. And that calls back into the <code>ThingManager<\/code>, which gets stuck trying to acquire a mutex that is already held (unwittingly, by itself).<\/p>\n<p>This is not a purely theoretical exercise. This sort of thing happens, and it&#8217;s a source of bugs.<\/p>\n<p>Next time, we&#8217;ll look at how to address these types of problems.<\/p>\n<p>\u00b9 If you work in Windows, a common scenario for this is that the <code>shared_ptr<\/code> in the example above takes the form of a COM reference-counted pointer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You may not know where that&#8217;s going to lead.<\/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-104439","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You may not know where that&#8217;s going to lead.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104439","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=104439"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104439\/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=104439"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104439"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104439"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}