asio
asio copied to clipboard
Enable asio::ssl::stream's ability to be moved to another contexts
Basic sockets, such as asio::ip::tcp::socket
naturally have an ability to be re-bound to a different context, via release() + assign()
, using a native_handle
. That's a great thing in a shared-nothing nano-service application, where different nano-services run on a separate threads, each with its own io_context
, to avoid CPU cache pollution. In such an architecture there may be some nano-services that establish connections (say, an optional SOCKS) and other nano-services that use established connections without the knowledge of how they are established.
This works well, until we start using asio::ssl::stream
. For some reason, asio::ssl::detail::stream_core
implementation uses timers to implement a queue of pending operation handlers (read and write). This seems like a bad decision. Times are bound to a single context, the one that was supplied in the stream_core
's constructor, at the moment of its creation. This leads to inability to completely move asio::ssl::stream
out of it's original context, even though the underlying layer, asio::ip::tcp::socket
, can be moved.
Leaving timers in one context and underlying layer in another will inevitably lead to violation of 'single strand usage' requirement and therefore to data races. In my case, this is easily reproduced by issuing async_shutdown()
while there is some pending IO already, causing segfaults.
Is it possible to replace timers with an actual handler queue? After all, timers there aren't used to actually track time, so this looks like a hack anyway.
Then, handlers can be invoked in a same way as done today: via fake 0-byte read:
...
// The SSL operation is done and we can invoke the handler, but we
// have to keep in mind that this function might be being called from
// the async operation's initiating function. In this case we're not
// allowed to call the handler directly. Instead, issue a zero-sized
// read so the handler runs "as-if" posted using io_context::post().
if (start)
{
next_layer_.async_read_some(
asio::buffer(core_.input_buffer_, 0),
ASIO_MOVE_CAST(io_op)(*this));
// Yield control until asynchronous operation completes. Control
// resumes at the "default:" label below.
return;
}
...