vtk-m2/vtkm/cont/Token.cxx
Kenneth Moreland 99e14ab8a6 Add proper enqueuing of Tokens for ArrayHandle
An issue that was identified for the thread safety of `ArrayHandle` is
that if several threads are waiting to use an `ArrayHandle`, there might
be an expectation of the order in which the operations happen. For
example, if one thread is modifying the contents of an `ArrayHandle` and
another is reading those results, we would need the first one to start
before the second one.

To solve this, a queue is added to `ArrayHandle` such that when waiting
to read or write an `ArrayHandle` the `Token` has to be at the top of
the queue in addition to other requirements being met.

Additionally, an `Enqueue` method is added to add a `Token` to the queue
without blocking. This allows a control thread to queue the access and
then spawn a thread where the actual work will be done. As long as
everything is enqueued on the main thread, the operations will happen in
the expected order.
2020-06-08 16:49:02 -06:00

149 lines
3.9 KiB
C++

//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/Token.h>
#include <list>
using LockType = std::unique_lock<std::mutex>;
class vtkm::cont::Token::InternalStruct
{
std::mutex Mutex;
std::list<vtkm::cont::Token::HeldReference> HeldReferences;
VTKM_CONT void CheckLock(const LockType& lock) const
{
VTKM_ASSERT((lock.mutex() == &this->Mutex) && (lock.owns_lock()));
}
public:
LockType GetLock() { return LockType(this->Mutex); }
std::list<vtkm::cont::Token::HeldReference>* GetHeldReferences(const LockType& lock)
{
this->CheckLock(lock);
return &this->HeldReferences;
}
};
struct vtkm::cont::Token::HeldReference
{
std::unique_ptr<vtkm::cont::Token::ObjectReference> ObjectReference;
vtkm::cont::Token::ReferenceCount* ReferenceCountPointer;
std::mutex* MutexPointer;
std::condition_variable* ConditionVariablePointer;
HeldReference(std::unique_ptr<vtkm::cont::Token::ObjectReference>&& objRef,
vtkm::cont::Token::ReferenceCount* refCountP,
std::mutex* mutexP,
std::condition_variable* conditionVariableP)
: ObjectReference(std::move(objRef))
, ReferenceCountPointer(refCountP)
, MutexPointer(mutexP)
, ConditionVariablePointer(conditionVariableP)
{
}
};
vtkm::cont::Token::Token()
{
}
vtkm::cont::Token::Token(Token&& rhs)
: Internals(std::move(rhs.Internals))
{
}
vtkm::cont::Token::~Token()
{
this->DetachFromAll();
}
void vtkm::cont::Token::DetachFromAll()
{
if (!this->Internals)
{
// If internals is NULL, then we are not attached to anything.
return;
}
LockType localLock = this->Internals->GetLock();
auto heldReferences = this->Internals->GetHeldReferences(localLock);
for (auto&& held : *heldReferences)
{
LockType objectLock(*held.MutexPointer);
*held.ReferenceCountPointer -= 1;
objectLock.unlock();
held.ConditionVariablePointer->notify_all();
}
heldReferences->clear();
}
vtkm::cont::Token::Reference vtkm::cont::Token::GetReference() const
{
if (!this->Internals)
{
this->Internals.reset(new InternalStruct);
}
return this->Internals.get();
}
void vtkm::cont::Token::Attach(std::unique_ptr<vtkm::cont::Token::ObjectReference>&& objectRef,
vtkm::cont::Token::ReferenceCount* referenceCountPointer,
std::unique_lock<std::mutex>& lock,
std::condition_variable* conditionVariablePointer)
{
if (!this->Internals)
{
this->Internals.reset(new InternalStruct);
}
LockType localLock = this->Internals->GetLock();
if (this->IsAttached(localLock, referenceCountPointer))
{
// Already attached.
return;
}
if (!lock.owns_lock())
{
lock.lock();
}
*referenceCountPointer += 1;
this->Internals->GetHeldReferences(localLock)->emplace_back(
std::move(objectRef), referenceCountPointer, lock.mutex(), conditionVariablePointer);
}
inline bool vtkm::cont::Token::IsAttached(
LockType& lock,
vtkm::cont::Token::ReferenceCount* referenceCountPointer) const
{
if (!this->Internals)
{
return false;
}
for (auto&& heldReference : *this->Internals->GetHeldReferences(lock))
{
if (referenceCountPointer == heldReference.ReferenceCountPointer)
{
return true;
}
}
return false;
}
bool vtkm::cont::Token::IsAttached(vtkm::cont::Token::ReferenceCount* referenceCountPointer) const
{
if (!this->Internals)
{
return false;
}
LockType lock = this->Internals->GetLock();
return this->IsAttached(lock, referenceCountPointer);
}