diff --git a/Control/CxxUtils/CMakeLists.txt b/Control/CxxUtils/CMakeLists.txt index e27276167d7d8aa1614b0481988ab88880ed6dd6..a6607bec17365ec01e70de1a94d10918aeb9b1f0 100644 --- a/Control/CxxUtils/CMakeLists.txt +++ b/Control/CxxUtils/CMakeLists.txt @@ -107,7 +107,8 @@ foreach( test sincos_test copyif_test ArrayScanner_test Arrayrep_test fpcompare_test StrFormat_test prefetch_test ClassName_test make_unique_test ones_test exctrace1_test bitscan_test ConcurrentRangeMap_test - CachedValue_test CachedPointer_test atomic_fetch_minmax_test + CachedValue_test CachedPointer_test CachedUniquePtr_test + atomic_fetch_minmax_test MurmurHash2_test bitmask_test crc64_test Ring_test ) atlas_add_test( ${test} SOURCES test/${test}.cxx diff --git a/Control/CxxUtils/CxxUtils/CachedUniquePtr.h b/Control/CxxUtils/CxxUtils/CachedUniquePtr.h new file mode 100644 index 0000000000000000000000000000000000000000..0c83de06ac5c01972454cff0aa88d5fc34e2244e --- /dev/null +++ b/Control/CxxUtils/CxxUtils/CachedUniquePtr.h @@ -0,0 +1,122 @@ +// This file's extension implies that it's C, but it's really -*- C++ -*-. +/* + * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file CxxUtils/CachedUniquePtr.h + * @author scott snyder <snyder@bnl.gov> + * @date Mar, 2019 + * @brief Cached unique_ptr with atomic update. + */ + + +#ifndef CXXUTILS_CACHEDUNIQUEPTR_H +#define CXXUTILS_CACHEDUNIQUEPTR_H + + +#include <atomic> +#include <memory> + + +namespace CxxUtils { + + +/** + * @brief Cached pointer with atomic update. + * + * This acts as a @c unique_ptr that may be set atomically through + * a const method. If the pointer is already set, then the new value + * is deleted. The intended use case is where one maintains some + * cached object that is computed lazily. So one can do, for example, + *@code + * CxxUtils::CachedUniquePtr<Payload> m_payload; + * ... + * const Payload* ...::get() const + * { + * if (!m_payload) { + * m_payload.set (std::make_unique<Payload> (...)); + * } + * return m_payload.get()); + * } + @endcode + * + * This only makes sense if the objects passed to set() are equivalent + * in all threads. This is generally the case for a lazily-computed value, + * but this class has no way of enforcing this. + * + * It is recommended to generally use the template @c CachedUniquePtr, which + * is specialized for const types. + * The more general template @c CachedUniquePtrT can be used with a non-const + * type (but you don't want to do that for the lazy-cache use case). + */ +template <class T> +class CachedUniquePtrT +{ +public: + /// Default constructor. Sets the element to null. + CachedUniquePtrT(); + + + /// Constructor from an element. + CachedUniquePtrT (std::unique_ptr<T> elt); + + + /// Move constructor. + CachedUniquePtrT (CachedUniquePtrT&& other); + + + /// Move. + CachedUniquePtrT& operator= (CachedUniquePtrT&& other); + + + // Destructor. + ~CachedUniquePtrT(); + + + /// Atomically set the element. If already set, then @c elt is discarded. + void set (std::unique_ptr<T> elt) const; + + + /// Store a new value to the element. + /// Not compatible with other concurrent access. + void store (std::unique_ptr<T> elt); + + + /// Return the current value of the element. + T* get() const; + + + /// Dereference the element. + T& operator*() const; + + + /// Dereference the element. + T* operator->() const; + + + /// Test if the element is null. + explicit operator bool() const; + + + /// Transfer ownership from the element: return the current value as a + /// unique_ptr, leaving the element null. + std::unique_ptr<T> release(); + + +private: + /// The cached element. + mutable std::atomic<T*> m_ptr; +}; + + +template <class T> +using CachedUniquePtr = CachedUniquePtrT<const T>; + + +} // namespace CxxUtils + + +#include "CxxUtils/CachedUniquePtr.icc" + + +#endif // not CXXUTILS_CACHEDUNIQUEPTR_H diff --git a/Control/CxxUtils/CxxUtils/CachedUniquePtr.icc b/Control/CxxUtils/CxxUtils/CachedUniquePtr.icc new file mode 100644 index 0000000000000000000000000000000000000000..f86b4c6293c802935d8669567d1570e4094c0174 --- /dev/null +++ b/Control/CxxUtils/CxxUtils/CachedUniquePtr.icc @@ -0,0 +1,172 @@ +// This file's extension implies that it's C, but it's really -*- C++ -*-. +/* + * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration. + */ +// $Id$ +/** + * @file CxxUtils/CachedUniquePtr.icc + * @author scott snyder <snyder@bnl.gov> + * @date Mar, 2019 + * @brief Cached unique_ptr with atomic update. + */ + + +namespace CxxUtils { + + +/** + * @brief Default constructor. Sets the element to null. + */ +template <class T> +inline +CachedUniquePtrT<T>::CachedUniquePtrT() + : m_ptr (nullptr) +{ +} + + +/** + * @brief Constructor from an element. + */ +template <class T> +inline +CachedUniquePtrT<T>::CachedUniquePtrT (std::unique_ptr<T> elt) + : m_ptr (elt.release()) +{ +} + + +/** + * @brief Move constructor. + */ +template <class T> +inline +CachedUniquePtrT<T>::CachedUniquePtrT (CachedUniquePtrT&& other) + : m_ptr (other.release().release()) +{ +} + + +/** + * @brief Move. + */ +template <class T> +inline +CachedUniquePtrT<T>& +CachedUniquePtrT<T>::operator= (CachedUniquePtrT&& other) +{ + if (this != &other) { + store (other.release()); + } + return *this; +} + + +/** + * @brief Destructor. + */ +template <class T> +inline +CachedUniquePtrT<T>::~CachedUniquePtrT() +{ + delete m_ptr; +} + + +/** + * @brief Atomically set the element. If already set, then @c elt is discarded. + * @param elt The new value for the element. + * + * If the current value of the element is null, then set it to @c elt. + * Otherwise, delete @c elt. + * This is done atomically. + */ +template <class T> +inline +void CachedUniquePtrT<T>::set (std::unique_ptr<T> elt) const +{ + // Set the element to ELT if it is currently null. + T* ptr = elt.release(); + T* null = nullptr; + if (!m_ptr.compare_exchange_strong (null, ptr)) { + // Was already set. Delete the new value. + delete ptr; + } +} + + +/** + * @brief Store a new value to the element. + * Not compatible with other concurrent access. + */ +template <class T> +inline +void CachedUniquePtrT<T>::store (std::unique_ptr<T> elt) +{ + T* old = m_ptr.exchange (elt.release()); + delete old; +} + + +/** + * @brief Return the current value of the element. + */ +template <class T> +inline +T* +CachedUniquePtrT<T>::get() const +{ + return m_ptr.load(); +} + + +/** + * @brief Dereference the element. + */ +template <class T> +inline +T& +CachedUniquePtrT<T>::operator*() const +{ + return *get(); +} + + +/** + * @brief Dereference the element. + */ +template <class T> +inline +T* +CachedUniquePtrT<T>::operator->() const +{ + return get(); +} + + +/** + * @brief Test if the element is null. + */ +template <class T> +inline +CachedUniquePtrT<T>::operator bool() const +{ + return get() != nullptr; +} + + +/** + * @brief Transfer ownership from the element: return the current value as a + * unique_ptr, leaving the element null. + */ +template <class T> +inline +std::unique_ptr<T> +CachedUniquePtrT<T>::release() +{ + T* old = m_ptr.exchange (nullptr); + return std::unique_ptr<T> (old); +} + + +} // namespace CxxUtils diff --git a/Control/CxxUtils/share/CachedUniquePtr_test.ref b/Control/CxxUtils/share/CachedUniquePtr_test.ref new file mode 100644 index 0000000000000000000000000000000000000000..f0c0fd55135d7b4ad32733a58f0c8e26d11d9799 --- /dev/null +++ b/Control/CxxUtils/share/CachedUniquePtr_test.ref @@ -0,0 +1,3 @@ +CachedUniquePtr_test +test1 +test2 diff --git a/Control/CxxUtils/share/ubsan.supp b/Control/CxxUtils/share/ubsan.supp index 77dc9ed56a865116f1d1452d5a39f82ce5267520..e0ba8742c058c367d432764deccfbb5c5bd8d588 100644 --- a/Control/CxxUtils/share/ubsan.supp +++ b/Control/CxxUtils/share/ubsan.supp @@ -9,3 +9,6 @@ vptr_check:PrepRawDataCollection # Suppress false positives caused by ToolHandle calls from code # generated by cling. vptr_check:ToolHandle + +# Suppress downcast warnings from boost::any. +vptr_check:holder diff --git a/Control/CxxUtils/test/CachedUniquePtr_test.cxx b/Control/CxxUtils/test/CachedUniquePtr_test.cxx new file mode 100644 index 0000000000000000000000000000000000000000..d47894580230d9628af460116af808c9d59510d7 --- /dev/null +++ b/Control/CxxUtils/test/CachedUniquePtr_test.cxx @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration. + */ + +// $Id$ +/** + * @file CxxUtils/test/CachedUniquePtr_test.cxx + * @author scott snyder <snyder@bnl.gov> + * @date mar, 2019 + * @brief Unit tests for CachedUniquePtr + */ + + +#undef NDEBUG +#include "CxxUtils/CachedUniquePtr.h" +#include <atomic> +#include <iostream> +#include <cassert> +#include <thread> +#include <shared_mutex> + + +struct P +{ + P(int x) : m_x(x) { ++s_count; } + ~P() { --s_count; } + + int m_x; + static std::atomic<int> s_count; +}; + +std::atomic<int> P::s_count = 0; + + +// Basic tests. +void test1() +{ + std::cout << "test1\n"; + //int x1 = 0; + //int x2 = 0; + //int x4 = 0; + + CxxUtils::CachedUniquePtr<P> cp1; + assert (!cp1.get()); + assert (!cp1); + assert (P::s_count == 0); + cp1.store (std::make_unique<P>(1)); + assert (P::s_count == 1); + assert (cp1.get()->m_x == 1); + assert (cp1); + assert (cp1->m_x == 1); + assert ((*cp1).m_x == 1); + cp1.store (nullptr); + assert (!cp1.get()); + assert (P::s_count == 0); + + CxxUtils::CachedUniquePtr<P> cp2 (std::make_unique<P>(2)); + assert (cp2->m_x == 2); + assert (P::s_count == 1); + + cp1 = std::move(cp2); + assert (cp1->m_x == 2); + assert (!cp2); + assert (P::s_count == 1); + + { + const CxxUtils::CachedUniquePtr<P> cp3 (std::move (cp1)); + assert (cp3->m_x == 2); + assert (!cp1); + assert (P::s_count == 1); + } + assert (P::s_count == 0); + + cp1.set (std::make_unique<P>(3)); + assert (cp1->m_x == 3); + assert (P::s_count == 1); + + cp1.set (std::make_unique<P>(4)); + assert (cp1->m_x == 3); + assert (P::s_count == 1); + + std::unique_ptr<const P> up = cp1.release(); + assert (!cp1); + assert (up->m_x == 3); + assert (P::s_count == 1); +} + + +class ThreadingTest +{ +public: + static const int NVAL = 10; + CxxUtils::CachedUniquePtr<P> m_vals[10]; + std::shared_timed_mutex m_sm; + int m_x[NVAL] = {0}; + + void threadedTest(); + + struct writerThread + { + writerThread (ThreadingTest& test, int iworker) + : m_test (test), m_iworker (iworker) {} + void operator()(); + ThreadingTest& m_test; + int m_iworker; + }; + + + struct readerThread + { + readerThread (ThreadingTest& test, int iworker) + : m_test (test), m_iworker (iworker) {} + void operator()(); + ThreadingTest& m_test; + int m_iworker; + }; +}; + + +void ThreadingTest::writerThread::operator()() +{ + int i = m_iworker; + do { + m_test.m_vals[i].set (std::make_unique<const P>(i)); + i++; + if (i >= NVAL) i = 0; + } while (i != m_iworker); +} + + +void ThreadingTest::readerThread::operator()() +{ + bool checked[NVAL] = {false}; + int nchecked = 0; + while (nchecked < NVAL) { + int i = m_iworker; + do { + if (!checked[i] && m_test.m_vals[i]) { + assert (m_test.m_vals[i]->m_x == i); + checked[i] = true; + ++nchecked; + } + i++; + if (i >= NVAL) i = 0; + } while (i != m_iworker); + } +} + + +void ThreadingTest::threadedTest() +{ + for (int i=0; i < NVAL; i++) { + m_vals[i].store (nullptr); + } + + const int nthread = 10; + std::thread threads[nthread]; + m_sm.lock(); + + for (int i=0; i < nthread; i++) { + if (i < 3) { + threads[i] = std::thread (writerThread (*this, i)); + } + else { + threads[i] = std::thread (readerThread (*this, i)); + } + } + + // Try to get the threads starting as much at the same time as possible. + m_sm.unlock(); + for (int i=0; i < nthread; i++) + threads[i].join(); +} + + +// Threading test. +void test2() +{ + std::cout << "test2\n"; + + ThreadingTest test; +#if 0 + for (int i=0; i < 20; i++) + test.threadedTest(); +#endif +} + + +int main() +{ + std::cout << "CachedUniquePtr_test\n"; + test1(); + test2(); + return 0; +} diff --git a/Control/CxxUtils/test/ConcurrentBitset_test.cxx b/Control/CxxUtils/test/ConcurrentBitset_test.cxx index 3b5e985a608f6be69c7eb8fd297bec5f273fe312..8590fc000c5bc31073c7bab2d378de921e804328 100644 --- a/Control/CxxUtils/test/ConcurrentBitset_test.cxx +++ b/Control/CxxUtils/test/ConcurrentBitset_test.cxx @@ -17,14 +17,14 @@ #include "CxxUtils/ConcurrentBitset.h" #include "CxxUtils/checker_macros.h" #include "TestTools/random.h" -// Work around a warning in tbb, found by gcc8. -// Fixed in TBB 2018 U5. -#if defined(__GNUC__) && __GNUC__ >= 8 +// tbb/machine/gcc_generic.h has spurious trailing semicolons after +// the clz() functiosn (as of TBB 2019 U1). +#if defined(__GNUC__) # pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wclass-memaccess" +# pragma GCC diagnostic ignored "-Wpedantic" #endif #include "tbb/concurrent_unordered_set.h" -#if defined(__GNUC__) && __GNUC__ >= 8 +#if defined(__GNUC__) # pragma GCC diagnostic pop #endif #include "boost/timer/timer.hpp"