{"id":107832,"date":"2023-02-15T07:00:00","date_gmt":"2023-02-15T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107832"},"modified":"2023-02-14T20:44:21","modified_gmt":"2023-02-15T04:44:21","slug":"20230215-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230215-00\/?p=107832","title":{"rendered":"What are the potentially-erroneous results if you don&#8217;t pass NULL as the lpNumberOfBytesRead when issuing overlapped I\/O?"},"content":{"rendered":"<p>The documentation for many I\/O functions that read or write bytes recommend that you pass <code>NULL<\/code> as the <code>lpNumber\u00adOf\u00adBytes\u00adRead<\/code> parameter when issuing overlapped I\/O to avoid &#8220;potentially erroneous results&#8221;. What are these potentially erroneous results the documentation is trying to warn against?<\/p>\n<p>For the purpose of this discussion, let&#8217;s use <code>ReadFile<\/code> as our example, even though the same argument applies to <code>Write\u00adFile<\/code>, <code>WSASend<\/code>, and other functions which follow the same pattern.<\/p>\n<p>The race condition here is a race between the code that calls <code>ReadFile<\/code> and the code that handles the asynchronous completion. If the variable passed as the output for <code>ReadFile<\/code>&#8216;s <code>lpNumber\u00adOf\u00adBytes\u00adRead<\/code> parameter is the same variable used as the output for <code>Get\u00adOverlapped\u00adResult<\/code>&#8216;s <code>lpNumber\u00adOf\u00adBytes\u00adTransferred<\/code> parameter, then there is a race because the completion might run concurrently with the exit out of <code>Read\u00adFile<\/code>.<\/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;\"><code>ReadFile(..., &amp;byteCount, ...);<\/code><\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\"><code>ReadFile<\/code> begins<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0\u00a0I\/O initiated asynchronously<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td>I\/O completes asynchronously<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td><code>Get\u00adOverlapped\u00adResult(..., &amp;byteCount, ...)<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0<\/td>\n<td><code>Get\u00adOverlapped\u00adResult<\/code> sets <code>byteCount = result<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0\u00a0set <code>byteCount = 0<\/code><\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0\u00a0<code>SetLastError(ERROR_IO_PENDING);<\/code><\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"border-right: solid 1px gray;\">\u00a0\u00a0<code>return FALSE;<\/code><\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If the I\/O completes very quickly, then the completion routine can run before <code>Read\u00adFile<\/code> returns. And then when <code>Read\u00adFile<\/code> tries to report the fact that the I\/O was initiated asynchronously, it overwrites the <code>byteCount<\/code> that the completion routine had calculated.<\/p>\n<p>So it&#8217;s okay to pass a non-null <code>lpNumberOfBytesRead<\/code> to <code>Read\u00adFile<\/code>, even when issuing asynchronous I\/O, provided that you do so into a different variable from the one that the completion routine uses.<\/p>\n<p>Normally, however, there&#8217;s no reason to pass a non-null <code>lpNumberOfBytesRead<\/code> because the result of the operation is going to be handled by the completion function. But there&#8217;s a case where it is advantageous to use a non-null value, and that&#8217;s where you have used <code>Set\u00adFile\u00adCompletion\u00adNotification\u00adModes<\/code> to configure the handle as <code>FILE_<wbr \/>SKIP_<wbr \/>COMPLETION_<wbr \/>PORT_<wbr \/>ON_<wbr \/>SUCCESS<\/code>. In that case, a synchronous completion does not queue a call to the completion function on the I\/O completion thread. Instead, the code that called <code>Read\u00adFile<\/code> is expected to deal with the synchronous completion inline. And one of the things it probably wants to know is how many bytes were read, so it would normally call <code>Get\u00adOverlapped\u00adResult<\/code> to find out. You can avoid that extra call to <code>Get\u00adOverlapped\u00adResult<\/code> by passing a non-null pointer to <code>Read\u00adFile<\/code> so that in the case of a synchronous completion, you have your answer immediately.<\/p>\n<p>This is admittedly a micro-optimization. One of my colleagues was not aware of this trick and just followed the guidance in the documentation by passing <code>NULL<\/code> and calling <code>Get\u00adOverlapped\u00adResult<\/code>, and he says that his code can still stream data at 100Gbps over the network despite doing things &#8220;inefficiently&#8221;.<\/p>\n<p>So maybe you&#8217;re better off not knowing and just following the simple rule of &#8220;Use <code>NULL<\/code> when issuing asynchronous I\/O.&#8221; It&#8217;s easier to explain and easier to remember.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s to avoid a self-inflicted race condition.<\/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-107832","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It&#8217;s to avoid a self-inflicted race condition.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107832","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=107832"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107832\/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=107832"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107832"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107832"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}