/* * Test program that illustrates how to annotate a smart pointer * implementation. In a multithreaded program the following is relevant when * working with smart pointers: * - whether or not the objects pointed at are shared over threads. * - whether or not the methods of the objects pointed at are thread-safe. * - whether or not the smart pointer objects are shared over threads. * - whether or not the smart pointer object itself is thread-safe. * * Most smart pointer implemenations are not thread-safe * (e.g. boost::shared_ptr<>, tr1::shared_ptr<> and the smart_ptr<> * implementation below). This means that it is not safe to modify a shared * pointer object that is shared over threads without proper synchronization. * * Even for non-thread-safe smart pointers it is possible to have different * threads access the same object via smart pointers without triggering data * races on the smart pointer objects. * * A smart pointer implementation guarantees that the destructor of the object * pointed at is invoked after the last smart pointer that points to that * object has been destroyed or reset. Data race detection tools cannot detect * this ordering without explicit annotation for smart pointers that track * references without invoking synchronization operations recognized by data * race detection tools. */ #include <cassert> // assert() #include <climits> // PTHREAD_STACK_MIN #include <iostream> // std::cerr #include <stdlib.h> // atoi() #include <vector> #ifdef _WIN32 #include <process.h> // _beginthreadex() #include <windows.h> // CRITICAL_SECTION #else #include <pthread.h> // pthread_mutex_t #endif #include "unified_annotations.h" static bool s_enable_annotations; #ifdef _WIN32 class AtomicInt32 { public: AtomicInt32(const int value = 0) : m_value(value) { } ~AtomicInt32() { } LONG operator++() { return InterlockedIncrement(&m_value); } LONG operator--() { return InterlockedDecrement(&m_value); } private: volatile LONG m_value; }; class Mutex { public: Mutex() : m_mutex() { InitializeCriticalSection(&m_mutex); } ~Mutex() { DeleteCriticalSection(&m_mutex); } void Lock() { EnterCriticalSection(&m_mutex); } void Unlock() { LeaveCriticalSection(&m_mutex); } private: CRITICAL_SECTION m_mutex; }; class Thread { public: Thread() : m_thread(INVALID_HANDLE_VALUE) { } ~Thread() { } void Create(void* (*pf)(void*), void* arg) { WrapperArgs* wrapper_arg_p = new WrapperArgs(pf, arg); m_thread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, wrapper, wrapper_arg_p, 0, NULL)); } void Join() { WaitForSingleObject(m_thread, INFINITE); } private: struct WrapperArgs { WrapperArgs(void* (*pf)(void*), void* arg) : m_pf(pf), m_arg(arg) { } void* (*m_pf)(void*); void* m_arg; }; static unsigned int __stdcall wrapper(void* arg) { WrapperArgs* wrapper_arg_p = reinterpret_cast<WrapperArgs*>(arg); WrapperArgs wa = *wrapper_arg_p; delete wrapper_arg_p; return reinterpret_cast<unsigned>((wa.m_pf)(wa.m_arg)); } HANDLE m_thread; }; #else // _WIN32 class AtomicInt32 { public: AtomicInt32(const int value = 0) : m_value(value) { } ~AtomicInt32() { } int operator++() { return __sync_add_and_fetch(&m_value, 1); } int operator--() { return __sync_sub_and_fetch(&m_value, 1); } private: volatile int m_value; }; class Mutex { public: Mutex() : m_mutex() { pthread_mutex_init(&m_mutex, NULL); } ~Mutex() { pthread_mutex_destroy(&m_mutex); } void Lock() { pthread_mutex_lock(&m_mutex); } void Unlock() { pthread_mutex_unlock(&m_mutex); } private: pthread_mutex_t m_mutex; }; class Thread { public: Thread() : m_tid() { } ~Thread() { } void Create(void* (*pf)(void*), void* arg) { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + 4096); pthread_create(&m_tid, &attr, pf, arg); pthread_attr_destroy(&attr); } void Join() { pthread_join(m_tid, NULL); } private: pthread_t m_tid; }; #endif // !defined(_WIN32) template<class T> class smart_ptr { public: typedef AtomicInt32 counter_t; template <typename Q> friend class smart_ptr; explicit smart_ptr() : m_ptr(NULL), m_count_ptr(NULL) { } explicit smart_ptr(T* const pT) : m_ptr(NULL), m_count_ptr(NULL) { set(pT, pT ? new counter_t(0) : NULL); } template <typename Q> explicit smart_ptr(Q* const q) : m_ptr(NULL), m_count_ptr(NULL) { set(q, q ? new counter_t(0) : NULL); } ~smart_ptr() { set(NULL, NULL); } smart_ptr(const smart_ptr<T>& sp) : m_ptr(NULL), m_count_ptr(NULL) { set(sp.m_ptr, sp.m_count_ptr); } template <typename Q> smart_ptr(const smart_ptr<Q>& sp) : m_ptr(NULL), m_count_ptr(NULL) { set(sp.m_ptr, sp.m_count_ptr); } smart_ptr& operator=(const smart_ptr<T>& sp) { set(sp.m_ptr, sp.m_count_ptr); return *this; } smart_ptr& operator=(T* const p) { set(p, p ? new counter_t(0) : NULL); return *this; } template <typename Q> smart_ptr& operator=(Q* const q) { set(q, q ? new counter_t(0) : NULL); return *this; } T* operator->() const { assert(m_ptr); return m_ptr; } T& operator*() const { assert(m_ptr); return *m_ptr; } private: void set(T* const pT, counter_t* const count_ptr) { if (m_ptr != pT) { if (m_count_ptr) { if (s_enable_annotations) U_ANNOTATE_HAPPENS_BEFORE(m_count_ptr); if (--(*m_count_ptr) == 0) { if (s_enable_annotations) U_ANNOTATE_HAPPENS_AFTER(m_count_ptr); delete m_ptr; m_ptr = NULL; delete m_count_ptr; m_count_ptr = NULL; } } m_ptr = pT; m_count_ptr = count_ptr; if (count_ptr) ++(*m_count_ptr); } } T* m_ptr; counter_t* m_count_ptr; }; class counter { public: counter() : m_mutex(), m_count() { } ~counter() { // Data race detection tools that do not recognize the // ANNOTATE_HAPPENS_BEFORE() / ANNOTATE_HAPPENS_AFTER() annotations in the // smart_ptr<> implementation will report that the assignment below // triggers a data race. m_count = -1; } int get() const { int result; m_mutex.Lock(); result = m_count; m_mutex.Unlock(); return result; } int post_increment() { int result; m_mutex.Lock(); result = m_count++; m_mutex.Unlock(); return result; } private: mutable Mutex m_mutex; int m_count; }; static void* thread_func(void* arg) { smart_ptr<counter>* pp = reinterpret_cast<smart_ptr<counter>*>(arg); (*pp)->post_increment(); *pp = NULL; delete pp; return NULL; } int main(int argc, char** argv) { const int nthreads = std::max(argc > 1 ? atoi(argv[1]) : 1, 1); const int iterations = std::max(argc > 2 ? atoi(argv[2]) : 1, 1); s_enable_annotations = argc > 3 ? !!atoi(argv[3]) : true; for (int j = 0; j < iterations; ++j) { std::vector<Thread> T(nthreads); smart_ptr<counter> p(new counter); p->post_increment(); for (std::vector<Thread>::iterator q = T.begin(); q != T.end(); q++) q->Create(thread_func, new smart_ptr<counter>(p)); { // Avoid that counter.m_mutex introduces a false ordering on the // counter.m_count accesses. const timespec delay = { 0, 100 * 1000 * 1000 }; nanosleep(&delay, 0); } p = NULL; for (std::vector<Thread>::iterator q = T.begin(); q != T.end(); q++) q->Join(); } std::cerr << "Done.\n"; return 0; }