Skip to content

Data race on _pplx_g_sched_t::get_scheduler() #1085

Closed
@whoan

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

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions