Boost C++ Libraries: Ticket #8722: The boost::asio::windows::stream_handle::read_some method may (incorrectly) throw when used with named-pipes in message mode (on Windows) and receives an ERROR_MORE_DATA error https://svn.boost.org/trac10/ticket/8722 <p> Please note that this ticket is similar to Ticket<a class="closed ticket" href="https://svn.boost.org/trac10/ticket/2936" title="#2936: Bugs: ASIO crashes when reads data from Windows Pipe (closed: fixed)">#2936</a>. </p> <p> When using named-pipes in message mode on Windows it is possible that the read operation on the client side will fail with <a class="missing wiki">GetLastError</a>() == ERROR_MORE_DATA. In the overlapped IO case the latter means that <a class="missing wiki">GetOverlappedResult</a>(...) fails and reports this error. </p> <p> Now looking at the win_iocp_handle_service::do_read(...) when in the above scenario if the <a class="missing wiki">GetOverlappedResult</a>(...) fails do_read(...) would update the ec parameter with the error value (in this case ERROR_MORE_DATA) and then boost::asio::windows::stream_handle::read_some(...) would throw. The latter may be incorrect as the <a class="missing wiki">ReadFile</a>(...) function may have actually read some data but the information about this data (bytes read) would be lost when the exception is thrown leaving the upper layers practically unable to recover from this situation even if they'd catch the exception... (see attached code) </p> <p> It seems the fix for this should be to avoid throwing the exception by adding an "if" statement similar to the one after the <a class="missing wiki">ReadFile</a>(...) call (several lines above in do_read(...)) e.g. making the code around <a class="missing wiki">GetOverlappedResult</a>(...) look as follows: </p> <blockquote> <p> <em> Wait for the operation to complete. DWORD bytes_transferred = 0; ok = ::<a class="missing wiki">GetOverlappedResult</a>(impl.handle_, </em></p> <blockquote> <p> &amp;overlapped, &amp;bytes_transferred, TRUE); </p> </blockquote> <p> if (!ok) { </p> <blockquote> <p> DWORD last_error = ::<a class="missing wiki">GetLastError</a>(); if (last_error != ERROR_MORE_DATA) <em> &lt;---- added "if" statement { </em></p> <blockquote> <p> if (last_error == ERROR_HANDLE_EOF) { </p> <blockquote> <p> ec = boost::asio::error::eof; </p> </blockquote> <p> } else { </p> <blockquote> <p> ec = boost::system::error_code(last_error, </p> <blockquote> <p> boost::asio::error::get_system_category()); </p> </blockquote> </blockquote> <p> } return 0; </p> </blockquote> <p> } </p> </blockquote> <p> } </p> </blockquote> <p> Please comment if the proposed fix is correct as I'd like to apply it before it is released in an official BOOST release. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/8722 Trac 1.4.3 Dentcho Bankov <dbankov@…> Mon, 24 Jun 2013 12:30:23 GMT attachment set https://svn.boost.org/trac10/ticket/8722 https://svn.boost.org/trac10/ticket/8722 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">BOOSTCA.7z</span> </li> </ul> <p> Windows named-pipe message mode example illustrating the described problem </p> Ticket Dentcho Bankov <dbankov@…> Mon, 24 Jun 2013 12:42:11 GMT attachment set https://svn.boost.org/trac10/ticket/8722 https://svn.boost.org/trac10/ticket/8722 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">proposedFix.patch</span> </li> </ul> <p> Proposed fix patch </p> Ticket Dentcho Bankov <dbankov@…> Mon, 24 Jun 2013 12:48:42 GMT <link>https://svn.boost.org/trac10/ticket/8722#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8722#comment:1</guid> <description> <p> Code formatting in my previous comment is awful. I'm sorry I should have previewed it. </p> <p> Here is another attempt: </p> <pre class="wiki">// Wait for the operation to complete. DWORD bytes_transferred = 0; ok = ::GetOverlappedResult(impl.handle_, &amp;overlapped, &amp;bytes_transferred, TRUE); if (!ok) { DWORD last_error = ::GetLastError(); if (last_error != ERROR_MORE_DATA) // &lt;--- the added "if" statement { if (last_error == ERROR_HANDLE_EOF) { ec = boost::asio::error::eof; } else { ec = boost::system::error_code(last_error, boost::asio::error::get_system_category()); } return 0; } } </pre> </description> <category>Ticket</category> </item> <item> <dc:creator>chris_kohlhoff</dc:creator> <pubDate>Tue, 01 Oct 2013 08:26:22 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/8722#comment:2 https://svn.boost.org/trac10/ticket/8722#comment:2 <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">fixed</span> </li> </ul> <p> Fixed on trunk in <a class="changeset" href="https://svn.boost.org/trac10/changeset/85759" title="Inore ERROR_MORE_DATA as a non-fatal error when returned by ...">[85759]</a>. Merged to release in <a class="changeset" href="https://svn.boost.org/trac10/changeset/85838" title="Merge asio from trunk. ...">[85838]</a>. </p> Ticket aoney@… Fri, 09 May 2014 22:31:48 GMT status changed; resolution deleted https://svn.boost.org/trac10/ticket/8722#comment:3 https://svn.boost.org/trac10/ticket/8722#comment:3 <ul> <li><strong>status</strong> <span class="trac-field-old">closed</span> → <span class="trac-field-new">reopened</span> </li> <li><strong>resolution</strong> <span class="trac-field-deleted">fixed</span> </li> </ul> <p> On the above note 'Fixed on trunk in <a class="changeset" href="https://svn.boost.org/trac10/changeset/85759" title="Inore ERROR_MORE_DATA as a non-fatal error when returned by ...">[85759]</a>. Merged to release in <a class="changeset" href="https://svn.boost.org/trac10/changeset/85838" title="Merge asio from trunk. ...">[85838]</a>.' </p> <p> I think this may have been fixed incorrectly. </p> <p> First note that the fix in the trunk is *not* the fix suggested by Dentcho above. The fix that went into trunk has a "return 0;" at the end of the "if (!ok)" clause (see below), not in the inner clause like above. </p> <p> Thus, the change does not revert the logic back to how it looked in boost 1.47 (in boost 1.48, the ERROR_MORE_DATA check was removed, only to be added back later). </p> <p> Now to the behavior - in message mode, <a class="missing wiki">ReadFile</a> et. all will return partial data and ERROR_MORE_DATA to indicate additional parts of the message remain in the buffer. The boost method signature isn't rich enough to return this extra information (that the read splits a message rather than terminates it). However, boost *does* need to at least return the number of bytes read so far. And trunk's (and 1.55.0's) logic below, unlike Dentcho's logic above, clearly fails to do that because the return 0 is located differently: </p> <blockquote> <p> if (!ok) { </p> <blockquote> <p> DWORD last_error = ::<a class="missing wiki">GetLastError</a>(); if (last_error != ERROR_MORE_DATA) { </p> <blockquote> <p> if (last_error == ERROR_HANDLE_EOF) { </p> <blockquote> <p> ec = boost::asio::error::eof; </p> </blockquote> <p> } else { </p> <blockquote> <p> ec = boost::system::error_code(last_error, </p> <blockquote> <p> boost::asio::error::get_system_category()); </p> </blockquote> </blockquote> <p> } </p> </blockquote> <p> } return 0; </p> </blockquote> <p> } </p> </blockquote> Ticket chris_kohlhoff Sat, 10 May 2014 09:14:39 GMT status changed; resolution set https://svn.boost.org/trac10/ticket/8722#comment:4 https://svn.boost.org/trac10/ticket/8722#comment:4 <ul> <li><strong>status</strong> <span class="trac-field-old">reopened</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> Can you please clarify what you mean by: </p> <blockquote class="citation"> <p> Thus, the change does not revert the logic back to how it looked in boost 1.47 (in boost 1.48, the ERROR_MORE_DATA check was removed, only to be added back later). </p> </blockquote> <p> as version control shows no diffs in win_iocp_handle_service.ipp between those versions. </p> <p> In any case, I now think that the premise of this bug report was wrong. The read_some() method <strong>should</strong> throw if <a class="missing wiki">ReadFile</a> fails with ERROR_MORE_DATA. If you don't want it to throw, you use the error_code overload. It just needs to also return the bytes_transferred value when it does. </p> <p> This should be done as a new ticket as the behaviour is different from the one described in this bug report. </p> Ticket chris_kohlhoff Sat, 10 May 2014 09:18:16 GMT <link>https://svn.boost.org/trac10/ticket/8722#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8722#comment:5</guid> <description> <p> Opened <a class="new ticket" href="https://svn.boost.org/trac10/ticket/10034" title="#10034: Bugs: Change win_iocp_handle_service to still return bytes_transferred if ... (new)">#10034</a>. </p> </description> <category>Ticket</category> </item> <item> <author>aoney@…</author> <pubDate>Fri, 16 May 2014 00:13:32 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8722#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8722#comment:6</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/8722#comment:4" title="Comment 4">chris_kohlhoff</a>: </p> <blockquote class="citation"> <p> Can you please clarify what you mean by: </p> <blockquote class="citation"> <p> Thus, the change does not revert the logic back to how it looked in boost 1.47 (in boost 1.48, the ERROR_MORE_DATA check was removed, only to be added back later). </p> </blockquote> <p> as version control shows no diffs in win_iocp_handle_service.ipp between those versions. </p> </blockquote> <p> Never mind on the 1.47 comment (I was looking at our fork of 1.47, not boost's). </p> <blockquote class="citation"> <p> In any case, I now think that the premise of this bug report was wrong. The read_some() method <strong>should</strong> throw if <a class="missing wiki">ReadFile</a> fails with ERROR_MORE_DATA. If you don't want it to throw, you use the error_code overload. It just needs to also return the bytes_transferred value when it does. </p> <p> This should be done as a new ticket as the behaviour is different from the one described in this bug report. </p> </blockquote> <p> Exposing the 'torn message' case is indeed better than the alternative. That said, for us being able to treat a message pipe like a byte-stream pipe has proved valuable. </p> </description> <category>Ticket</category> </item> <item> <author>Dentcho Bankov <dbankov@…></author> <pubDate>Tue, 01 Aug 2017 15:44:04 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8722#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8722#comment:7</guid> <description> <p> We hit this problem again now that we moved to BOOST 1.61. At the time I filed this ticket I suspected that the correct fix could be to provide the bytes_transmitted value in the exception but weren't happy with this because we'd have to handle a standard use case through an exception. Using the error_code overload seems to be a good enough way to avoid throwing an exception in a normal use case so I'd agree this is an acceptable solution. Still <a class="new ticket" href="https://svn.boost.org/trac10/ticket/10034" title="#10034: Bugs: Change win_iocp_handle_service to still return bytes_transferred if ... (new)">#10034</a> is not fixed so I'll try to provide a fix for it. </p> </description> <category>Ticket</category> </item> </channel> </rss>