[*] Improved destruction watchers
This commit is contained in:
parent
b4f4e623d8
commit
27977779a9
@ -16,15 +16,17 @@ namespace Aurora::Utility
|
||||
inline DestructionWatch();
|
||||
inline ~DestructionWatch();
|
||||
|
||||
// If your (optionally abstract) ADestructionWatcher requires access to the parent object, it is highly recommened to call RemoveAll within the destructor of the parent class.
|
||||
// Otherwise, you must maintain a strict ctor/dtor order of your classes layout.
|
||||
inline void RemoveAll();
|
||||
|
||||
private:
|
||||
friend struct ADestructionWatcher;
|
||||
|
||||
Threading::Waitables::FutexWaitable mutex;
|
||||
Threading::Waitables::FutexWaitable dtorSignal;
|
||||
ADestructionWatcher *pFirstWatcher {};
|
||||
AuList<ADestructionWatcher *> listpWatches;
|
||||
AuList<ADestructionWatcher *> *pListpWatches {};
|
||||
AuUInt32 uDtorCalls {};
|
||||
|
||||
inline void RemoveWatcher(ADestructionWatcher *pWatcher);
|
||||
inline void RemoveWatcherCall(ADestructionWatcher *pWatcher);
|
||||
@ -41,11 +43,38 @@ namespace Aurora::Utility
|
||||
inline bool IsObservedDead();
|
||||
|
||||
protected:
|
||||
inline virtual void OnDestroyedChild()
|
||||
// Note that since the parent object is in the midst of destruction; you should not be manually calling the destroy operations of it or its' children.
|
||||
// C++ construction and the inverse destruction order are well defined.
|
||||
// In the case of extending DestructionWatch as a base class, our constructor should be invoked first, and therefore our destructor will be invoked last.
|
||||
// In the case of inserting DestructionWatch as a class member, you must guard resource access via synchronization primitives placed in proper construction/destruction order.
|
||||
inline virtual void OnParentDestroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
inline virtual void OnDestroy()
|
||||
{
|
||||
// You *might* be able to access the parent object at this point in time.
|
||||
//
|
||||
// It is legal to access the memory under both OnParentDestroy(), and OnDestroy() in the case that IsObservedAlive() return true; however, accessing the parents
|
||||
// member fields shall be restricted pursuant to the inverse of C++s construction order. It is possible for multithreaded code to be in the midst of calling
|
||||
// the destruction chain before we are aware of the destruction condition. Shared pointers work around this by invoking private std::enable_shared_from_this
|
||||
// members at the time of construction and time of control block release. It *should* be safe to access HANDLEs, raw c pointers, etc without any special logic,
|
||||
// on the account that the parent object will not have released its' memory until we're done; same applies to OnParentDestroy().
|
||||
//
|
||||
// ** To further harden against out of order destruction, it is advisable but not required, to call DestructionWatch::RemoveAll() in the parents destructor. **
|
||||
// ** Alternatively, you must be hyper-aware of the C++ destruction order. **
|
||||
}
|
||||
|
||||
public:
|
||||
inline void Destroy()
|
||||
{
|
||||
if (!DoUnderLock(&ADestructionWatcher::OnDestroy, this))
|
||||
{
|
||||
this->OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct DestructionWatch;
|
||||
friend class Threading::LockGuard<ADestructionWatcher *>;
|
||||
@ -60,21 +89,10 @@ namespace Aurora::Utility
|
||||
AuConditional_t<AuIsSame_v<AuResultOf_t<T, Args...>, void>, bool, AuOptional<AuResultOf_t<T, Args...>>>
|
||||
DoUnderLock(const T &callable, Args && ...args)
|
||||
{
|
||||
AU_LOCK_GUARD(this);
|
||||
{
|
||||
AU_LOCK_GUARD(this->selfLock);
|
||||
|
||||
if (this->pOwnsLock)
|
||||
{
|
||||
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
|
||||
{
|
||||
callable(AuForward<Args &&>(args)...);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return callable(AuForward<Args &&>(args)...);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!this->pParent)
|
||||
{
|
||||
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
|
||||
{
|
||||
@ -85,12 +103,28 @@ namespace Aurora::Utility
|
||||
return AuOptional<AuResultOf_t<T, Args...>> {};
|
||||
}
|
||||
}
|
||||
|
||||
AuAtomicAdd(&this->pParent->uDtorCalls, 1u);
|
||||
}
|
||||
|
||||
{
|
||||
AU_LOCK_GUARD(this);
|
||||
|
||||
if constexpr (AuIsSame_v<AuResultOf_t<T, Args...>, void>)
|
||||
{
|
||||
AuInvoke(callable, AuForward<Args &&>(args)...);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return AuInvoke(callable, AuForward<Args &&>(args)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Threading::Waitables::FutexWaitable selfLock;
|
||||
DestructionWatch *pParent {};
|
||||
Threading::Waitables::FutexWaitable *pOwnsLock {};
|
||||
};
|
||||
|
||||
DestructionWatch::DestructionWatch()
|
||||
@ -113,14 +147,23 @@ namespace Aurora::Utility
|
||||
this->RemoveWatcherCall(pWatcher);
|
||||
}
|
||||
|
||||
for (const auto pWatcher : AuExchange(this->listpWatches, {}))
|
||||
if (auto pWatchList = AuExchange(this->pListpWatches, nullptr))
|
||||
{
|
||||
for (const auto pWatcher : *pWatchList)
|
||||
{
|
||||
this->RemoveWatcherCall(pWatcher);
|
||||
}
|
||||
|
||||
delete pWatchList;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AU_LOCK_GUARD(this->dtorSignal);
|
||||
AuUInt32 uOld {};
|
||||
while ((uOld = AuAtomicLoad(&this->uDtorCalls)) != 0)
|
||||
{
|
||||
Aurora::Threading::WaitOnAddress(&this->uDtorCalls, &uOld, sizeof(uOld), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +177,10 @@ namespace Aurora::Utility
|
||||
return;
|
||||
}
|
||||
|
||||
AuTryRemove(this->listpWatches, pWatcher);
|
||||
if (this->pListpWatches)
|
||||
{
|
||||
AuTryRemove(*this->pListpWatches, pWatcher);
|
||||
}
|
||||
}
|
||||
|
||||
void DestructionWatch::RemoveWatcherCall(ADestructionWatcher *pWatcher)
|
||||
@ -161,7 +207,11 @@ namespace Aurora::Utility
|
||||
}
|
||||
else
|
||||
{
|
||||
pWatch->listpWatches.push_back(this);
|
||||
if (!pWatch->pListpWatches)
|
||||
{
|
||||
pWatch->pListpWatches = new AuRemovePointer_t<decltype(pWatch->pListpWatches)>();
|
||||
}
|
||||
pWatch->pListpWatches->push_back(this);
|
||||
}
|
||||
this->pParent = pWatch;
|
||||
}
|
||||
@ -187,8 +237,14 @@ namespace Aurora::Utility
|
||||
|
||||
if (auto pParent = AuExchange(this->pParent, nullptr))
|
||||
{
|
||||
this->OnDestroy();
|
||||
|
||||
pParent->RemoveWatcher(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
void ADestructionWatcher::OnDTOR()
|
||||
@ -198,32 +254,19 @@ namespace Aurora::Utility
|
||||
this->pParent = nullptr;
|
||||
}
|
||||
|
||||
this->OnDestroyedChild();
|
||||
this->OnParentDestroy();
|
||||
}
|
||||
|
||||
void ADestructionWatcher::Lock()
|
||||
{
|
||||
AU_LOCK_GUARD(this->selfLock);
|
||||
|
||||
this->pOwnsLock = nullptr;
|
||||
|
||||
if (!this->pParent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->pParent->dtorSignal.Lock();
|
||||
|
||||
// noting that once we leave this scope, we could have our parent stolen from us (this->selfLock), but its' destructor will still be blocked until we finish
|
||||
// this is why we end up using a raw pointer here.
|
||||
this->pOwnsLock = &this->pParent->dtorSignal;
|
||||
}
|
||||
|
||||
void ADestructionWatcher::Unlock()
|
||||
{
|
||||
if (this->pOwnsLock)
|
||||
if (AuAtomicSub(&this->pParent->uDtorCalls, 0u) == 0)
|
||||
{
|
||||
this->pOwnsLock->Unlock();
|
||||
Aurora::Threading::WakeOnAddress(&this->pParent->uDtorCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user