Description
Google's thread sanitizer is reporting a data race on _pplx_g_sched_t::get_scheduler()
function which I discovered creating multiple websocket instances at the same time on Linux.
You can run this simpler snippet to reproduce:
#include <thread>
#include <cpprest/ws_client.h>
int main() {
//pplx::get_ambient_scheduler(); // uncomment this line to fix the data race
std::thread a([] () { pplx::get_ambient_scheduler(); });
std::thread b([] () { pplx::get_ambient_scheduler(); });
a.join();
b.join();
return 0;
}
This is sanitizer's output:
==================
WARNING: ThreadSanitizer: data race (pid=4653)
Read of size 8 at 0x7f96cf428c50 by thread T2:
#0 std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>::operator bool() const /usr/include/c++/8.2.1/bits/shared_ptr_base.h:1291 (libcpprest.so.2.10+0xa787a2)
#1 pplx::_pplx_g_sched_t::get_scheduler() /home/bambino/projects/tradehelm/libfix-adapter/vendor/cpprest/Release/src/pplx/pplx.cpp:77 (libcpprest.so.2.10+0xa784e4)
#2 pplx::get_ambient_scheduler() /home/bambino/projects/tradehelm/libfix-adapter/vendor/cpprest/Release/src/pplx/pplx.cpp:125 (libcpprest.so.2.10+0xa77e36)
#3 operator() /data/projects/pruebas/c++/pplx-ambient-scheduler.cpp:7 (a.out+0x33a9)
#4 __invoke_impl<void, main()::<lambda()> > /usr/include/c++/8.2.1/bits/invoke.h:60 (a.out+0x3b4e)
#5 __invoke<main()::<lambda()> > /usr/include/c++/8.2.1/bits/invoke.h:95 (a.out+0x366b)
#6 _M_invoke<0> /usr/include/c++/8.2.1/thread:244 (a.out+0x4198)
#7 operator() /usr/include/c++/8.2.1/thread:253 (a.out+0x40c3)
#8 _M_run /usr/include/c++/8.2.1/thread:196 (a.out+0x4024)
#9 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80 (libstdc++.so.6+0xbc062)
Previous write of size 8 at 0x7f96cf428c50 by thread T1:
#0 std::enable_if<std::__and_<std::__not_<std::__is_tuple_like<pplx::scheduler_interface*> >, std::is_move_constructible<pplx::scheduler_interface*>, std::is_move_assignable<pplx::scheduler_interface*> >::value, void>::type std::swap<pplx::scheduler_interface*>(pplx::scheduler_interface*&, pplx::scheduler_interface*&) /usr/include/c++/8.2.1/bits/move.h:195 (libcpprest.so.2.10+0xa78f48)
#1 std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>::swap(std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>&) /usr/include/c++/8.2.1/bits/shared_ptr_base.h:1304 (libcpprest.so.2.10+0xa78da8)
#2 std::enable_if<std::__sp_compatible_with<pplx::details::linux_scheduler*, pplx::scheduler_interface*>::value, std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>&>::type std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>::operator=<pplx::details::linux_scheduler>(std::__shared_ptr<pplx::details::linux_scheduler, (__gnu_cxx::_Lock_policy)2>&&) /usr/include/c++/8.2.1/bits/shared_ptr_base.h:1251 (libcpprest.so.2.10+0xa78bac)
#3 std::enable_if<std::is_assignable<std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>&, std::shared_ptr<pplx::details::linux_scheduler> >::value, std::shared_ptr<pplx::scheduler_interface>&>::type std::shared_ptr<pplx::scheduler_interface>::operator=<pplx::details::linux_scheduler>(std::shared_ptr<pplx::details::linux_scheduler>&&) /usr/include/c++/8.2.1/bits/shared_ptr.h:343 (libcpprest.so.2.10+0xa78947)
#4 pplx::_pplx_g_sched_t::get_scheduler() /home/bambino/projects/tradehelm/libfix-adapter/vendor/cpprest/Release/src/pplx/pplx.cpp:82 (libcpprest.so.2.10+0xa7853c)
#5 pplx::get_ambient_scheduler() /home/bambino/projects/tradehelm/libfix-adapter/vendor/cpprest/Release/src/pplx/pplx.cpp:125 (libcpprest.so.2.10+0xa77e36)
#6 operator() /data/projects/pruebas/c++/pplx-ambient-scheduler.cpp:6 (a.out+0x3333)
#7 __invoke_impl<void, main()::<lambda()> > /usr/include/c++/8.2.1/bits/invoke.h:60 (a.out+0x3910)
#8 __invoke<main()::<lambda()> > /usr/include/c++/8.2.1/bits/invoke.h:95 (a.out+0x350d)
#9 _M_invoke<0> /usr/include/c++/8.2.1/thread:244 (a.out+0x41f0)
#10 operator() /usr/include/c++/8.2.1/thread:253 (a.out+0x412d)
#11 _M_run /usr/include/c++/8.2.1/thread:196 (a.out+0x406e)
#12 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80 (libstdc++.so.6+0xbc062)
Location is global 'pplx::_pplx_g_sched' of size 32 at 0x7f96cf428c40 (libcpprest.so.2.10+0x000000d58c50)
Thread T2 (tid=4656, running) created by main thread at:
#0 pthread_create /build/gcc/src/gcc/libsanitizer/tsan/tsan_interceptors.cc:915 (libtsan.so.0+0x2bf03)
#1 __gthread_create /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:662 (libstdc++.so.6+0xbc359)
#2 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:135 (libstdc++.so.6+0xbc359)
#3 main /data/projects/pruebas/c++/pplx-ambient-scheduler.cpp:7 (a.out+0x3435)
Thread T1 (tid=4655, finished) created by main thread at:
#0 pthread_create /build/gcc/src/gcc/libsanitizer/tsan/tsan_interceptors.cc:915 (libtsan.so.0+0x2bf03)
#1 __gthread_create /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:662 (libstdc++.so.6+0xbc359)
#2 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:135 (libstdc++.so.6+0xbc359)
#3 main /data/projects/pruebas/c++/pplx-ambient-scheduler.cpp:6 (a.out+0x3422)
SUMMARY: ThreadSanitizer: data race /usr/include/c++/8.2.1/bits/shared_ptr_base.h:1291 in std::__shared_ptr<pplx::scheduler_interface, (__gnu_cxx::_Lock_policy)2>::operator bool() const
==================
However, I don't see the actual data race on said function:
sched_ptr get_scheduler()
{
switch (m_state)
{
case post_ctor:
// This is the 99.9% case.
if (!m_scheduler)
{
::pplx::details::_Scoped_spin_lock lock(m_spinlock);
if (!m_scheduler)
{
m_scheduler = std::make_shared<::pplx::default_scheduler_t>();
}
}
return m_scheduler;
default:
// This case means the global m_scheduler is not available.
// We spin off an individual scheduler instead.
return std::make_shared<::pplx::default_scheduler_t>();
}
}
It's easy to avoid the WARNING reported by the sanitizer, making a call to pplx::get_ambient_scheduler()
beforehand. In my application, I use a global variable which makes a call to the function and I wonder if something similar should be applied to cpprestsdk to ease the task to other developers seeing the same data-race.
An alternative might be to call get_scheduler
in _pplx_g_sched_t
's constructor. Something like this:
_pplx_g_sched_t() { m_state = post_ctor; get_scheduler(); }
Thoughts? Ping @BillyONeal