Search code examples
c++multithreadingboostboost-intrusive

TSAN thread race error detected with Boost intrusive_ptr use


Below code generates a TSAN error(race condition). Is this a valid error ? or a false positive ?
Object is destroyed only after ref count becomes zero(after all other thread memory operations are visible - with atomic_thread_fence)
If I use a std::shared_ptr instead of boost::intrusive_ptr, then TSAN error disappears.
Since both threads use the object as read-only, I presume this should be safe.
If this is a valid error how do I fix it ?
gcc version - 7.3.1 boost version - 1.72.0

compile command : "g++ -ggdb -I /usr/local/boost_1_72_0 -O3 -fsanitize=thread TSan_Intr_Ptr.cpp -lpthread "

#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <boost/smart_ptr/detail/spinlock.hpp>
#include <boost/atomic.hpp>
#include <thread>
#include <iostream>
#include <vector>
#include <atomic>
#include <unistd.h>
using namespace std;

struct Shared
{
        mutable boost::atomic<int> refcount_;

        //From https://www.boost.org/doc/libs/1_72_0/doc/html/atomic/usage_examples.html
        friend void intrusive_ptr_add_ref(const Shared * x)
        {
                x->refcount_.fetch_add(1, boost::memory_order_relaxed);
        }

        friend void intrusive_ptr_release(const Shared* x)
        {
                if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1)
                {
                        boost::atomic_thread_fence(boost::memory_order_acquire);
                        delete x;
                }
        }
};

vector<boost::intrusive_ptr<Shared const>>  g_vec;
boost::detail::spinlock g_lock = BOOST_DETAIL_SPINLOCK_INIT;

void consumer()
{
        while(true)
        {
                g_lock.lock();
                g_vec.clear();
                g_lock.unlock();
                usleep(10);
        }

}

int main()
{
        thread thd(consumer);

        while(true)
        {
                boost::intrusive_ptr<Shared const> p(new Shared);
                g_lock.lock();
                g_vec.push_back(p);
                g_lock.unlock();
                usleep(1);
        }
        return 0;
}

TSAN Error

WARNING: ThreadSanitizer: data race (pid=14513)
  Write of size 8 at 0x7b0400000010 by main thread:
    #0 operator delete(void*) <null> (libtsan.so.0+0x00000006fae4)
    #1 intrusive_ptr_release(Shared const*) /Test/TSan_Intr_Ptr_Min.cpp:25 (a.out+0x000000401195)
    #2 boost::intrusive_ptr<Shared const>::~intrusive_ptr() /boost_1_72_0/boost/smart_ptr/intrusive_ptr.hpp:98 (a.out+0x000000401195)
    #3 main /x01/exch/Test/TSan_Intr_Ptr_Min.cpp:51 (a.out+0x000000401195)

Previous atomic write of size 4 at 0x7b0400000010 by thread T1:
    #0 __tsan_atomic32_fetch_sub <null> (libtsan.so.0+0x00000006576f)
    #1 boost::atomics::detail::gcc_atomic_operations<4ul, true>::fetch_sub(unsigned int volatile&, unsigned int, boost::memory_order) /boost_1_72_0/boost/atomic/detail/ops_gcc_atomic.hpp:116 (a.out+0x000000401481)
    #2 boost::atomics::detail::base_atomic<int, int>::fetch_sub(int, boost::memory_order) volatile /usr/local/boost_1_72_0/boost/atomic/detail/atomic_template.hpp:348 (a.out+0x000000401481)
    #3 intrusive_ptr_release(Shared const*) /Test/TSan_Intr_Ptr_Min.cpp:22 (a.out+0x000000401481)
...

Solution

  • it seems using memory_order_acq_rel resolves the issue. (May be https://www.boost.org/doc/libs/1_72_0/doc/html/atomic/usage_examples.html example is in-correct)

    
            friend void intrusive_ptr_add_ref(const Shared * x)
            {
                    x->refcount_.fetch_add(1, boost::memory_order_acq_rel);
            }
    
            friend void intrusive_ptr_release(const Shared* x)
            {
                    if (x->refcount_.fetch_sub(1, boost::memory_order_acq_rel) == 1)
                    {
                            delete x;
                    }
            }