I have a boost::asio::ssl::stream<boost::asio::ip::tcp::socket>
whose io_context
is serviced by a thread, Tskt
.
I have a "write loop" that asynchronously writes to the socket, invoked from a different thread, Tsnd
:
void handleBufferWritten(
boost::system::error_code const& ec,
std::shared_ptr<std::vector<char>> const& pSrc, std::size_t curSrcPos,
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& rSkt) {
if (ec)
return;
if (!pSrc)
return;
static auto const MaxToWrite = 200;
auto const howManyBytesRemaining = pSrc->size() - curSrcPos;
if (howManyBytesRemaining == 0)
return; // loop exit
// else there are more bytes to be written:
auto const howManyBytesToWrite =
std::min(howManyBytesRemaining, MaxToWrite);
boost::asio::async_write(rSkt,
boost::asio::buffer(pSrc->data() + curSrcPos, howManyBytesToWrite),
[pSrc, nextSrcPos = curSrcPos + howManyBytesToWrite,
&rSkt](boost::system::error_code const& ec,
std::size_t /*bytesTransferred*/) {
handleBufferWritten(ec, pSrc, nextSrcPos, rSkt);
});
}
void startBufferWrite(
std::vector<char> const& src,
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& rSkt) {
auto pBuf = std::make_shared<std::vector<char>>(src);
// start loop:
handleBufferWritten(boost::system::error_code{}, pBuf, 0, rSkt);
}
I built with "-fsanitize=thread" and it warned me of a data race.
As a result of my analysis, I believe that there is no resolution between 2 threads contending on a deadline_timer
's expiry point (Tskt
thread writing: https://github.com/boostorg/asio/blob/25dc6780c2c73dd6a4d74e65e854fc0f705cbb60/include/boost/asio/ssl/detail/io.hpp#L182, Tsnd
thread reading: https://github.com/boostorg/asio/blob/25dc6780c2c73dd6a4d74e65e854fc0f705cbb60/include/boost/asio/ssl/detail/io.hpp#L179). (I'm using boost1.67, but the difference between this and the current version is minimal.)
Here's the ThreadSanitizer warning:
==================
WARNING: ThreadSanitizer: data race (pid=19106)
Write of size 8 at 0x7b440000ff78 by thread T4:
#0 boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::expires_at(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type&, boost::posix_time::ptime const&, boost::system::error_code&) /boost-1_67/boost/asio/detail/deadline_timer_service.hpp:194 (MyApp+0x000000a6ac6c)
#1 boost::asio::basic_deadline_timer<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> >::expires_at(boost::posix_time::ptime const&) /boost-1_67/boost/asio/basic_deadline_timer.hpp:439 (MyApp+0x000000a684a1)
#2 boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >::operator()(boost::system::error_code, unsigned long, int) /boost-1_67/boost/asio/ssl/detail/io.hpp:182 (MyApp+0x000000a6ea5b)
#3 boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>::operator()() /boost-1_67/boost/asio/detail/bind_handler.hpp:164 (MyApp+0x000000a802aa)
#4 void boost::asio::asio_handler_invoke<boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long> >(boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>&, ...) /boost-1_67/boost/asio/handler_invoke_hook.hpp:69 (MyApp+0x000000a7f966)
#5 void boost_asio_handler_invoke_helpers::invoke<boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>, std::function<void (boost::system::error_code const&, unsigned long)> >(boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>&, std::function<void (boost::system::error_code const&, unsigned long)>&) /boost-1_67/boost/asio/detail/handler_invoke_helpers.hpp:37 (MyApp+0x000000a7e8d0)
#6 void boost::asio::ssl::detail::asio_handler_invoke<boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>, boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >(boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>&, boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >*) /boost-1_67/boost/asio/ssl/detail/io.hpp:316 (MyApp+0x000000a7c878)
#7 void boost_asio_handler_invoke_helpers::invoke<boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>, boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> > >(boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>&, boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >&) /boost-1_67/boost/asio/detail/handler_invoke_helpers.hpp:37 (MyApp+0x000000a7b736)
#8 void boost::asio::detail::handler_work<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::asio::system_executor>::complete<boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long> >(boost::asio::detail::binder2<boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >, boost::system::error_code, unsigned long>&, boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >&) /boost-1_67/boost/asio/detail/handler_work.hpp:82 (MyApp+0x000000a79dc5)
#9 boost::asio::detail::reactive_socket_recv_op<boost::asio::mutable_buffers_1, boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> > >::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) /boost-1_67/boost/asio/detail/reactive_socket_recv_op.hpp:122 (MyApp+0x000000a779b0)
#10 boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /boost-1_67/boost/asio/detail/scheduler_operation.hpp:40 (MyApp+0x00000096f0c6)
#11 boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /boost-1_67/boost/asio/detail/impl/scheduler.ipp:401 (MyApp+0x0000009707ff)
#12 boost::asio::detail::scheduler::run(boost::system::error_code&) /boost-1_67/boost/asio/detail/impl/scheduler.ipp:154 (MyApp+0x0000009704b5)
#13 boost::asio::io_context::run() /boost-1_67/boost/asio/impl/io_context.ipp:62 (MyApp+0x000000970aa0)
#14 operator() /src/MyThread.cpp:24 (MyApp+0x0000009e3101)
#15 __invoke_impl<void, MyThread::start()::<lambda()> > /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/invoke.h:60 (MyApp+0x0000009e35ab)
#16 __invoke<MyThread::start()::<lambda()> > /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/invoke.h:95 (MyApp+0x0000009e32da)
#17 _M_invoke<0> /opt/rh/devtoolset-7/root/usr/include/c++/7/thread:234 (MyApp+0x0000009e3b14)
#18 operator() /opt/rh/devtoolset-7/root/usr/include/c++/7/thread:243 (MyApp+0x0000009e3aa6)
#19 _M_run /opt/rh/devtoolset-7/root/usr/include/c++/7/thread:186 (MyApp+0x0000009e3a4c)
#20 execute_native_thread_routine <null> (MyApp+0x0000010e6f7e)
Previous read of size 8 at 0x7b440000ff78 by thread T3 (mutexes: write M1465, write M1880, write M1853):
#0 boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::expires_at(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type const&) const /boost-1_67/boost/asio/detail/deadline_timer_service.hpp:180 (MyApp+0x000000a6ad70)
#1 boost::asio::basic_deadline_timer<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> >::expires_at() const /boost-1_67/boost/asio/basic_deadline_timer.hpp:411 (MyApp+0x000000a68534)
#2 boost::asio::ssl::detail::stream_core::expiry(boost::asio::basic_deadline_timer<boost::posix_time::ptime, boost::asio::time_traits<boost::posix_time::ptime> > const&) /boost-1_67/boost/asio/ssl/detail/stream_core.hpp:84 (MyApp+0x000000a66974)
#3 boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::write_op<boost::asio::const_buffers_1>, boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> > >::operator()(boost::system::error_code, unsigned long, int) /boost-1_67/boost/asio/ssl/detail/io.hpp:179 (MyApp+0x000000a760ac)
#4 void boost::asio::ssl::detail::async_io<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::write_op<boost::asio::const_buffers_1>, boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> > >(boost::asio::basic_stream_socket<boost::asio::ip::tcp>&, boost::asio::ssl::detail::stream_core&, boost::asio::ssl::detail::write_op<boost::asio::const_buffers_1> const&, boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> >&) /boost-1_67/boost/asio/ssl/detail/io.hpp:334 (MyApp+0x000000a73838)
#5 boost::asio::async_result<std::decay<boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> > >::type, void (boost::system::error_code, unsigned long)>::return_type boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::async_write_some<boost::asio::const_buffers_1, boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> > >(boost::asio::const_buffers_1 const&, boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> >&&) /boost-1_67/boost/asio/ssl/stream.hpp:653 (MyApp+0x000000a718cc)
#6 boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> >::operator()(boost::system::error_code const&, unsigned long, int) /boost-1_67/boost/asio/impl/write.hpp:259 (MyApp+0x000000a6f084)
#7 void boost::asio::detail::start_write_buffer_sequence_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)> >(boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >&, boost::asio::mutable_buffers_1 const&, boost::asio::mutable_buffer const* const&, boost::asio::detail::transfer_all_t, std::function<void (boost::system::error_code const&, unsigned long)>&) /boost-1_67/boost/asio/impl/write.hpp:345 (MyApp+0x000000a6bcc0)
#8 boost::asio::async_result<std::decay<std::function<void (boost::system::error_code const&, unsigned long)> const&>::type, void (boost::system::error_code, unsigned long)>::return_type boost::asio::async_write<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >, boost::asio::mutable_buffers_1, std::function<void (boost::system::error_code const&, unsigned long)> const&>(boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >&, boost::asio::mutable_buffers_1 const&, std::function<void (boost::system::error_code const&, unsigned long)> const&, std::enable_if<boost::asio::is_const_buffer_sequence<boost::asio::mutable_buffers_1>::value, void>::type*) /boost-1_67/boost/asio/impl/write.hpp:435 (MyApp+0x000000a692a0)
...
#10 startBufferWrite() /src/startBufferWrite.cpp:163 (MyApp+0x000000a8384c)
...
#48 boost::asio::detail::scheduler::do_wait_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, long, boost::system::error_code const&) /boost-1_67/boost/asio/detail/impl/scheduler.ipp:481 (MyApp+0x000000f33030)
...
SUMMARY: ThreadSanitizer: data race /boost-1_67/boost/asio/detail/deadline_timer_service.hpp:194 in boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::expires_at(boost::asio::detail::deadline_timer_service<boost::asio::time_traits<boost::posix_time::ptime> >::implementation_type&, boost::posix_time::ptime const&, boost::system::error_code&)
==================
For now I'm simply removing all my code that uses asio ssl. I'd like to be able to put it back in but right now I'm getting crashes and hangs when it's in that I'm not getting when it's out.
Fixes I've considered:
- Add a std::mutex member to the
boost::asio::detail::deadline_timer_service<Time_Traits>::implementation_type
class. But that would potentially penalize code that has nothing to do with sockets or ssl, to no particular benefit (that I can see). - Replace the
stream_core
'sdeadline_timer
objects with explicit continuation queues. (would require adding explicit invocation of the continuations, too) - (already rejected, but I did consider:) post the initial write to the socket's
io_context
: this would be broken as soon as I (or another developer) decide to dedicate more than one thread to that context (the "posted" write could be running on one of the context's threads while the previously-received write is running on the other).
Any ideas?
Here's how Tsan is saying it's relevant:
A. in the "previous read" (thread T3) stack trace:
- at trace #5,
ssl::stream<>::async_write_some()
invokesssl::stream::detail::async_io()
- at trace #4,
ssl::stream::detail::async_io()
invokesssl::stream::detail::io_op<...>::operator()
- at trace #3,
ssl::stream::detail::io_op<...>::operator()
invokesssl::stream::detail::stream_core::expiry(boost::asio::deadline_timer const&)
. The stream_core class contains 2 deadline_timer objects. - at trace #2,
ssl::stream::detail::stream_core::expiry()
invokesasio::deadline_timer::expires_at(void)
- at trace #1,
asio::deadline_timer::expires_at(void)
invokesasio::detail::deadline_timer_service<asio::time_traits<boost::posix_time::ptime>>::expires_at()
- at trace #0,
asio::detail::deadline_timer_service<asio::time_traits<boost::posix_time::ptime>>::expires_at()
accessesasio::detail::deadline_timer_service<asio::time_traits<boost::posix_time::ptime>>::implementation_type::expiry
, which is of typeboost::posix_time::ptime
.
B. Meanwhile, in thread T4:
- at trace #13,
asio::io_context::run()
begins to process a continuation. It takes reading all the way to trace #3 to see that it is aboost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::asio::ssl::detail::read_op<boost::asio::mutable_buffers_1>, std::function<void (boost::system::error_code const&, unsigned long)> >::operator()
. What would have posted that io_op to the context? probably a previous invocation ofio_op::operator()
, upon discoveringcore_.pending_write_
's expiry to be unequal tocore_.neg_infin()
. - at trace #2,
ssl::stream::detail::io_op<...>::operator()
invokesasio::deadline_timer::expires_at(boost::posix_time::ptime)
- at trace #1,
asio::deadline_timer::expires_at(boost::posix_time::ptime)
invokesasio::detail::deadline_timer_service<asio::time_traits<ptime> >::expires_at(impl, ptime, error_code)
- at trace #0,
asio::detail::deadline_timer_service<asio::time_traits<ptime> >::expires_at(impl, ptime, error_code)
writesasio::detail::deadline_timer_service<asio::time_traits<boost::posix_time::ptime>>::implementation_type::expiry
, which is of typeboost::posix_time::ptime
.
The deadline_timer
instances are fully managed internally to asio. There is no way for me to ensure that each instance of io_op
is posted on an appropriate strand--asio simply posts them to the deadline_timer. Therefore: there exists no method to ensure that my invocation of asio::async_write(skt, buf, hf)
doesn't read/write a deadline_timer's expiry
while one of the socket's io_context
's threads is also reading/writing it.
Does this make clear the relevance between my invocation of asio::async_write()
on an asio::ssl::stream<>
and an interaction with an asio::deadline_timer
?