diff --git a/CHANGELOG.md b/CHANGELOG.md index 56071180..42e39b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version XXX: + +* Fix assert when basic_stream used as underlying of ssl::stream with zero-length write. + +-------------------------------------------------------------------------------- + Version 301: * Fix Travis CI bug. diff --git a/include/boost/beast/core/detail/stream_base.hpp b/include/boost/beast/core/detail/stream_base.hpp index 3f675d47..e6e80620 100644 --- a/include/boost/beast/core/detail/stream_base.hpp +++ b/include/boost/beast/core/detail/stream_base.hpp @@ -56,19 +56,25 @@ struct stream_base class pending_guard { - bool& b_; + bool* b_ = nullptr; bool clear_ = true; public: ~pending_guard() { - if(clear_) - b_ = false; + if(clear_ && b_) + *b_ = false; + } + + pending_guard() + : b_(nullptr) + , clear_(true) + { } explicit pending_guard(bool& b) - : b_(b) + : b_(&b) { // If this assert goes off, it means you are attempting // to issue two of the same asynchronous I/O operation @@ -77,8 +83,8 @@ struct stream_base // calls to async_read_some. Only one pending call of // each I/O type (read and write) is permitted. // - BOOST_ASSERT(! b_); - b_ = true; + BOOST_ASSERT(! *b_); + *b_ = true; } pending_guard( @@ -89,11 +95,29 @@ struct stream_base { } + void assign(bool& b) + { + BOOST_ASSERT(!b_); + BOOST_ASSERT(clear_); + b_ = &b; + + // If this assert goes off, it means you are attempting + // to issue two of the same asynchronous I/O operation + // at the same time, without waiting for the first one + // to complete. For example, attempting two simultaneous + // calls to async_read_some. Only one pending call of + // each I/O type (read and write) is permitted. + // + BOOST_ASSERT(! *b_); + *b_ = true; + } + void reset() { BOOST_ASSERT(clear_); - b_ = false; + if (b_) + *b_ = false; clear_ = false; } }; diff --git a/include/boost/beast/core/impl/basic_stream.hpp b/include/boost/beast/core/impl/basic_stream.hpp index 30f00e66..73fb9169 100644 --- a/include/boost/beast/core/impl/basic_stream.hpp +++ b/include/boost/beast/core/impl/basic_stream.hpp @@ -269,6 +269,8 @@ class transfer_op std::move(*this)); } + static bool never_pending_; + public: template transfer_op( @@ -278,10 +280,25 @@ public: : async_base( std::forward(h), s.get_executor()) , impl_(s.impl_) - , pg_(state().pending) + , pg_() , b_(b) { - (*this)({}); + if (buffer_bytes(b_) == 0 && state().pending) + { + // Workaround: + // Corner case discovered in https://github.com/boostorg/beast/issues/2065 + // Enclosing SSL stream wishes to complete a 0-length write early by + // executing a 0-length read against the underlying stream. + // This can occur even if an existing async_read is in progress. + // In this specific case, we will complete the async op with no error + // in order to prevent assertions and/or internal corruption of the basic_stream + this->complete(false, error_code(), 0); + } + else + { + pg_.assign(state().pending); + (*this)({}); + } } void diff --git a/test/beast/core/basic_stream.cpp b/test/beast/core/basic_stream.cpp index ab71bff5..24b94d04 100644 --- a/test/beast/core/basic_stream.cpp +++ b/test/beast/core/basic_stream.cpp @@ -1366,6 +1366,34 @@ public: }; } + void + testIssue2065() + { + using stream_type = basic_stream; + + char buf[4]; + net::io_context ioc; + std::memset(buf, 0, sizeof(buf)); + net::mutable_buffer mb(buf, sizeof(buf)); + auto const ep = net::ip::tcp::endpoint( + net::ip::make_address("127.0.0.1"), 0); + + // async_read_some + + { + // success + test_server srv("*", ep, log); + stream_type s(ioc); + s.socket().connect(srv.local_endpoint()); + s.expires_never(); + s.async_read_some(mb, handler({}, 1)); + s.async_read_some(net::buffer(buf, 0), handler({}, 0)); + ioc.run(); + ioc.restart(); + } + } + void run() { @@ -1382,6 +1410,7 @@ public: boost::ignore_unused(&basic_stream_test::testAwaitableCompilation); #endif boost::ignore_unused(&basic_stream_test::testConnectionConditionArgs); + testIssue2065(); } };