Boost C++ Libraries: Ticket #8967: Spurious zero bytes written and eof notifications highlight ASIO IOCP issues https://svn.boost.org/trac10/ticket/8967 <p> I have been working on a multi-threaded boost asio based HTTP client. My tests perform hundreds of HTTP calls against a variety of web servers. </p> <p> The tests run reliably on OS X and on Windows with the BOOST_ASIO_DISABLE_IOCP flag set. I use multiple threads calling io_service:run(). </p> <p> However, on Windows using IOCP I was seeing occasional notifications that zero bytes had been written with async_write even though data had successfully been written to the socket and was received by the web server. </p> <p> I also saw random boost::asio::error::eof notications from async_read_some even though there was more data to read and the tcp socket's available() method returned a non-zero value. </p> <p> In trying to track down the problem I reviewed the implementation of the ASIO IOCP implementation and found several issues: </p> <blockquote> <p> 1) WSARecv and WSASend were supplying non-NULL for the lpNumberOfBytesRecvd parameter with an overlapped socket. The MSDN docs say "Use NULL for this parameter if the lpOverlapped parameter is not NULL to avoid potentially erroneous results. This parameter can be NULL only if the lpOverlapped parameter is not NULL. </p> </blockquote> <blockquote> <p> 2) <a class="missing wiki">PostQueuedCompletionStatus</a> is only needed to deliver errors where WSARecv and WSASend synchronously fail. Instead it was always being used for successful synchronous and pending I/O through win_iocp_io_service::on_pending. This is not required because an overlapped socket's results are automatically delivered to thread(s) calling <a class="missing wiki">GetQueuedCompletionStatus</a>. I believe this could lead to incorrect behaviour due to the explicit and implicit posting of each I/O request. Also, this would increase the number of notifications that the IOCP would have to handle. </p> </blockquote> <blockquote> <p> 3) The ready_ flag seems to be unnecessary for a correct implementation and could cause notifications from <a class="missing wiki">QueuedCompletionStatus</a> to be ignored </p> </blockquote> <p> I have attached a patch file of my changes with comments prefixed with SP: indicating the reason for each of the changes. </p> <p> After applying these changes to Boost ASIO in 1.54 I can now reliably run the IOCP version of the tests on Windows 7. </p> <p> I believe the modified implementation is closer to that expected when using sockets and I/O completion ports. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/8967 Trac 1.4.3 simoncperkins@… Mon, 05 Aug 2013 16:14:30 GMT attachment set https://svn.boost.org/trac10/ticket/8967 https://svn.boost.org/trac10/ticket/8967 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">patchfile.zip</span> </li> </ul> <p> Had to zip the patch file because the comments contain links to MSDN docs </p> Ticket Shane Powell <killerbee@…> Sat, 10 Aug 2013 07:06:47 GMT <link>https://svn.boost.org/trac10/ticket/8967#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:1</guid> <description> <p> Hi, </p> <p> I found and fixed the same problem: <a class="ext-link" href="https://svn.boost.org/trac/boost/ticket/8933"><span class="icon">​</span>https://svn.boost.org/trac/boost/ticket/8933</a> </p> <p> I disagree with your patch tho as I think it's dangerous removing how the on_pending works. It's there to delay the calling of the complete handler until after the returning of the winsock functions as they are not reentrant for a single socket. Removing the it completely would result in existing asio programs not working correctly in all situations. </p> <p> The ASIO documentation says the the complete handler is not called until after the call to the sockets function has returned. This means that it's safe to call the next read/write cycle again. Taking away how the on_pending works will mean that it's possible that you can call back into the read/write functions before you are allowed to, which can caused undefined behaviour. </p> <p> Quote from MSDN: WSARecv should not be called on the same socket simultaneously from different threads, because it can result in an unpredictable buffer order. </p> <p> Check out my patch, it's much simpler (basically it reverts the code back to how it used to work), as the actual bug is that on_pending should be calling <a class="missing wiki">PostQueuedCompletionStatus</a> with the 'overlapped_contains_result' value so that the io completion routine knows that it's a delayed result rather than a real result. Because it got changed from 'overlapped_contains_result' to zero, the io completion routine thinks it's a result from a winsock type function rather than a delayed result where the result is stored in the overlapped structure. </p> </description> <category>Ticket</category> </item> <item> <author>simoncperkins@…</author> <pubDate>Sat, 10 Aug 2013 11:37:19 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:2</guid> <description> <p> Thanks for your comment. I don't believe that handling the notification directly without on_pending is a problem for the following reasons: </p> <p> 1) The existing I/O operation has already completed (even though the API may not have returned) if another thread picks up the notification using <a class="missing wiki">GetQueueCompletionStatus</a>(). </p> <p> 2) None of the multi-threaded samples I looked at on MSDN worry about calling WSARecv/WSASend in a notification handler even though the originating API may not have returned. </p> <p> 3) The MSDN docs for WSARecv explicitly state that this is acceptable for completion handlers: </p> <p> <strong>* "The WSARecv function using overlapped I/O can be called from within the completion routine of a previous WSARecv, WSARecvFrom, WSASend or WSASendTo function. For a given socket, I/O completion routines will not be nested. This permits time-sensitive data transmissions to occur entirely within a preemptive context." </strong>* </p> <p> The IOCP / <a class="missing wiki">GetQueueCompletionStatus</a> mechanism is just a way of running the completion handler on a pool thread. </p> <p> It's not clear to me how the on_pending function should work. When you call <a class="missing wiki">PostQueuedCompletionStatus</a> it doesn't even care if you supply an overlapped structure (google for 'The parameters to <a class="missing wiki">PostQueuedCompletionStatus</a> are not interpreted' ). It can be read by <a class="missing wiki">GetQueueedCompletionStatus</a> independently of whether the I/O has completed. You would have to use a call to <a class="missing wiki">GetOverlappedResult</a> to wait for the results. </p> <p> It looks to me like the code around on_pending and win_iocp_io_service::do_one has been written assuming that the notification from on_pending will be delivered when the I/O is complete. That's not the case - it just delivers the parameter values from <a class="missing wiki">PostQueuedCompletionStatus</a> when the next thread returns from <a class="missing wiki">GetQueueedCompletionStatus</a>. It does not look at the I/O operation associated with the overlapped structure. </p> <p> Although, my patch contains more changes I think it has the following benefits: </p> <p> 1) It results in less code in the ASIO IOCP implementation </p> <p> 2) During normal operations <a class="missing wiki">PostQueuedCompletionStatus</a> is only called when an I/O immediately fails. I believe this could have a performance benefit. Currently, it is called for every non-failing I/O operation effectively doubling the number of notifications that have to be handled by the service threads. </p> <p> 3) The code is closer to the structure of the IOCP samples that Microsoft provides </p> <p> 4) I believe the on_pending code has a problem because it's notification may be dequeued before the I/O has completed </p> </description> <category>Ticket</category> </item> <item> <author>Shane Powell <killerbee@…></author> <pubDate>Sat, 10 Aug 2013 21:28:28 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:3</guid> <description> <p> Looking at the asio code up util 1.53.0, the on_pending is used to delay the result of a early completion routine result until after the OS call (WSARecv/WSASend/...) has been completed. </p> <p> It works because the on_pending only calls <a class="missing wiki">PostQueuedCompletionStatus</a> if op ready_ was already 1. i.e. the completion routine has already fired. It doesn't post a <a class="missing wiki">PostQueuedCompletionStatus</a> if the completion routine hasn't fired, it just sets the ready_ to 1 which allows the the callers completion handler to be fired in the completion routine. So I don't think there is a problem with the on_pending code. </p> <p> So the change to 1.54.0 from 'overlapped_contains_result' to using '0' is what is causing all the problems. </p> <p> I can also see you may be correct is that it's not actually required in the case of WSARecv/WSASend/WSA* winsock functions to use the op ready_/overlapped_contains_result. It's used in other places so you may not be able to get rid of the overlapped_contains_result and storing the results in the overlapped structure tho. </p> <p> Also you would have to check all the other places that use the on_pending to see if it's completely ok to remove the ready flag ok as well. I see it's used with <a class="missing wiki">ReadFile/WriteFile/AcceptEx</a> </p> </description> <category>Ticket</category> </item> <item> <dc:creator>chris_kohlhoff</dc:creator> <pubDate>Tue, 01 Oct 2013 11:06:12 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/8967#comment:4 https://svn.boost.org/trac10/ticket/8967#comment:4 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">invalid</span> </li> </ul> <p> The OVERLAPPED object must remain valid until *both* the following conditions are true: </p> <p> 1) The initiating function (e.g. WSARecv) has returned. </p> <p> 2) The completion packet has been dequeued via <a class="missing wiki">GetQueuedCompletionStatus</a>. </p> <p> Freeing the memory immediately after (2) can result in an access violation if (1) has not yet happened. The on_pending() function and the ready_ flag exist to cover the case where (2) happens before (1). </p> <p> Boost 1.54 had a regression that prevented on_pending() from working correctly. This has now been fixed. See <a class="closed ticket" href="https://svn.boost.org/trac10/ticket/8933" title="#8933: Bugs: on the windows platform async reads with multiple threads can produce ... (closed: fixed)">#8933</a>. </p> Ticket simoncperkins@… Tue, 01 Oct 2013 11:18:18 GMT <link>https://svn.boost.org/trac10/ticket/8967#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:5</guid> <description> <p> Ok, thanks for fixing the problem but as stated above I disagree with your points. </p> <p> I believe you can safely free the overlapped structure in 2) if the IO in 1) has completed. That's how IO completion ports are intended to work. </p> <p> The current implementation is sub-optimal because it makes unnecessary calls to <a class="missing wiki">PostQueuedCompletionStatus</a> . </p> </description> <category>Ticket</category> </item> <item> <author>Smueller@…</author> <pubDate>Wed, 26 Feb 2014 05:36:51 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:6</guid> <description> <p> Chris, have you seen situations where dequeuing from an iocompletionport has occurred before the wsarecv has returned? If so? Have you seen access violations from this occurence? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>chris_kohlhoff</dc:creator> <pubDate>Mon, 05 May 2014 08:53:00 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:7</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8967#comment:6" title="Comment 6">Smueller@…</a>: </p> <blockquote class="citation"> <p> Chris, have you seen situations where dequeuing from an iocompletionport has occurred before the wsarecv has returned? If so? Have you seen access violations from this occurence? </p> </blockquote> <p> Yes and yes. </p> </description> <category>Ticket</category> </item> <item> <author>simoncperkins@…</author> <pubDate>Tue, 06 May 2014 08:47:03 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:8 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:8</guid> <description> <p> Chris, </p> <p> When you had the access violations were you using non-null lpNumberOfBytesRecvd / lpNumberOfBytesSent parameter values for WSARecv/WSASend like the current Boost ASIO implementation? </p> <p> The MSDN docs for both functions clearly state that these parameters should be NULL for overlapped I/O: <em> "Use NULL for this parameter if the lpOverlapped parameter is not NULL to avoid potentially erroneous results. This parameter can be NULL only if the lpOverlapped parameter is not NULL."</em> </p> <p> Perhaps, the APIs attempt to access the overlapped structure after completion using <a class="missing wiki">GetOverlappedResult</a> to extract the bytes read/written value and that's what causes the access violation. </p> <p> We've been running with my diffed code on Windows 7 and 8 for nine months now both in release and debug with no access violations. However, the code does use NULL for the lpNumberOfBytesRecvd / lpNumberOfBytesSent parameters. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>chris_kohlhoff</dc:creator> <pubDate>Tue, 06 May 2014 09:00:06 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:9 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:9</guid> <description> <p> The MSDN docs are clearly stating the wrong thing :) That parameter must be non-null or you will get other crashes on some versions of Windows, and you can see that in some of the comments at the bottom of the WSARecv page. It may no longer be a problem with Windows 7 or later, but it was definitely the case on older versions of Windows. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Tue, 06 May 2014 22:42:32 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:10 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:10</guid> <description> <p> Hi Chris, the comment on WSARecv in MSDN doesn't seem relevant, as the commenter was trying to look at first chance exceptions thrown, and handled, by the kernel. Can you give us more details as to what the versions of Windows were where you saw the problem? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>chris_kohlhoff</dc:creator> <pubDate>Wed, 07 May 2014 00:07:29 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8967#comment:11 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8967#comment:11</guid> <description> <p> The comment: </p> <blockquote class="citation"> <blockquote> <p> lpNumberOfBytesRecvd </p> </blockquote> <p> ... </p> <blockquote> <p> Use NULL for this parameter if the lpOverlapped parameter is not NULL to avoid potentially erroneous results </p> </blockquote> <blockquote> <p> is BAD advice. </p> </blockquote> <blockquote> <p> If call WSARecv and pass NULL as lpNumberOfBytesRecvd and not-NULL lpOverlapped, then your application will generate a bunch of these: </p> </blockquote> <blockquote> <p> Access violation - code c0000005 (first chance) </p> </blockquote> </blockquote> <p> describes the problem exactly. I encountered it on Windows XP, or possibly 2000, but I no longer have the system details. Furthermore, the MSDN Library shipped with Visual Studio 2008 did not include the "advice" for lpNumberOfBytesRecvd. What it does say is: </p> <blockquote class="citation"> <p> If an overlapped operation completes immediately, WSARecv returns a value of zero and the lpNumberOfBytesRecvd parameter is updated with the number of bytes received ... </p> </blockquote> <p> This text still appears in the latest MSDN, for what it's worth. </p> </description> <category>Ticket</category> </item> </channel> </rss>