/* * safecoll.cxx * * Thread safe collection classes. * * Portable Windows Library * * Copyright (c) 2002 Equivalence Pty. Ltd. * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Portable Windows Library. * * The Initial Developer of the Original Code is Equivalence Pty. Ltd. * * Contributor(s): ______________________________________. * * $Log: safecoll.cxx,v $ * Revision 1.14 2004/10/14 23:01:31 csoutheren * Fiuxed problem with usage of Sleep * * Revision 1.13 2004/10/14 12:31:47 rjongbloed * Added synchronous mode for safe collection RemoveAll() to wait until all objects * have actually been deleted before returning. * * Revision 1.12 2004/10/04 12:54:33 rjongbloed * Added functions for locking an unlocking to "auto-unlock" classes. * * Revision 1.11 2004/08/14 07:42:31 rjongbloed * Added trace log at level 6 for helping find PSafeObject reference/dereference errors. * * Revision 1.10 2004/08/12 12:37:41 rjongbloed * Fixed bug recently introduced so removes deleted object from deletion list. * Also changed removal list to be correct type. * * Revision 1.9 2004/08/05 12:15:56 rjongbloed * Added classes for auto unlocking read only and read write mutex on * PSafeObject - similar to PWaitAndSIgnal. * Utilised mutable keyword for mutex and improved the constness of functions. * Added DisallowDeleteObjects to safe collections so can have a PSafeObject in * multiple collections. * Added a tempalte function to do casting of PSafePtr to a PSafePtr of a derived * class. * Assured that a PSafeObject present on a collection always increments its * reference count so while in collection it is not deleted. * * Revision 1.8 2004/04/03 08:22:22 csoutheren * Remove pseudo-RTTI and replaced with real RTTI * * Revision 1.7 2002/12/10 07:37:34 robertj * optimised SetLockMode() so if doesn't change mode it doesn't do anything. * * Revision 1.6 2002/10/29 00:06:24 robertj * Changed template classes so things like PSafeList actually creates the * base collection class as well. * Allowed for the PSafeList::Append() to return a locked pointer to the * object just appended. * * Revision 1.5 2002/10/04 08:22:50 robertj * Changed read/write mutex so can be called by same thread without deadlock * removing the need to a lock count in safe pointer. * Added asserts if try and dereference a NULL safe pointer. * Added more documentation on behaviour. * * Revision 1.4 2002/08/29 06:53:28 robertj * Added optimisiation, separate mutex for toBeRemoved list. * Added assert for reference count going below zero. * Fixed incorrect usage of lockCount if target of an assignment from another * safe pointer. Would not unlock the safe object which could cause deadlock. * * Revision 1.3 2002/05/06 00:44:45 robertj * Made the lock/unlock read only const so can be used in const functions. * * Revision 1.2 2002/05/01 04:48:05 robertj * GNU compatibility. * * Revision 1.1 2002/05/01 04:16:44 robertj * Added thread safe collection classes. * */ #ifdef __GNUC__ #pragma implementation "safecoll.h" #endif #include #include #define new PNEW ///////////////////////////////////////////////////////////////////////////// PSafeObject::PSafeObject() { safeReferenceCount = 0; safelyBeingRemoved = FALSE; } BOOL PSafeObject::SafeReference() { PWaitAndSignal mutex(safetyMutex); if (safelyBeingRemoved) return FALSE; safeReferenceCount++; PTRACE(6, "SafeColl\tIncrement reference count to " << safeReferenceCount << " for " << GetClass() << ' ' << (void *)this); return TRUE; } void PSafeObject::SafeDereference() { safetyMutex.Wait(); if (PAssert(safeReferenceCount > 0, PLogicError)) { safeReferenceCount--; PTRACE(6, "SafeColl\tDecrement reference count to " << safeReferenceCount << " for " << GetClass() << ' ' << (void *)this); } safetyMutex.Signal(); } BOOL PSafeObject::LockReadOnly() const { safetyMutex.Wait(); if (safelyBeingRemoved) { safetyMutex.Signal(); return FALSE; } safetyMutex.Signal(); safeInUseFlag.StartRead(); return TRUE; } void PSafeObject::UnlockReadOnly() const { safeInUseFlag.EndRead(); } BOOL PSafeObject::LockReadWrite() { safetyMutex.Wait(); if (safelyBeingRemoved) { safetyMutex.Signal(); return FALSE; } safetyMutex.Signal(); safeInUseFlag.StartWrite(); return TRUE; } void PSafeObject::UnlockReadWrite() { safeInUseFlag.EndWrite(); } void PSafeObject::SafeRemove() { safetyMutex.Wait(); safelyBeingRemoved = TRUE; safetyMutex.Signal(); } BOOL PSafeObject::SafelyCanBeDeleted() const { PWaitAndSignal mutex(safetyMutex); return safelyBeingRemoved && safeReferenceCount == 0; } ///////////////////////////////////////////////////////////////////////////// PSafeLockReadOnly::PSafeLockReadOnly(const PSafeObject & object) : safeObject((PSafeObject &)object) { locked = safeObject.LockReadOnly(); } PSafeLockReadOnly::~PSafeLockReadOnly() { if (locked) safeObject.UnlockReadOnly(); } BOOL PSafeLockReadOnly::Lock() { locked = safeObject.LockReadOnly(); return locked; } void PSafeLockReadOnly::Unlock() { if (locked) { safeObject.UnlockReadOnly(); locked = FALSE; } } ///////////////////////////////////////////////////////////////////////////// PSafeLockReadWrite::PSafeLockReadWrite(const PSafeObject & object) : safeObject((PSafeObject &)object) { locked = safeObject.LockReadWrite(); } PSafeLockReadWrite::~PSafeLockReadWrite() { if (locked) safeObject.UnlockReadWrite(); } BOOL PSafeLockReadWrite::Lock() { locked = safeObject.LockReadWrite(); return locked; } void PSafeLockReadWrite::Unlock() { if (locked) { safeObject.UnlockReadWrite(); locked = FALSE; } } ///////////////////////////////////////////////////////////////////////////// PSafeCollection::PSafeCollection(PCollection * coll) { collection = coll; collection->DisallowDeleteObjects(); toBeRemoved.DisallowDeleteObjects(); deleteObjects = TRUE; } PSafeCollection::~PSafeCollection() { deleteObjectsTimer.Stop(); toBeRemoved.AllowDeleteObjects(); toBeRemoved.RemoveAll(); collection->AllowDeleteObjects(); delete collection; } BOOL PSafeCollection::SafeRemove(PSafeObject * obj) { if (obj == NULL) return FALSE; PWaitAndSignal mutex(collectionMutex); if (!collection->Remove(obj)) return FALSE; SafeRemoveObject(obj); return TRUE; } BOOL PSafeCollection::SafeRemoveAt(PINDEX idx) { PWaitAndSignal mutex(collectionMutex); PSafeObject * obj = PDownCast(PSafeObject, collection->RemoveAt(idx)); if (obj == NULL) return FALSE; SafeRemoveObject(obj); return TRUE; } void PSafeCollection::RemoveAll(BOOL synchronous) { collectionMutex.Wait(); while (collection->GetSize() > 0) SafeRemoveObject(PDownCast(PSafeObject, collection->RemoveAt(0))); collectionMutex.Signal(); if (synchronous) { // Have unfortunate busy loop here, but it should be very // rare that it will be here for long while (!DeleteObjectsToBeRemoved()) PThread::Sleep(100); } } void PSafeCollection::SafeRemoveObject(PSafeObject * obj) { if (obj == NULL) return; obj->SafeDereference(); if (!deleteObjects) return; obj->SafeRemove(); removalMutex.Wait(); toBeRemoved.Append(obj); removalMutex.Signal(); } BOOL PSafeCollection::DeleteObjectsToBeRemoved() { PWaitAndSignal lock(removalMutex); PINDEX i = 0; while (i < toBeRemoved.GetSize()) { if (toBeRemoved[i].SafelyCanBeDeleted()) { PObject * obj = toBeRemoved.RemoveAt(i); removalMutex.Signal(); DeleteObject(obj); removalMutex.Wait(); i = 0; // Restart looking through list } else i++; } return toBeRemoved.IsEmpty() && collection->IsEmpty(); } void PSafeCollection::DeleteObject(PObject * object) const { delete object; } void PSafeCollection::SetAutoDeleteObjects() { if (deleteObjectsTimer.IsRunning()) return; deleteObjectsTimer.SetNotifier(PCREATE_NOTIFIER(DeleteObjectsTimeout)); deleteObjectsTimer.RunContinuous(1000); // EVery second } void PSafeCollection::DeleteObjectsTimeout(PTimer &, INT) { DeleteObjectsToBeRemoved(); } PINDEX PSafeCollection::GetSize() const { PWaitAndSignal lock(collectionMutex); return collection->GetSize(); } ///////////////////////////////////////////////////////////////////////////// PSafePtrBase::PSafePtrBase(PSafeObject * obj, PSafetyMode mode) { collection = NULL; currentObject = obj; lockMode = mode; EnterSafetyMode(WithReference); } PSafePtrBase::PSafePtrBase(const PSafeCollection & safeCollection, PSafetyMode mode, PINDEX idx) { collection = &safeCollection; currentObject = NULL; lockMode = mode; Assign(idx); } PSafePtrBase::PSafePtrBase(const PSafeCollection & safeCollection, PSafetyMode mode, PSafeObject * obj) { collection = &safeCollection; currentObject = NULL; lockMode = mode; Assign(obj); } PSafePtrBase::PSafePtrBase(const PSafePtrBase & enumerator) { collection = enumerator.collection; currentObject = enumerator.currentObject; lockMode = enumerator.lockMode; EnterSafetyMode(WithReference); } PSafePtrBase::~PSafePtrBase() { ExitSafetyMode(WithDereference); } PObject::Comparison PSafePtrBase::Compare(const PObject & obj) const { const PSafePtrBase * other = PDownCast(const PSafePtrBase, &obj); if (other == NULL) return GreaterThan; if (currentObject < other->currentObject) return LessThan; if (currentObject > other->currentObject) return GreaterThan; return EqualTo; } void PSafePtrBase::Assign(const PSafePtrBase & enumerator) { if (this == &enumerator) return; // lockCount ends up zero after this ExitSafetyMode(WithDereference); collection = enumerator.collection; currentObject = enumerator.currentObject; lockMode = enumerator.lockMode; EnterSafetyMode(WithReference); } void PSafePtrBase::Assign(const PSafeCollection & safeCollection) { // lockCount ends up zero after this ExitSafetyMode(WithDereference); collection = &safeCollection; lockMode = PSafeReadWrite; Assign((PINDEX)0); } void PSafePtrBase::Assign(PSafeObject * newObj) { ExitSafetyMode(WithDereference); currentObject = newObj; if (newObj == NULL) return; if (collection == NULL) { lockMode = PSafeReference; if (!EnterSafetyMode(WithReference)) currentObject = NULL; return; } collection->collectionMutex.Wait(); if (collection->collection->GetObjectsIndex(newObj) == P_MAX_INDEX) { collection->collectionMutex.Signal(); collection = NULL; lockMode = PSafeReference; if (!EnterSafetyMode(WithReference)) currentObject = NULL; } else { if (!newObj->SafeReference()) currentObject = NULL; collection->collectionMutex.Signal(); EnterSafetyMode(AlreadyReferenced); } } void PSafePtrBase::Assign(PINDEX idx) { ExitSafetyMode(WithDereference); currentObject = NULL; if (collection == NULL) return; collection->collectionMutex.Wait(); while (idx < collection->collection->GetSize()) { currentObject = (PSafeObject *)collection->collection->GetAt(idx); if (currentObject != NULL) { if (currentObject->SafeReference()) break; currentObject = NULL; } idx++; } collection->collectionMutex.Signal(); EnterSafetyMode(AlreadyReferenced); } void PSafePtrBase::Next() { if (collection == NULL || currentObject == NULL) return; ExitSafetyMode(NoDereference); collection->collectionMutex.Wait(); PINDEX idx = collection->collection->GetObjectsIndex(currentObject); currentObject->SafeDereference(); currentObject = NULL; if (idx != P_MAX_INDEX) { while (++idx < collection->collection->GetSize()) { currentObject = (PSafeObject *)collection->collection->GetAt(idx); if (currentObject != NULL) { if (currentObject->SafeReference()) break; currentObject = NULL; } } } collection->collectionMutex.Signal(); EnterSafetyMode(AlreadyReferenced); } void PSafePtrBase::Previous() { if (collection == NULL || currentObject == NULL) return; ExitSafetyMode(NoDereference); collection->collectionMutex.Wait(); PINDEX idx = collection->collection->GetObjectsIndex(currentObject); currentObject->SafeDereference(); currentObject = NULL; if (idx != P_MAX_INDEX) { while (idx-- > 0) { currentObject = (PSafeObject *)collection->collection->GetAt(idx); if (currentObject != NULL) { if (currentObject->SafeReference()) break; currentObject = NULL; } } } collection->collectionMutex.Signal(); EnterSafetyMode(AlreadyReferenced); } BOOL PSafePtrBase::SetSafetyMode(PSafetyMode mode) { if (lockMode == mode) return TRUE; ExitSafetyMode(NoDereference); lockMode = mode; return EnterSafetyMode(AlreadyReferenced); } BOOL PSafePtrBase::EnterSafetyMode(EnterSafetyModeOption ref) { if (currentObject == NULL) return FALSE; if (ref == WithReference && !currentObject->SafeReference()) { currentObject = NULL; return FALSE; } switch (lockMode) { case PSafeReadOnly : if (currentObject->LockReadOnly()) return TRUE; break; case PSafeReadWrite : if (currentObject->LockReadWrite()) return TRUE; break; case PSafeReference : return TRUE; } currentObject->SafeDereference(); currentObject = NULL; return FALSE; } void PSafePtrBase::ExitSafetyMode(ExitSafetyModeOption ref) { if (currentObject == NULL) return; switch (lockMode) { case PSafeReadOnly : currentObject->UnlockReadOnly(); break; case PSafeReadWrite : currentObject->UnlockReadWrite(); break; case PSafeReference : break; } if (ref == WithDereference) currentObject->SafeDereference(); } // End of File ///////////////////////////////////////////////////////////////