diff --git a/app/gcompose/src/main.cpp b/app/gcompose/src/main.cpp index f59b387..81e5eed 100644 --- a/app/gcompose/src/main.cpp +++ b/app/gcompose/src/main.cpp @@ -9,7 +9,7 @@ #include "HEP/Detectors/DetectorChamber.h" #include "Vtk/HEP/Detectors/vtkDetectorChamber.h" -#include +#include #include #include "Core/ObjectsContext.h" diff --git a/src/Core/CMakeLists.txt b/src/Core/CMakeLists.txt index 5e58787..fcc6d17 100644 --- a/src/Core/CMakeLists.txt +++ b/src/Core/CMakeLists.txt @@ -19,6 +19,8 @@ set(HEADERS SmartPointer.h StaticInterface.h StringReader.h + Threads.h + Monitor.h Types.h Uuid.h Vector.h @@ -34,6 +36,7 @@ set(SOURCES Serializable.cpp Signal.cpp Uuid.cpp + Threads.cpp ) set(LIBRARIES Boost::program_options Boost::serialization) diff --git a/src/Core/Monitor.h b/src/Core/Monitor.h new file mode 100644 index 0000000..52e6b27 --- /dev/null +++ b/src/Core/Monitor.h @@ -0,0 +1,175 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova + All rights reserved + + Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > + + ------------------------------------------------------------------ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3.0 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. + +//////////////////////////////////////////////////////////////////////////////*/ + +#ifndef U_CORE_MONITOR_H +#define U_CORE_MONITOR_H + +#include +#include +#include +#include + +namespace uLib { + +/** + * @brief Mutex class wraps std::timed_mutex and is used for thread synchronization. + */ +class Mutex { +public: + Mutex() = default; + ~Mutex() = default; + + /** @brief Locks the mutex, blocking if necessary. */ + void Lock() { m_Mutex.lock(); } + + /** @brief Unlocks the mutex. */ + void Unlock() { m_Mutex.unlock(); } + + /** @brief Tries to lock the mutex without blocking. */ + bool TryLock() { return m_Mutex.try_lock(); } + + /** @brief Tries to lock the mutex within a timeout in milliseconds. */ + bool TryLockFor(int timeout_ms) { + if (timeout_ms < 0) { Lock(); return true; } + return m_Mutex.try_lock_for(std::chrono::milliseconds(timeout_ms)); + } + + /** @brief RAII helper for scoped locking. */ + class ScopedLock { + public: + ScopedLock(Mutex &mutex) : m_Mutex(mutex) { m_Mutex.Lock(); } + ~ScopedLock() { m_Mutex.Unlock(); } + private: + Mutex &m_Mutex; + // Non-copyable + ScopedLock(const ScopedLock&) = delete; + ScopedLock& operator=(const ScopedLock&) = delete; + }; + + /** @brief Returns the underlying std::timed_mutex. */ + std::timed_mutex& GetNative() { return m_Mutex; } + +private: + std::timed_mutex m_Mutex; + // Non-copyable + Mutex(const Mutex &) = delete; + Mutex &operator=(const Mutex &) = delete; +}; + +namespace detail { + +/** @brief Internal implementation for the ULIB_MUTEX_LOCK macros. */ +class ScopedTimedLock { +public: + ScopedTimedLock(Mutex& mutex, int timeout_ms) + : m_RawMutex(nullptr), m_MutexWrapper(&mutex), m_Locked(false) { + m_Locked = m_MutexWrapper->TryLockFor(timeout_ms); + } + + ScopedTimedLock(std::timed_mutex& mutex, int timeout_ms) + : m_RawMutex(&mutex), m_MutexWrapper(nullptr), m_Locked(false) { + if (timeout_ms < 0) { m_RawMutex->lock(); m_Locked = true; } + else m_Locked = m_RawMutex->try_lock_for(std::chrono::milliseconds(timeout_ms)); + } + + ~ScopedTimedLock() { + if (m_Locked) { + if (m_RawMutex) m_RawMutex->unlock(); + else if (m_MutexWrapper) m_MutexWrapper->Unlock(); + } + } + + operator bool() const { return m_Locked; } + void unlock() { if (m_Locked) { + if (m_RawMutex) m_RawMutex->unlock(); + else if (m_MutexWrapper) m_MutexWrapper->Unlock(); + m_Locked = false; + } } + +private: + std::timed_mutex* m_RawMutex = nullptr; + Mutex* m_MutexWrapper = nullptr; + bool m_Locked; + + // Non-copyable/movable to be safe in the 'for' loop + ScopedTimedLock(const ScopedTimedLock&) = delete; + ScopedTimedLock& operator=(const ScopedTimedLock&) = delete; + ScopedTimedLock(ScopedTimedLock&&) = default; +}; + +inline ScopedTimedLock makeScopedMutexLock(Mutex& mutex, int timeout_ms) { + return ScopedTimedLock(mutex, timeout_ms); +} + +inline ScopedTimedLock makeScopedMutexLock(std::timed_mutex& mutex, int timeout_ms) { + return ScopedTimedLock(mutex, timeout_ms); +} + +} // namespace detail + +/** + * @brief Macro for block-scoped locking of a static mutex. + * @param timeout Timeout in ms (-1 for infinite). + */ +#define ULIB_STATIC_LOCK(timeout) \ + static std::timed_mutex __ulib_static_mutex; \ + for (auto __ulib_lock = uLib::detail::makeScopedMutexLock(__ulib_static_mutex, timeout); \ + __ulib_lock; \ + __ulib_lock.unlock()) + +/** + * @brief Macro for block-scoped locking of a provided mutex. + * @param mutex The uLib::Mutex or std::timed_mutex to lock. + * @param timeout Timeout in ms (-1 for infinite). + */ +#define ULIB_MUTEX_LOCK(mutex, timeout) \ + for (auto __ulib_lock = uLib::detail::makeScopedMutexLock(mutex, timeout); \ + __ulib_lock; \ + __ulib_lock.unlock()) + +/** + * @brief Monitor class provides a base for objects that need thread-safe access. + */ +template +class Monitor { +protected: + T* m_Resource; + Mutex m_Mutex; + +public: + Monitor(T* resource) : m_Resource(resource) {} + virtual ~Monitor() { delete m_Resource; } + + /** @brief Thread-safe access to the resource through a lambda. */ + template + auto Access(F f) -> decltype(f(*m_Resource)) { + Mutex::ScopedLock lock(m_Mutex); + return f(*m_Resource); + } +}; + +} // namespace uLib + +#endif // U_CORE_MONITOR_H diff --git a/src/Core/Object.cpp b/src/Core/Object.cpp index 2b1ff91..e3e1ef1 100644 --- a/src/Core/Object.cpp +++ b/src/Core/Object.cpp @@ -65,6 +65,7 @@ public: Vector slov; std::vector m_Properties; std::vector m_DynamicProperties; + bool m_SignalsBlocked; }; // Implementations of Property methods @@ -114,9 +115,14 @@ template void Object::serialize(Archive::log_archive &, const unsigned int); //////////////////////////////////////////////////////////////////////////////// // OBJECT IMPLEMENTATION -Object::Object() : d(new ObjectPrivate) {} +Object::Object() : d(new ObjectPrivate) { + d->m_SignalsBlocked = false; +} Object::Object(const Object ©) : d(new ObjectPrivate) { - if (copy.d) d->m_InstanceName = copy.d->m_InstanceName; + if (copy.d) { + d->m_InstanceName = copy.d->m_InstanceName; + d->m_SignalsBlocked = copy.d->m_SignalsBlocked; + } } Object::~Object() { @@ -209,6 +215,16 @@ void Object::SetInstanceName(const std::string& name) { d->m_InstanceName = name; this->Updated(); } + +bool Object::blockSignals(bool block) { + bool old = d->m_SignalsBlocked; + d->m_SignalsBlocked = block; + return old; +} + +bool Object::signalsBlocked() const { + return d->m_SignalsBlocked; +} // std::ostream & // operator << (std::ostream &os, uLib::Object &ob) diff --git a/src/Core/Object.h b/src/Core/Object.h index 4b784c2..46a62cf 100644 --- a/src/Core/Object.h +++ b/src/Core/Object.h @@ -81,6 +81,12 @@ public: const std::string& GetInstanceName() const; void SetInstanceName(const std::string& name); + + /** @brief Temporarily blocks all signal emissions from this object. Returns previous state. */ + bool blockSignals(bool block); + + /** @brief Checks if signals are currently blocked. */ + bool signalsBlocked() const; //////////////////////////////////////////////////////////////////////////// // PROPERTIES // @@ -138,24 +144,22 @@ public: // Qt5 style connector // template - static bool + static Connection connect(typename FunctionPointer::Object *sender, Func1 sigf, typename FunctionPointer::Object *receiver, Func2 slof) { SignalBase *sigb = sender->findOrAddSignal(sigf); - ConnectSignal::SignalSignature>(sigb, slof, + return ConnectSignal::SignalSignature>(sigb, slof, receiver); - return true; } // Lambda/Function object connector // template - static bool connect(typename FunctionPointer::Object *sender, + static Connection connect(typename FunctionPointer::Object *sender, Func1 sigf, SlotT slof) { SignalBase *sigb = sender->findOrAddSignal(sigf); typedef typename FunctionPointer::SignalSignature SigSignature; typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect(slof); - return true; + return reinterpret_cast(sigb)->connect(slof); } template @@ -167,10 +171,9 @@ public: } template - static inline bool connect(SignalBase *sigb, FuncT slof, Object *receiver) { - ConnectSignal::SignalSignature>(sigb, slof, + static inline Connection connect(SignalBase *sigb, FuncT slof, Object *receiver) { + return ConnectSignal::SignalSignature>(sigb, slof, receiver); - return true; } template diff --git a/src/Core/Signal.h b/src/Core/Signal.h index 42ef621..f8307e4 100644 --- a/src/Core/Signal.h +++ b/src/Core/Signal.h @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include "Function.h" #include @@ -63,9 +65,11 @@ using namespace boost::placeholders; #define _ULIB_DETAIL_SIGNAL_EMIT(_name, ...) \ do { \ - BOOST_AUTO(sig, this->findOrAddSignal(&_name)); \ - if (sig) \ - sig->operator()(__VA_ARGS__); \ + if (!this->signalsBlocked()) { \ + BOOST_AUTO(sig, this->findOrAddSignal(&_name)); \ + if (sig) \ + sig->operator()(__VA_ARGS__); \ + } \ } while (0) /** @@ -90,6 +94,7 @@ namespace uLib { // TODO ... typedef boost::signals2::signal_base SignalBase; +typedef boost::signals2::connection Connection; template struct Signal { typedef boost::signals2::signal type; @@ -104,57 +109,57 @@ struct ConnectSignal {}; template struct ConnectSignal { - static void connect(SignalBase *sigb, FuncT slof, + static Connection connect(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect(slof); + return reinterpret_cast(sigb)->connect(slof); } }; template struct ConnectSignal { - static void connect(SignalBase *sigb, FuncT slof, + static Connection connect(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect(boost::bind(slof, receiver)); + return reinterpret_cast(sigb)->connect(boost::bind(slof, receiver)); } }; template struct ConnectSignal { - static void connect(SignalBase *sigb, FuncT slof, + static Connection connect(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect(boost::bind(slof, receiver, _1)); + return reinterpret_cast(sigb)->connect(boost::bind(slof, receiver, _1)); } }; template struct ConnectSignal { - static void connect(SignalBase *sigb, FuncT slof, + static Connection connect(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect( + return reinterpret_cast(sigb)->connect( boost::bind(slof, receiver, _1, _2)); } }; template struct ConnectSignal { - static void connect(SignalBase *sigb, FuncT slof, + static Connection connect(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect( + return reinterpret_cast(sigb)->connect( boost::bind(slof, receiver, _1, _2, _3)); } }; template struct ConnectSignal { - static void connect(SignalBase *sigb, FuncT slof, + static Connection connect(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { typedef typename Signal::type SigT; - reinterpret_cast(sigb)->connect( + return reinterpret_cast(sigb)->connect( boost::bind(slof, receiver, _1, _2, _3, _4)); } }; @@ -167,11 +172,11 @@ template SignalBase *NewSignal(FuncT f) { } template -void ConnectSignal(SignalBase *sigb, FuncT slof, +Connection ConnectSignal(SignalBase *sigb, FuncT slof, typename FunctionPointer::Object *receiver) { - detail::ConnectSignal::arity>::connect(sigb, slof, - receiver); + return detail::ConnectSignal::arity>::connect(sigb, slof, + receiver); } } // namespace uLib diff --git a/src/Core/Threads.cpp b/src/Core/Threads.cpp new file mode 100644 index 0000000..ff44879 --- /dev/null +++ b/src/Core/Threads.cpp @@ -0,0 +1,84 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova + All rights reserved + + Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > + + ------------------------------------------------------------------ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3.0 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. + +//////////////////////////////////////////////////////////////////////////////*/ + +#include "Threads.h" +#include + +namespace uLib { + +Thread::Thread() : m_Running(false) {} + +Thread::~Thread() { + if (m_Thread.joinable()) { + m_Thread.detach(); + } +} + +void Thread::Start() { + Mutex::ScopedLock lock(m_ThreadMutex); + if (m_Running) return; + + m_Running = true; + m_Thread = std::thread(&Thread::ThreadEntryPoint, this); +} + +void Thread::Join() { + if (m_Thread.joinable()) { + m_Thread.join(); + } +} + +void Thread::Detach() { + if (m_Thread.joinable()) { + m_Thread.detach(); + } +} + +bool Thread::IsJoinable() const { + return m_Thread.joinable(); +} + +bool Thread::IsRunning() const { + return m_Running; +} + +void Thread::Run() { + // Override in subclasses +} + +void Thread::ThreadEntryPoint() { + this->Run(); + m_Running = false; +} + +void Thread::Sleep(int milliseconds) { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +} + +void Thread::Yield() { + std::this_thread::yield(); +} + +} // namespace uLib diff --git a/src/Core/Threads.h b/src/Core/Threads.h new file mode 100644 index 0000000..75a9d8d --- /dev/null +++ b/src/Core/Threads.h @@ -0,0 +1,80 @@ +/*////////////////////////////////////////////////////////////////////////////// +// CMT Cosmic Muon Tomography project ////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova + All rights reserved + + Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it > + + ------------------------------------------------------------------ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3.0 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. + +//////////////////////////////////////////////////////////////////////////////*/ + +#ifndef U_CORE_THREADS_H +#define U_CORE_THREADS_H + +#include +#include +#include +#include "Core/Monitor.h" +#include "Core/Object.h" + +namespace uLib { + +/** + * @brief Thread class wraps std::thread and provides a common interface. + */ +class Thread : public Object { +public: + Thread(); + virtual ~Thread(); + + /** @brief Starts the thread by calling Run(). */ + void Start(); + + /** @brief Joins the thread. */ + void Join(); + + /** @brief Detaches the thread. */ + void Detach(); + + /** @brief Returns true if the thread is currently joinable. */ + bool IsJoinable() const; + + /** @brief Returns true if the thread is currently running. */ + bool IsRunning() const; + + /** @brief The entry point for the thread. Override this in subclasses. */ + virtual void Run(); + + /** @brief Static helper to sleep the current thread. */ + static void Sleep(int milliseconds); + + /** @brief Static helper to yield the current thread. */ + static void Yield(); + +protected: + // Internal thread entry point + void ThreadEntryPoint(); + + std::thread m_Thread; + std::atomic m_Running; + mutable Mutex m_ThreadMutex; +}; + +} // namespace uLib + +#endif // U_CORE_THREADS_H diff --git a/src/Core/testing/CMakeLists.txt b/src/Core/testing/CMakeLists.txt index ea1cb5e..a3b0d52 100644 --- a/src/Core/testing/CMakeLists.txt +++ b/src/Core/testing/CMakeLists.txt @@ -23,6 +23,8 @@ set( TESTS VectorMetaAllocatorTest PropertyTypesTest HRPTest + MutexTest + ThreadsTest ) set(LIBRARIES diff --git a/src/Core/testing/MutexTest.cpp b/src/Core/testing/MutexTest.cpp new file mode 100644 index 0000000..9a2d648 --- /dev/null +++ b/src/Core/testing/MutexTest.cpp @@ -0,0 +1,108 @@ +#include "Core/Monitor.h" +#include +#include +#include +#include + +using namespace uLib; + +void TestBasicLock() { + std::cout << "Testing basic Mutex Lock/Unlock..." << std::endl; + Mutex m; + m.Lock(); + m.Unlock(); + assert(m.TryLock()); + m.Unlock(); + std::cout << " Passed." << std::endl; +} + +void TestScopedLock() { + std::cout << "Testing Mutex::ScopedLock..." << std::endl; + Mutex m; + { + Mutex::ScopedLock lock(m); + assert(!m.TryLock()); + } + assert(m.TryLock()); + m.Unlock(); + std::cout << " Passed." << std::endl; +} + +void TestTimedLock() { + std::cout << "Testing Mutex TryLockFor..." << std::endl; + Mutex m; + m.Lock(); + auto start = std::chrono::steady_clock::now(); + bool locked = m.TryLockFor(100); + auto end = std::chrono::steady_clock::now(); + auto diff = std::chrono::duration_cast(end - start).count(); + + assert(!locked); + assert(diff >= 100); + m.Unlock(); + std::cout << " Passed (waited " << diff << "ms)." << std::endl; +} + +void TestMacros() { + std::cout << "Testing ULIB_STATIC_LOCK and ULIB_MUTEX_LOCK macros..." << std::endl; + + int counter = 0; + auto task = [&]() { + for(int i=0; i<500; ++i) { + ULIB_STATIC_LOCK(-1) { + counter++; + } + } + }; + + std::vector threads; + for(int i=0; i<4; ++i) threads.emplace_back(task); + for(auto& t : threads) t.join(); + + assert(counter == 2000); + + Mutex m; + int counter2 = 0; + ULIB_MUTEX_LOCK(m, -1) { + counter2++; + } + assert(counter2 == 1); + + std::cout << " Passed." << std::endl; +} + +void TestMonitor() { + std::cout << "Testing Monitor pattern..." << std::endl; + struct Resource { + int value = 0; + void increment() { value++; } + }; + + Monitor monitor(new Resource()); + + auto task = [&]() { + for(int i=0; i<1000; ++i) { + monitor.Access([](Resource& r) { + r.increment(); + }); + } + }; + + std::vector threads; + for(int i=0; i<5; ++i) threads.emplace_back(task); + for(auto& t : threads) t.join(); + + int final_value = monitor.Access([](Resource& r) { return r.value; }); + assert(final_value == 5000); + std::cout << " Passed (final value: " << final_value << ")." << std::endl; +} + +int main() { + TestBasicLock(); + TestScopedLock(); + TestTimedLock(); + TestMacros(); + TestMonitor(); + std::cout << "All Mutex and Monitor tests passed!" << std::endl; + return 0; +} diff --git a/src/Core/testing/ThreadsTest.cpp b/src/Core/testing/ThreadsTest.cpp new file mode 100644 index 0000000..14ee737 --- /dev/null +++ b/src/Core/testing/ThreadsTest.cpp @@ -0,0 +1,72 @@ +#include "Core/Threads.h" +#include +#include +#include + +using namespace uLib; + +class MyThread : public Thread { +public: + MyThread() : counter(0) {} + void Run() override { + for (int i = 0; i < 5; ++i) { + counter++; + Thread::Sleep(10); + } + } + std::atomic counter; +}; + +void TestBasicThread() { + std::cout << "Testing basic Thread lifecycle..." << std::endl; + MyThread t; + assert(!t.IsRunning()); + t.Start(); + assert(t.IsRunning()); + t.Join(); + assert(!t.IsRunning()); + assert(t.counter == 5); + std::cout << " Passed." << std::endl; +} + +void TestThreadDetach() { + std::cout << "Testing Thread Detach..." << std::endl; + std::atomic done(false); + + // Using a lambda or a simple subclass + class DetachedThread : public Thread { + public: + DetachedThread(std::atomic& d) : m_done(d) {} + void Run() override { + Thread::Sleep(50); + m_done = true; + } + std::atomic& m_done; + }; + + { + DetachedThread* t = new DetachedThread(done); + t->Start(); + t->Detach(); + // The thread object 't' is still alive here, + // but it will be destroyed soon if we delete it. + // For a detached thread using members, we MUST keep it alive. + + int wait_count = 0; + while(!done && wait_count < 20) { + Thread::Sleep(10); + wait_count++; + } + delete t; + } + + assert(done); + std::cout << " Passed." << std::endl; +} + +int main() { + TestBasicThread(); + TestThreadDetach(); + std::cout << "All Thread tests passed!" << std::endl; + return 0; +}