[asio] Does ssl::stream allow multiple pending async operations?
The documentation for ssl::stream says: "Shared objects: Unsafe. The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand." Does this mean we have to wait for an async operation to finish before another is started (for read/read, read/write, and write/write)? (Async operations refer to async_read_some() and async_write_some(), not the composed operations.) If so, is the operation said to have finished when the handler is invoked or when the handler returns? That is: void handler( const boost::system::error_code& error, std::size_t bytes_transferred ) { // Does the operation finish here ... } // or here? If not, what's the difference between the async operations of ip::tcp::socket and ssl::stream then (as strand just avoids concurrency for the thread-unsafe)? I came across different views on this problem. This answer[1] suggests that simply using the same strand (ie. no concurrency) will suffice. Another one[2] says multiple pending asynchronous operations can be problematic for ssl::stream even if no concurrency occurs. Thanks. [1] https://stackoverflow.com/questions/27212123/executing-asynchronous-operatio... [2] https://stackoverflow.com/questions/15352472/using-boosts-ssl-asio-code-asyn...
On Sun, Nov 5, 2017 at 6:00 AM, Phil Sean via Boost-users
Does this mean we have to wait for an async operation to finish before another is started (for read/read, read/write, and write/write)?
I believe you can have one outstanding read and one outstanding write operation pending simultaneously for ssl::stream and tcp::socket.
(Async operations refer to async_read_some() and async_write_some(), not the composed operations.)
All calls to initiating functions (functions prefixed with "async_") are considered asynchronous operations, composed or otherwise.
If so, is the operation said to have finished when the handler is invoked or when the handler returns?
The operation is finished when the handler is invoked.
If not, what's the difference between the async operations of ip::tcp::socket and ssl::stream
tcp::socket transfers buffers unmodified, while ssl::stream encrypts data both ways and supports other cryptographic operations such as providing certificates during handshakes.
I came across different views on this problem. This answer[1] suggests that simply using the same strand (ie. no concurrency) will suffice. Another one[2] says multiple pending asynchronous operations can be problematic for ssl::stream even if no concurrency occurs.
It is very easy to mix up "thread safety" with "multiple pending asynchronous operations". The Thread Safety clause (Shared Objects: unsafe) is very specific. It means that no member functions may be invoked concurrently. That is to say: * No async_read from two threads at the same time * No async_write from two threads at the same time * No cancel or close while also calling async_* from multiple threads concurrently The last point is especially important. According to the Asio documentation, you cannot close a socket from another thread while asynchronous operations are pending. You must post a function to the implicit or explicit strand which closes the socket. This should not be confused with "multiple pending operations." For example, you can call async_read followed by async_write (from the correct implicit or explicit strand), without waiting for any completions. There is only one thread in this scenario. Note the careful wording of "implicit or explicit strand." This means: * implicit strand: Only one thread calling io_service::run * explicit strand: Completion handlers are wrapped using io_service::strand::wrap I hope this helps!
(In reply to Vinnie Falco
I believe you can have one outstanding read and one outstanding write operation pending simultaneously for ssl::stream and tcp::socket.
What about two outstanding read operations or two outstanding write operations? I think they're safe for tcp::socket but not ssl::stream. Moreover, because the documentation has an extra clause in thread safety for ssl::stream which tcp::socket does not have: "The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand," there should be a difference in the calling requirements for the asynchronous operations in ssl::stream and tcp::socket. Specifically, is there some example code/model that can be used with tcp::socket but not ssl::stream, just because of the extra clause?
Note the careful wording of "implicit or explicit strand." This means: * implicit strand: Only one thread calling io_service::run * explicit strand: Completion handlers are wrapped using io_service::strand::wrap
Apart from handlers called within strands, what other effects does wrapping completion handlers with io_service::strand::wrap have? For example: void handler( const boost::system::error_code& error, std::size_t bytes_transferred ) { ... } void wrapped_handler( const boost::system::error_code& error, std::size_t bytes_transferred ) { strand_.dispatch(boost::bind(&handler, error, bytes_transferred)); } // How does the following differ? ssl_stream.async_read_some(buffer, &wrapped_handler); ssl_stream.async_read_some(buffer, strand_.wrap(&handler)); Since Boost uses argument dependent lookup (ADL) for composed operations [1], I guess it could be similar here. Thanks! [1] https://stackoverflow.com/questions/12794107/why-do-i-need-strand-per-connec...
On Sun, Nov 5, 2017 at 8:46 AM, Phil Sean via Boost-users
What about two outstanding read operations or two outstanding write operations? I think they're safe for tcp::socket but not ssl::stream.
On this, I am unsure, as I cannot find where the Asio documentation states this clearly.
Moreover, because the documentation has an extra clause in thread safety for ssl::stream which tcp::socket does not have: "The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand,"
Very good point, I had not noticed that the clauses were different. This affects Beast, I will update its documentation.
there should be a difference in the calling requirements for the asynchronous operations in ssl::stream and tcp::socket. Specifically, is there some example code/model that can be used with tcp::socket but not ssl::stream, just because of the extra clause?
I can't think of one.
Apart from handlers called within strands, what other effects does wrapping completion handlers with io_service::strand::wrap have?
The additional execution guarantee, "none of those handlers will execute concurrently." of the strand is the only effect I can think of: http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/reference/io_servic... Thanks
On 11/05/2017 05:46 PM, Phil Sean via Boost-users wrote:
What about two outstanding read operations or two outstanding write operations? I think they're safe for tcp::socket but not ssl::stream.
You are not guaranteed that multiple asynchronous write operations are executed in the order you initiate them, so having more than one outstanding operation can lead to unexpected results.
On 6/11/2017 12:00, Bjorn Reese wrote:
On 11/05/2017 05:46 PM, Phil Sean wrote:
What about two outstanding read operations or two outstanding write operations? I think they're safe for tcp::socket but not ssl::stream.
You are not guaranteed that multiple asynchronous write operations are executed in the order you initiate them, so having more than one outstanding operation can lead to unexpected results.
They're also not guaranteed to execute atomically -- ie. you can have part of write1 interleaved with part of write2, although all of the bytes in each individual write should eventually turn up in the correct relative order -- but that's rarely sufficient. The same applies for reads -- if you have two outstanding reads on the same socket/stream then any received bytes could up spread non-deterministically between the two completion handlers. So regardless of whether the objects officially support it or not, I cannot think of a situation in which actually doing that would be anything other than a mess. So just don't do it. UDP is a different story; there are scenarios where multiple pending reads or writes makes sense there. But not for anything stream-oriented.
participants (4)
-
Bjorn Reese
-
Gavin Lambert
-
Phil Sean
-
Vinnie Falco