I consider myself reasonably experienced with asio but can't figure out how to correctly perform async_read
and async_write
on a boost::asio::ssl::stream<boost::asio::ip::tcp::socket>
. I have created the following minimal example https://github.com/ladnir/asio-ssl-stackoverflow which I explain next.
My goal is quite simple, perform full duplex async read and write on a ssl_stream
. The documentation is clear that you need to perform the async_read
and async_write
calls from within a strand which I do.
My setup is to have an io_context
with multiple threads. Data is continuously sent and received on the socket. Sending and receiving data each have their own callback chain. In the completion handler for each I simply schedule another send or receive operation. All of this is performed within a strand. Below is the main bit of code
std::function<void(bool, ssl::stream<tcp::socket>&, io_context::strand&, u64)> f =
[&](bool send, ssl::stream<tcp::socket>& sock, io_context::strand& strand, u64 t) {
strand.dispatch([&, send, t]() {
std::vector<u8> buffer(10000);
auto bb = mutable_buffer(buffer.data(), buffer.size());
auto callback = [&, send, t, buffer = std::move(buffer), moveOnly = std::unique_ptr<int>{}](boost::system::error_code error, std::size_t n) mutable {
if (error) {
std::cout << error.message() << std::endl;
std::terminate();
}
// perform another operation or complete.
if (t)
f(send, sock, strand, t - 1);
else
--spinLock;
};
if (send)
async_write(sock, bb, std::move(callback));
else
async_read(sock, bb, std::move(callback));
});
};
A send and receive callback chain is then started for the server and client sockets.
// launch our callback chains.
f(true, srvSocket, srvStrand, trials);
f(false, srvSocket, srvStrand, trials);
f(true, cliSocket, cliStrand, trials);
f(false, cliSocket, cliStrand, trials);
It seems that despite the use of the strand, something inside OpenSSL is not being performed in a thread-safe manner. When I run the code I sometimes get a decryption failure and sometimes it just crashes somewhere in OpenSSL.
If I use a tcp::socket
this code works fine. If I make the io_context
single-threaded then it works fine. I have tested this on ubuntu and windows.
It seems from related questions, e.g. this, that full duplex should work as long as you wrap it in a strand.
Does anyone see what I'm doing wrong? Maybe it is simply not safe to perform an async_read
/async_write
when the other is already scheduled?
Notes on Sehe's solution: After comparing my code to Sehe's solution I determined that my code has one major bug. The execution context of the ssl::stream<tcp::socket>
's is the multi-thread io_context
. As such, there is no guarantee that the ssl::stream
perform work on my strand. Moreover, how could it? The ssl::stream
is unaware of my strand and only happens to be executed on it during the initial call. When the ssl::stream
gets called back from the underlying socket, it will/might schedule more read/write operations but it wont be on my chosen strand.
The only thing guaranteed is that the ssl::stream
is on the execution context that it was constructed with. A simple fix is to construct the stream with a strand as it's execution context.
I updated the github repo above to contain my solution.
Thanks, Sehe for the insight.
wrap
member function). I agree that the new executor interface is much more ergonomic – Vina