diff --git a/src/db/unit_tests/dbTilingProcessor.cc b/src/db/unit_tests/dbTilingProcessor.cc index d6d93b288..bdd0262d9 100644 --- a/src/db/unit_tests/dbTilingProcessor.cc +++ b/src/db/unit_tests/dbTilingProcessor.cc @@ -31,6 +31,8 @@ #include "dbSaveLayoutOptions.h" #include +#include +#include unsigned int get_rand() { @@ -400,6 +402,8 @@ public: void add (double x) const { + static QMutex lock; + QMutexLocker locker (&lock); *mp_sum += x; *mp_n += 1; } @@ -419,6 +423,8 @@ gsi::Class decl_MyTilingOutputReceiver (gsi::dbdecl_Tile ); // Multithreaded, access to _rec() +// This will mainly test the ability of gsi::Proxy to manage references +// in a multithreaded case. TEST(5) { db::Layout ly1; diff --git a/src/gsi/gsi/gsiObject.cc b/src/gsi/gsi/gsiObject.cc index 882b97602..ee3ce2549 100644 --- a/src/gsi/gsi/gsiObject.cc +++ b/src/gsi/gsi/gsiObject.cc @@ -27,9 +27,13 @@ #include "tlLog.h" +#include + namespace gsi { +QMutex Proxy::m_lock; + Proxy::Proxy (const gsi::ClassBase *_cls_decl) : m_cls_decl (_cls_decl), m_obj (0), @@ -43,8 +47,10 @@ Proxy::Proxy (const gsi::ClassBase *_cls_decl) Proxy::~Proxy () { + QMutexLocker locker (&m_lock); + try { - set (0, false, false, false); + set_internal (0, false, false, false); } catch (std::exception &ex) { tl::warn << "Caught exception in object destructor: " << ex.what (); } catch (tl::Exception &ex) { @@ -58,6 +64,8 @@ Proxy::~Proxy () void Proxy::destroy () { + QMutexLocker locker (&m_lock); + if (! m_cls_decl) { m_obj = 0; return; @@ -82,7 +90,7 @@ Proxy::destroy () if (m_owned || m_can_destroy) { o = m_obj; } - detach (); + detach_internal (); if (o) { m_cls_decl->destroy (o); } @@ -91,23 +99,15 @@ Proxy::destroy () void Proxy::detach () { - if (! m_destroyed && m_cls_decl && m_cls_decl->is_managed ()) { - gsi::ObjectBase *gsi_object = m_cls_decl->gsi_object (m_obj, false); - if (gsi_object) { - gsi_object->status_changed_event ().remove (this, &Proxy::object_status_changed); - } - } - - m_obj = 0; - m_destroyed = true; - m_const_ref = false; - m_owned = false; - m_can_destroy = false; + QMutexLocker locker (&m_lock); + detach_internal (); } void Proxy::release () { + QMutexLocker locker (&m_lock); + // If the object is managed we first reset the ownership of all other clients // and then make us the owner const gsi::ClassBase *cls = m_cls_decl; @@ -125,6 +125,8 @@ Proxy::release () void Proxy::keep () { + QMutexLocker locker (&m_lock); + const gsi::ClassBase *cls = m_cls_decl; if (cls) { void *o = obj (); @@ -142,6 +144,45 @@ Proxy::keep () void Proxy::set (void *obj, bool owned, bool const_ref, bool can_destroy) +{ + QMutexLocker locker (&m_lock); + set_internal (obj, owned, const_ref, can_destroy); +} + +void * +Proxy::obj () +{ + QMutexLocker locker (&m_lock); + + if (! m_obj) { + if (m_destroyed) { + throw tl::Exception (tl::to_string (QObject::tr ("Object has been destroyed already"))); + } else { + // delayed creation of a detached C++ object .. + set_internal (m_cls_decl->create (), true, false, true); + } + } + + return m_obj; +} + +void +Proxy::object_status_changed (gsi::ObjectBase::StatusEventType type) +{ + QMutexLocker locker (&m_lock); + + if (type == gsi::ObjectBase::ObjectDestroyed) { + m_destroyed = true; // NOTE: must be set before detach and indicates that the object was destroyed externally. + detach_internal (); + } else if (type == gsi::ObjectBase::ObjectKeep) { + m_owned = false; + } else if (type == gsi::ObjectBase::ObjectRelease) { + m_owned = true; + } +} + +void +Proxy::set_internal (void *obj, bool owned, bool const_ref, bool can_destroy) { bool prev_owned = m_owned; @@ -198,32 +239,21 @@ Proxy::set (void *obj, bool owned, bool const_ref, bool can_destroy) m_destroyed = false; } -void * -Proxy::obj () +void +Proxy::detach_internal() { - if (! m_obj) { - if (m_destroyed) { - throw tl::Exception (tl::to_string (QObject::tr ("Object has been destroyed already"))); - } else { - // delayed creation of a detached C++ object .. - set(m_cls_decl->create (), true, false, true); + if (! m_destroyed && m_cls_decl && m_cls_decl->is_managed ()) { + gsi::ObjectBase *gsi_object = m_cls_decl->gsi_object (m_obj, false); + if (gsi_object) { + gsi_object->status_changed_event ().remove (this, &Proxy::object_status_changed); } } - return m_obj; -} - -void -Proxy::object_status_changed (gsi::ObjectBase::StatusEventType type) -{ - if (type == gsi::ObjectBase::ObjectDestroyed) { - m_destroyed = true; // NOTE: must be set before detach and indicates that the object was destroyed externally. - detach (); - } else if (type == gsi::ObjectBase::ObjectKeep) { - m_owned = false; - } else if (type == gsi::ObjectBase::ObjectRelease) { - m_owned = true; - } + m_obj = 0; + m_destroyed = true; + m_const_ref = false; + m_owned = false; + m_can_destroy = false; } } diff --git a/src/gsi/gsi/gsiObject.h b/src/gsi/gsi/gsiObject.h index cc39f2fb9..1cb76162d 100644 --- a/src/gsi/gsi/gsiObject.h +++ b/src/gsi/gsi/gsiObject.h @@ -29,6 +29,7 @@ #include "gsiCommon.h" #include +#include // For a comprehensive documentation see gsi.h @@ -191,6 +192,17 @@ private: * * Using a proxy object allows having a gsi::ObjectBase that does not derive * from tl::Object and can derive from any base class (specifically QObject). + * + * NOTE about MT safety: the model is: + * + * - the Proxy belongs to a thread + * - the original object (the proxy target) belongs to a different thread + * - there can be multiple Proxy objects for one target + * - the target object itself and the methods by which the proxy acts on + * the target object are thread safe + * + * This implies that all operations related to the manipulation of proxy + * to target relation need to be guarded by a lock. */ class GSI_PUBLIC Proxy : public tl::Object @@ -228,8 +240,11 @@ private: bool m_const_ref : 1; bool m_destroyed : 1; bool m_can_destroy : 1; + static QMutex m_lock; + void set_internal (void *obj, bool owned, bool const_ref, bool can_destroy); void object_status_changed (gsi::ObjectBase::StatusEventType type); + void detach_internal (); }; }