{"id":109311,"date":"2024-01-24T07:00:00","date_gmt":"2024-01-24T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109311"},"modified":"2024-01-24T06:57:10","modified_gmt":"2024-01-24T14:57:10","slug":"20240124-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240124-00\/?p=109311","title":{"rendered":"The dangerous implementations of the <CODE>IMemory&shy;Buffer&shy;Reference.Closed<\/CODE> event"},"content":{"rendered":"<p>Last time, we noted that <a title=\"The useless IMemory\u00adBuffer\u00adReference.Closed event\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240123-00\/?p=109307\"> the <code>IMemory\u00adBuffer\u00adReference.Closed<\/code> event is useless<\/a>. Which is a good thing, because every implementation I&#8217;ve looked at is also wrong.<\/p>\n<p>The problem comes with the need to raise the <code>Closed<\/code> event when the final reference is released. The implementations I&#8217;ve looked at all raise the event in the object&#8217;s destructor as a &#8220;last gasp&#8221;:<\/p>\n<pre>MemoryBufferReference::~MemoryBufferReference()\r\n{\r\n    m_refCount = 1; \/\/ <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050928-10\/?p=34013\">avoid double-destruction<\/a>\r\n\r\n    Close();\r\n}\r\n\r\nvoid MemoryBufferReference::Close()\r\n{\r\n    if (!m_notified.exchange(true)) {\r\n        m_closed.Invoke(this, nullptr);\r\n        m_buffer = nullptr;\r\n    }\r\n}\r\n<\/pre>\n<p>We are giving out COM references to an object from its destructor, and as we noted when we discussed this trick earlier, this assumes that none of the functions who are given these COM references will call <code>AddRef<\/code> and retain the pointer after the call returns. (They can call <code>AddRef<\/code>, provided they also call <code>Release<\/code> the same number of times before returning.)<\/p>\n<p>The problem here is that the <code>Closed<\/code> event does not come with any guarantee that the handler will honor this constraint. And in fact, you have a reverse guarantee: If the handler is written in C#, you will almost certainly <i>not<\/i> have balanced <code>AddRef<\/code> and <code>Release<\/code> calls. That&#8217;s because the common language runtime (CLR) wraps inbound COM references inside a so-called runtime-callable wrapper (RCW). The runtime-callable wrapper retains a reference to the underlying COM object, which means that the COM reference gets <code>AddRef<\/code>&#8216;d when it is placed inside an RCW. When the RCW gets garbage-collected, the finalizer for the RCW does a <code>Release<\/code> on the backing COM reference.<\/p>\n<p>What will happen is that the <code>Closed<\/code> event handler will receive two RCWs, one for each of the event handler parameters, and the RCW will hang around after the handler returns. The <code>IMemory\u00adBuffer\u00adReference<\/code> object destructs despite being <code>AddRef<\/code>&#8216;d. Eventually, those RCWs will get cleaned up by the garbage collector, and they will <code>Release<\/code> the wrapped COM pointer. Unfortunately, that pointer is now pointing to freed memory because the object destructed way back when the <code>Closed<\/code> event was raised.\u00b9<\/p>\n<p>Even if your <code>Closed<\/code> handler ignores its parameters, the CLR will still create RCWs for them, because it doesn&#8217;t know that your handler ignores its parameters. Its job is to pass the two parameters to the handler, and by golly, it will pass the two parameters to the handler.<\/p>\n<p>The only winning move is not to play: Never subscribe to the <code>Closed<\/code> event.<\/p>\n<p>Fortunately, you shouldn&#8217;t be tempted to subscribe to it anyway, since we also learned that the event is useless.<\/p>\n<p>\u00b9 Here&#8217;s a test program I wrote to prove that it crashes. You have to run it in Release mode so that the necessary optimizations kick in.<\/p>\n<pre>using Buffer = Windows.Storage.Streams.Buffer;\r\n\r\nvoid Test()\r\n{\r\n    bool done = false;\r\n    Buffer.CreateMemoryBufferOverIBuffer(new Buffer(10)).\r\n        CreateReference().Closed += (s, _) =&gt; done = true;\r\n\r\n    \/\/ Force GC's until the MemoryBuffer's RCW runs down\r\n    while (!done)\r\n    {\r\n        GC.Collect();\r\n    }\r\n    \/\/ The Closed event handler created a new RCW for the\r\n    \/\/ MemoryBuffer. Force another GC to run it down, which\r\n    \/\/ will crash.\r\n    GC.Collect();\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Mistakenly handing out COM references that don&#8217;t work.<\/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-109311","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Mistakenly handing out COM references that don&#8217;t work.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109311","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=109311"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109311\/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=109311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}