[asio] Does ssl::stream allow multiple pending async operations?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

[asio] Does ssl::stream allow multiple pending async operations?

Boost - Users mailing list
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-operations-within-the-same-strand#27214989
[2] https://stackoverflow.com/questions/15352472/using-boosts-ssl-asio-code-async-write-some-hangs#15367222
_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: [asio] Does ssl::stream allow multiple pending async operations?

Boost - Users mailing list
On Sun, Nov 5, 2017 at 6:00 AM, Phil Sean via Boost-users
<[hidden email]> wrote:
> 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!
_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: [asio] Does ssl::stream allow multiple pending async operations?

Boost - Users mailing list
(In reply to Vinnie Falco <vinnie.falco_at_[hidden]>)

Thanks for your reply!
It addressed most of my problems. Just a few points to make clear:

> 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-connection-when-using-boostasio#12801042
_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: [asio] Does ssl::stream allow multiple pending async operations?

Boost - Users mailing list
On Sun, Nov 5, 2017 at 8:46 AM, Phil Sean via Boost-users
<[hidden email]> wrote:
> 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_service__strand.html>

Thanks
_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: [asio] Does ssl::stream allow multiple pending async operations?

Boost - Users mailing list
In reply to this post by Boost - Users mailing list
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.
_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: [asio] Does ssl::stream allow multiple pending async operations?

Boost - Users mailing list
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.

_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users