{"id":103466,"date":"2020-02-21T07:00:00","date_gmt":"2020-02-21T15:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103466"},"modified":"2020-02-20T22:32:20","modified_gmt":"2020-02-21T06:32:20","slug":"20200221-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200221-00\/?p=103466","title":{"rendered":"Why you might need additional control over the secret event hiding inside the file object"},"content":{"rendered":"<p>Some time ago, I noted that <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190719-00\/?p=102722\"> the <code>Set\u00adFile\u00adCompletion\u00adNotification\u00adModes<\/code> function provides a small amount of additional control over the secret event hiding inside the file object<\/a>, but I noted that I could not come up with a scenario where you would need to exercise that much control.<\/p>\n<p>The purpose of the <code>FILE_<\/code><code>SKIP_<\/code><code>SET_<\/code><code>EVENT_<\/code><code>ON_<\/code><code>HANDLE<\/code> flag is to prevent the kernel from messing with the secret event hiding inside the file object. But if you aren&#8217;t using that secret event anyway, why does it matter what the kernel does with it?<\/p>\n<p>Malcolm Smith explained to me why it matters: It&#8217;s to avoid contention.<\/p>\n<p>In high-performance scenarios, you may have tons of outstanding I\/O operations on a handle. Those operations are all queueing to an I\/O completion port. They don&#8217;t need an explicit event handle, nor do they need to synchronize on the secret handle hiding inside the file object.<\/p>\n<p>The normal behavior at I\/O completion on an overlapped handle is that the kernel signals the event provided in the <code>OVERLAPPED<\/code> structure, if present. If the event handle in the <code>OVERLAPPED<\/code> structure is <code>nullptr<\/code>, then the kernel signals the secret event inside the file object.<\/p>\n<p>If you&#8217;re doing I\/O on an overlapped handle, then the secret event inside the file object is useless once you have two outstanding I\/O operations on the file handle, because they will both try to use the event and end up confusing each other. You would have to ensure that only one I\/O operation is active at a time, which sort of defeats the point of overlapped I\/O.<\/p>\n<p>Okay, I can think of one scenario where it&#8217;s useful: If you are using overlapped I\/O solely for its asynchronous behavior and not for the ability to have multiple outstanding I\/O at a time. But even then, just create your own event already. Don&#8217;t rely on the secret event inside the file object.<\/p>\n<p>In the case of overlapped I\/O issued on a handle bound to an I\/O completion port, you definitely don&#8217;t care about the secret event hiding inside the file object, and making the kernel set it at completion is just another multithreading bottleneck. The <code>FILE_<\/code><code>SKIP_<\/code><code>SET_<\/code><code>EVENT_<\/code><code>ON_<\/code><code>HANDLE<\/code> flag lets you tell the kernel to skip that step entirely.<\/p>\n<p>Incorporating the <code>FILE_<\/code><code>SKIP_<\/code><code>SET_<\/code><code>EVENT_<\/code><code>ON_<\/code><code>HANDLE<\/code> flag into the I\/O completion process results in this pseudo-code:<\/p>\n<pre>SignalEventsWhenOverlappedIoCompleted()\r\n{\r\n  if (hEvent present) {\r\n    SetEvent(hEvent);\r\n  } else if (FILE_SKIP_SET_EVENT_ON_HANDLE is clear) {\r\n    SetEvent(secret_event);\r\n  } else {\r\n    do nothing;\r\n  }\r\n}\r\n<\/pre>\n<p>Now, you might think you could avoid the bottleneck on the secret event hiding inside the file object by passing an event in the <code>OVERLAPPED<\/code> structure. According to the algorithm above, this means that the kernel will set the event in the <code>OVERLAPPED<\/code> structure and ignore the secret event in the file object. Since each I\/O has its own event (if you know what&#8217;s good for you), the <code>SetEvent(hEvent)<\/code> will not experience contention, so it will be fast. It&#8217;s still annoying having to create an event that you have no use for, but its purpose is to be a <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060110-17\/?p=32703\"> decoy<\/a> so the kernel won&#8217;t try to set the secret event hiding inside the file object.<\/p>\n<p>Unfortunately, this solution runs into its own bottleneck. When the I\/O completes, the I\/O manager returns to the original issuing thread in order to set the event,\u00b9 and then queues the completion to the I\/O completion port, This introduces an extra thread switch to the I\/O operation, as well as additional contention into I\/O completion bookkeeping.\u00b2<\/p>\n<p>The secret event hiding inside the file object is useful only in the case of synchronous I\/O. If you&#8217;re doing asychnronous I\/O, all it does is get in your way. The <code>FILE_<\/code><code>SKIP_<\/code><code>SET_<\/code><code>EVENT_<\/code><code>ON_<\/code><code>HANDLE<\/code> flag lets you move it out of your way.<\/p>\n<p>\u00b9 I suspect the &#8220;return to the original issuing thread&#8221; is an artifact of the fact that completion callbacks are delivered to the original issuing thread.<\/p>\n<p>\u00b2 The cure ends up being worse than the disease if the I\/O originated from an I\/O completion port thread, which is highly likely if the handle is associated with an I\/O completion port in the first place. I\/O completion ports keep track of how many threads are running and how many are blocked, so that they don&#8217;t <a href=\"https:\/\/blogs.msdn.microsoft.com\/visualizeparallel\/2009\/12\/01\/oversubscription-a-classic-parallel-performance-problem\/\"> oversubscribe<\/a> the associated thread pool. When the thread is woken to set the event, the I\/O completion port updates the bookkeeping to account for an idle thread being woken, and when the thread goes back to sleep after setting the event, the I\/O completion port updates the bookkeeping once again to account for the thread going back to idle. That&#8217;s two more points of contention on a very busy I\/O completion port. So your attempt to remove one contention point on a busy file object turned into the addition of <i>two<\/i> contention points on an even busier I\/O completion port!<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sometimes every little bit of contention means a lot.<\/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-103466","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Sometimes every little bit of contention means a lot.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103466","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=103466"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103466\/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=103466"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103466"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103466"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}