mirror of
https://gitlab.kitware.com/vtk/vtk-m
synced 2024-09-16 17:22:55 +00:00
Merge topic 'no-control-portal-locking'
594c73c06 Simplify use of ControlArrayValid acc9774f6 Fix CUDA warnings 10e8a4a7f Remove locking control ArrayPortals Acked-by: Kitware Robot <kwrobot@kitware.com> Acked-by: Matt Larsen <larsen30@llnl.gov> Acked-by: Robert Maynard <robert.maynard@kitware.com> Merge-request: !1988
This commit is contained in:
commit
d0d1f750c2
@ -158,15 +158,13 @@ the covers for most users.
|
||||
The `GetPortalConstControl` and `GetPortalControl` methods have been
|
||||
deprecated. Instead, the methods `ReadPortal` and `WritePortal` should be
|
||||
used. The calling signature is the same as their predecessors, but the
|
||||
returned portal contains a `Token` as part of its state and prevents and
|
||||
changes to the `ArrayHandle` it comes from. The `WritePortal` also prevents
|
||||
other reads from the array.
|
||||
returned portal contains a reference back to the original `ArrayHandle`.
|
||||
The reference keeps track of whether the memory allocation has changed.
|
||||
|
||||
The advantage is that the returned portal will always be valid. However, it
|
||||
is now the case that a control portal can prevent something else from
|
||||
running. This means that control portals should drop scope as soon as
|
||||
possible. It is because of this behavior change that new methods were
|
||||
created instead of altering the old ones.
|
||||
If the `ArrayHandle` is changed while the `ArrayPortal` still exists,
|
||||
nothing will happen immediately. However, if the portal is subsequently
|
||||
accessed (i.e. `Set` or `Get` is called on it), then a fatal error will be
|
||||
reported to the log.
|
||||
|
||||
## Deadlocks
|
||||
|
||||
@ -175,46 +173,38 @@ to able to be immediately invalidated), the scopes have the ability to
|
||||
cause operations to block. This can cause issues if the `ArrayHandle` is
|
||||
attempted to be used by multiple `Token`s at once.
|
||||
|
||||
Care should be taken to ensure that a single thread does not attempt to use
|
||||
an `ArrayHandle` two ways at the same time.
|
||||
The following is a contrived example of causing a deadlock.
|
||||
|
||||
``` cpp
|
||||
auto portal = array.WritePortal();
|
||||
for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index)
|
||||
{
|
||||
portal.Set(index, /* An interesting value */);
|
||||
}
|
||||
vtkm::cont::Invoker invoke;
|
||||
invoke(MyWorklet, array); // Oops. Deadlock here.
|
||||
vtkm::cont::Token token1;
|
||||
auto portal1 = array.PrepareForInPlace(Device{}, token1);
|
||||
|
||||
vtkm::cont::Token token2;
|
||||
auto portal2 = array.PrepareForInput(Device{}, token2);
|
||||
```
|
||||
|
||||
In this example, the last line deadlocks because `portal` is still holding
|
||||
onto `array` for writing. When the worklet is invoked, it waits for
|
||||
everything to stop writing to `array` so that it can be safely be read.
|
||||
Instead, `portal` should be properly scoped.
|
||||
The last line will deadlock as `PrepareForInput` waits for `token1` to
|
||||
detach, which will never happen. To prevent this from happening, if you use
|
||||
the same `Token` on the array, it will always allow the action. Thus, the
|
||||
following will work fine.
|
||||
|
||||
``` cpp
|
||||
{
|
||||
auto portal = array.GetPortalControl();
|
||||
for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index)
|
||||
{
|
||||
portal.Set(index, /* An interesting value */);
|
||||
}
|
||||
}
|
||||
vtkm::cont::Invoker invoke;
|
||||
invoke(MyWorklet, array); // Runs fine because portal left scope
|
||||
vtkm::cont::Token token;
|
||||
|
||||
auto portal1 = array.PrepareForInPlace(Device{}, token);
|
||||
auto portal2 = array.PrepareForInput(Device{}, token);
|
||||
```
|
||||
|
||||
Alternately, you can call `Detach` on the portal, which will invalidate the
|
||||
portal and unlock the `ArrayHandle`.
|
||||
This prevents deadlock during the invocation of a worklet (so long as no
|
||||
intermediate object tries to create its own `Token`, which would be bad
|
||||
practice).
|
||||
|
||||
``` cpp
|
||||
auto portal = array.WritePortal();
|
||||
for (vtkm::Id index = 0; index < portal.GetNumberOfValues(); ++index)
|
||||
{
|
||||
portal.Set(index, /* An interesting value */);
|
||||
}
|
||||
portal.Detach();
|
||||
vtkm::cont::Invoker invoke;
|
||||
invoke(MyWorklet, array); // Runs fine because portal detached
|
||||
```
|
||||
Deadlocks are more likely when actually running multiple threads in the
|
||||
control environment, but still pretty unlikely. One way it can occur is if
|
||||
you have one (or more) worklet that has two output fields. You then try to
|
||||
run the worklet(s) simultaneously on multiple threads. It could be that one
|
||||
thread locks the first output array and the other thread locks the second
|
||||
output array.
|
||||
|
||||
However, having multiple threads trying to write to the same output arrays
|
||||
at the same time without its own coordination is probably a bad idea in itself.
|
||||
|
@ -35,8 +35,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include <vtkm/cont/internal/ArrayHandleExecutionManager.h>
|
||||
#include <vtkm/cont/internal/ArrayPortalCheck.h>
|
||||
#include <vtkm/cont/internal/ArrayPortalFromIterators.h>
|
||||
#include <vtkm/cont/internal/ArrayPortalToken.h>
|
||||
|
||||
namespace vtkm
|
||||
{
|
||||
@ -262,9 +262,9 @@ public:
|
||||
using StorageType = vtkm::cont::internal::Storage<T, StorageTag_>;
|
||||
using ValueType = T;
|
||||
using StorageTag = StorageTag_;
|
||||
using WritePortalType = vtkm::cont::internal::ArrayPortalToken<typename StorageType::PortalType>;
|
||||
using WritePortalType = vtkm::cont::internal::ArrayPortalCheck<typename StorageType::PortalType>;
|
||||
using ReadPortalType =
|
||||
vtkm::cont::internal::ArrayPortalToken<typename StorageType::PortalConstType>;
|
||||
vtkm::cont::internal::ArrayPortalCheck<typename StorageType::PortalConstType>;
|
||||
template <typename DeviceAdapterTag>
|
||||
struct ExecutionTypes
|
||||
{
|
||||
@ -454,6 +454,9 @@ public:
|
||||
this->WaitToWrite(lock, vtkm::cont::Token{});
|
||||
this->ReleaseResourcesExecutionInternal(lock);
|
||||
this->Internals->GetControlArray(lock)->Allocate(numberOfValues);
|
||||
// Set to false and then to true to ensure anything pointing to an array before the allocate
|
||||
// is invalidated.
|
||||
this->Internals->SetControlArrayValid(lock, false);
|
||||
this->Internals->SetControlArrayValid(lock, true);
|
||||
}
|
||||
|
||||
@ -690,7 +693,7 @@ protected:
|
||||
class VTKM_ALWAYS_EXPORT InternalStruct
|
||||
{
|
||||
mutable StorageType ControlArray;
|
||||
mutable bool ControlArrayValid = false;
|
||||
mutable std::shared_ptr<bool> ControlArrayValid;
|
||||
|
||||
mutable std::unique_ptr<ExecutionManagerType> ExecutionArray;
|
||||
mutable bool ExecutionArrayValid = false;
|
||||
@ -711,28 +714,52 @@ protected:
|
||||
InternalStruct(const StorageType& storage);
|
||||
InternalStruct(StorageType&& storage);
|
||||
|
||||
#ifdef VTKM_ASSERTS_CHECKED
|
||||
~InternalStruct()
|
||||
{
|
||||
// It should not be possible to destroy this array if any tokens are still attached to it.
|
||||
LockType lock(this->Mutex);
|
||||
VTKM_ASSERT((*this->GetReadCount(lock) == 0) && (*this->GetWriteCount(lock) == 0));
|
||||
this->SetControlArrayValid(lock, false);
|
||||
}
|
||||
#else
|
||||
~InternalStruct() = default;
|
||||
#endif
|
||||
|
||||
// To access any feature in InternalStruct, you must have locked the mutex. You have
|
||||
// to prove it by passing in a reference to a std::unique_lock.
|
||||
VTKM_CONT bool IsControlArrayValid(const LockType& lock) const
|
||||
{
|
||||
this->CheckLock(lock);
|
||||
return this->ControlArrayValid;
|
||||
if (!this->ControlArrayValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return *this->ControlArrayValid;
|
||||
}
|
||||
}
|
||||
VTKM_CONT void SetControlArrayValid(const LockType& lock, bool value)
|
||||
{
|
||||
this->CheckLock(lock);
|
||||
this->ControlArrayValid = value;
|
||||
if (IsControlArrayValid(lock) == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value) // ControlArrayValid == false or nullptr
|
||||
{
|
||||
// If we are changing the valid flag from false to true, then refresh the pointer.
|
||||
// There may be array portals that already have a reference to the flag. Those portals
|
||||
// will stay in an invalid state whereas new portals will go to a valid state. To
|
||||
// handle both conditions, drop the old reference and create a new one.
|
||||
this->ControlArrayValid.reset(new bool(true));
|
||||
}
|
||||
else // value == false and ControlArrayValid == true
|
||||
{
|
||||
*this->ControlArrayValid = false;
|
||||
}
|
||||
}
|
||||
VTKM_CONT std::shared_ptr<bool> GetControlArrayValidPointer(const LockType& lock) const
|
||||
{
|
||||
this->CheckLock(lock);
|
||||
return this->ControlArrayValid;
|
||||
}
|
||||
VTKM_CONT StorageType* GetControlArray(const LockType& lock) const
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ template <typename T, typename S>
|
||||
ArrayHandle<T, S>::InternalStruct::InternalStruct(
|
||||
const typename ArrayHandle<T, S>::StorageType& storage)
|
||||
: ControlArray(storage)
|
||||
, ControlArrayValid(true)
|
||||
, ControlArrayValid(new bool(true))
|
||||
, ExecutionArrayValid(false)
|
||||
{
|
||||
}
|
||||
@ -29,7 +29,7 @@ ArrayHandle<T, S>::InternalStruct::InternalStruct(
|
||||
template <typename T, typename S>
|
||||
ArrayHandle<T, S>::InternalStruct::InternalStruct(typename ArrayHandle<T, S>::StorageType&& storage)
|
||||
: ControlArray(std::move(storage))
|
||||
, ControlArrayValid(true)
|
||||
, ControlArrayValid(new bool(true))
|
||||
, ExecutionArrayValid(false)
|
||||
{
|
||||
}
|
||||
@ -160,15 +160,15 @@ template <typename T, typename S>
|
||||
typename ArrayHandle<T, S>::ReadPortalType ArrayHandle<T, S>::ReadPortal() const
|
||||
{
|
||||
LockType lock = this->GetLock();
|
||||
{
|
||||
vtkm::cont::Token token;
|
||||
this->WaitToRead(lock, token);
|
||||
}
|
||||
|
||||
this->SyncControlArray(lock);
|
||||
if (this->Internals->IsControlArrayValid(lock))
|
||||
{
|
||||
token.Attach(
|
||||
*this, this->Internals->GetReadCount(lock), lock, &this->Internals->ConditionVariable);
|
||||
return ReadPortalType(std::move(token),
|
||||
return ReadPortalType(this->Internals->GetControlArrayValidPointer(lock),
|
||||
this->Internals->GetControlArray(lock)->GetPortalConst());
|
||||
}
|
||||
else
|
||||
@ -182,8 +182,10 @@ template <typename T, typename S>
|
||||
typename ArrayHandle<T, S>::WritePortalType ArrayHandle<T, S>::WritePortal() const
|
||||
{
|
||||
LockType lock = this->GetLock();
|
||||
{
|
||||
vtkm::cont::Token token;
|
||||
this->WaitToWrite(lock, token);
|
||||
}
|
||||
|
||||
this->SyncControlArray(lock);
|
||||
if (this->Internals->IsControlArrayValid(lock))
|
||||
@ -192,9 +194,8 @@ typename ArrayHandle<T, S>::WritePortalType ArrayHandle<T, S>::WritePortal() con
|
||||
// array will become invalid. Play it safe and release the execution
|
||||
// resources. (Use the const version to preserve the execution array.)
|
||||
this->ReleaseResourcesExecutionInternal(lock);
|
||||
token.Attach(
|
||||
*this, this->Internals->GetWriteCount(lock), lock, &this->Internals->ConditionVariable);
|
||||
return WritePortalType(std::move(token), this->Internals->GetControlArray(lock)->GetPortal());
|
||||
return WritePortalType(this->Internals->GetControlArrayValidPointer(lock),
|
||||
this->Internals->GetControlArray(lock)->GetPortal());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -79,8 +79,9 @@ public:
|
||||
|
||||
VTKM_CONT
|
||||
ArrayHandleConstant(T value, vtkm::Id numberOfValues = 0)
|
||||
: Superclass(
|
||||
typename Superclass::ReadPortalType(internal::ConstantFunctor<T>(value), numberOfValues))
|
||||
: Superclass(typename internal::Storage<T, StorageTag>::PortalConstType(
|
||||
internal::ConstantFunctor<T>(value),
|
||||
numberOfValues))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -126,7 +126,7 @@ public:
|
||||
|
||||
VTKM_CONT
|
||||
ArrayHandleCounting(CountingValueType start, CountingValueType step, vtkm::Id length)
|
||||
: Superclass(typename Superclass::ReadPortalType(start, step, length))
|
||||
: Superclass(internal::ArrayPortalCounting<CountingValueType>(start, step, length))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -501,12 +501,10 @@ public:
|
||||
using ArrayHandleType = ArrayHandle<WordTypeDefault, StorageTagBasic>;
|
||||
|
||||
/// The BitPortal used in the control environment.
|
||||
using WritePortalType = vtkm::cont::internal::ArrayPortalToken<
|
||||
detail::BitPortal<vtkm::cont::internal::AtomicInterfaceControl>>;
|
||||
using WritePortalType = detail::BitPortal<vtkm::cont::internal::AtomicInterfaceControl>;
|
||||
|
||||
/// A read-only BitPortal used in the control environment.
|
||||
using ReadPortalType = vtkm::cont::internal::ArrayPortalToken<
|
||||
detail::BitPortalConst<vtkm::cont::internal::AtomicInterfaceControl>>;
|
||||
using ReadPortalType = detail::BitPortalConst<vtkm::cont::internal::AtomicInterfaceControl>;
|
||||
|
||||
using PortalControl VTKM_DEPRECATED(1.6, "Use BitField::WritePortalType instead.") =
|
||||
detail::BitPortal<vtkm::cont::internal::AtomicInterfaceControl>;
|
||||
@ -638,7 +636,7 @@ public:
|
||||
VTKM_CONT WritePortalType WritePortal() const
|
||||
{
|
||||
auto dataPortal = this->Internals->Data.WritePortal();
|
||||
return WritePortalType{ dataPortal.GetToken(), dataPortal, this->Internals->NumberOfBits };
|
||||
return WritePortalType{ dataPortal, this->Internals->NumberOfBits };
|
||||
}
|
||||
|
||||
/// \brief Get a read-only portal to the data that is usable from the control environment.
|
||||
@ -647,7 +645,7 @@ public:
|
||||
VTKM_CONT ReadPortalType ReadPortal() const
|
||||
{
|
||||
auto dataPortal = this->Internals->Data.ReadPortal();
|
||||
return ReadPortalType{ dataPortal.GetToken(), dataPortal, this->Internals->NumberOfBits };
|
||||
return ReadPortalType{ dataPortal, this->Internals->NumberOfBits };
|
||||
}
|
||||
|
||||
VTKM_CONT
|
||||
|
@ -53,6 +53,8 @@ ArrayHandleImpl::InternalStruct::~InternalStruct()
|
||||
this->ExecutionInterface->Free(execArray);
|
||||
}
|
||||
|
||||
this->SetControlArrayValid(lock, false);
|
||||
|
||||
delete this->ControlArray;
|
||||
delete this->ExecutionInterface;
|
||||
}
|
||||
@ -98,6 +100,9 @@ void ArrayHandleImpl::Allocate(LockType& lock, vtkm::Id numberOfValues, vtkm::UI
|
||||
this->WaitToWrite(lock, vtkm::cont::Token{});
|
||||
this->ReleaseResourcesExecutionInternal(lock);
|
||||
this->Internals->GetControlArray(lock)->AllocateValues(numberOfValues, sizeOfT);
|
||||
// Set to false and then to true to ensure anything pointing to an array before the allocate
|
||||
// is invalidated.
|
||||
this->Internals->SetControlArrayValid(lock, false);
|
||||
this->Internals->SetControlArrayValid(lock, true);
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,7 @@ struct VTKM_CONT_EXPORT ArrayHandleImpl
|
||||
|
||||
class VTKM_CONT_EXPORT InternalStruct
|
||||
{
|
||||
mutable bool ControlArrayValid;
|
||||
mutable std::shared_ptr<bool> ControlArrayValid;
|
||||
StorageBasicBase* ControlArray;
|
||||
|
||||
mutable ExecutionArrayInterfaceBasicBase* ExecutionInterface = nullptr;
|
||||
@ -226,7 +226,7 @@ struct VTKM_CONT_EXPORT ArrayHandleImpl
|
||||
|
||||
template <typename T>
|
||||
VTKM_CONT explicit InternalStruct(T)
|
||||
: ControlArrayValid(false)
|
||||
: ControlArrayValid(new bool(false))
|
||||
, ControlArray(new vtkm::cont::internal::Storage<T, vtkm::cont::StorageTagBasic>())
|
||||
{
|
||||
}
|
||||
@ -234,7 +234,7 @@ struct VTKM_CONT_EXPORT ArrayHandleImpl
|
||||
template <typename T>
|
||||
VTKM_CONT explicit InternalStruct(
|
||||
const vtkm::cont::internal::Storage<T, vtkm::cont::StorageTagBasic>& storage)
|
||||
: ControlArrayValid(true)
|
||||
: ControlArrayValid(new bool(true))
|
||||
, ControlArray(new vtkm::cont::internal::Storage<T, vtkm::cont::StorageTagBasic>(storage))
|
||||
{
|
||||
}
|
||||
@ -242,7 +242,7 @@ struct VTKM_CONT_EXPORT ArrayHandleImpl
|
||||
VTKM_CONT
|
||||
template <typename T>
|
||||
explicit InternalStruct(vtkm::cont::internal::Storage<T, vtkm::cont::StorageTagBasic>&& storage)
|
||||
: ControlArrayValid(true)
|
||||
: ControlArrayValid(new bool(true))
|
||||
, ControlArray(
|
||||
new vtkm::cont::internal::Storage<T, vtkm::cont::StorageTagBasic>(std::move(storage)))
|
||||
{
|
||||
@ -255,12 +255,25 @@ struct VTKM_CONT_EXPORT ArrayHandleImpl
|
||||
VTKM_CONT bool IsControlArrayValid(const LockType& lock) const
|
||||
{
|
||||
this->CheckLock(lock);
|
||||
return this->ControlArrayValid;
|
||||
return *this->ControlArrayValid;
|
||||
}
|
||||
VTKM_CONT void SetControlArrayValid(const LockType& lock, bool value)
|
||||
{
|
||||
this->CheckLock(lock);
|
||||
this->ControlArrayValid = value;
|
||||
if (!IsControlArrayValid(lock) && value)
|
||||
{
|
||||
// If we are changing the valid flag from false to true, then refresh the pointer.
|
||||
// There may be array portals that already have a reference to the flag. Those portals
|
||||
// will stay in an invalid state whereas new portals will go to a valid state. To
|
||||
// handle both conditions, drop the old reference and create a new one.
|
||||
this->ControlArrayValid.reset(new bool);
|
||||
}
|
||||
*this->ControlArrayValid = value;
|
||||
}
|
||||
VTKM_CONT std::shared_ptr<bool> GetControlArrayValidPointer(const LockType& lock) const
|
||||
{
|
||||
this->CheckLock(lock);
|
||||
return this->ControlArrayValid;
|
||||
}
|
||||
VTKM_CONT StorageBasicBase* GetControlArray(const LockType& lock) const
|
||||
{
|
||||
@ -347,9 +360,9 @@ public:
|
||||
using StorageTag = ::vtkm::cont::StorageTagBasic;
|
||||
using StorageType = vtkm::cont::internal::Storage<T, StorageTag>;
|
||||
using ValueType = T;
|
||||
using WritePortalType = vtkm::cont::internal::ArrayPortalToken<typename StorageType::PortalType>;
|
||||
using WritePortalType = vtkm::cont::internal::ArrayPortalCheck<typename StorageType::PortalType>;
|
||||
using ReadPortalType =
|
||||
vtkm::cont::internal::ArrayPortalToken<typename StorageType::PortalConstType>;
|
||||
vtkm::cont::internal::ArrayPortalCheck<typename StorageType::PortalConstType>;
|
||||
|
||||
using PortalControl VTKM_DEPRECATED(1.6, "Use ArrayHandle::WritePortalType instead.") =
|
||||
typename StorageType::PortalType;
|
||||
|
@ -157,8 +157,10 @@ typename ArrayHandle<T, StorageTagBasic>::ReadPortalType
|
||||
ArrayHandle<T, StorageTagBasic>::ReadPortal() const
|
||||
{
|
||||
LockType lock = this->GetLock();
|
||||
{
|
||||
vtkm::cont::Token token;
|
||||
this->Internals->WaitToRead(lock, token);
|
||||
}
|
||||
this->SyncControlArray(lock);
|
||||
this->Internals->CheckControlArrayValid(lock);
|
||||
//CheckControlArrayValid will throw an exception if this->Internals->ControlArrayValid
|
||||
@ -166,12 +168,8 @@ ArrayHandle<T, StorageTagBasic>::ReadPortal() const
|
||||
|
||||
StorageType* privStorage =
|
||||
static_cast<StorageType*>(this->Internals->Internals->GetControlArray(lock));
|
||||
token.Attach(this->Internals,
|
||||
this->Internals->Internals->GetReadCount(lock),
|
||||
lock,
|
||||
&this->Internals->Internals->ConditionVariable);
|
||||
return ArrayHandle<T, StorageTagBasic>::ReadPortalType(std::move(token),
|
||||
privStorage->GetPortalConst());
|
||||
return ArrayHandle<T, StorageTagBasic>::ReadPortalType(
|
||||
this->Internals->Internals->GetControlArrayValidPointer(lock), privStorage->GetPortalConst());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -179,8 +177,10 @@ typename ArrayHandle<T, StorageTagBasic>::WritePortalType
|
||||
ArrayHandle<T, StorageTagBasic>::WritePortal() const
|
||||
{
|
||||
LockType lock = this->GetLock();
|
||||
{
|
||||
vtkm::cont::Token token;
|
||||
this->Internals->WaitToWrite(lock, token);
|
||||
}
|
||||
this->SyncControlArray(lock);
|
||||
this->Internals->CheckControlArrayValid(lock);
|
||||
//CheckControlArrayValid will throw an exception if this->Internals->ControlArrayValid
|
||||
@ -192,12 +192,8 @@ ArrayHandle<T, StorageTagBasic>::WritePortal() const
|
||||
this->ReleaseResourcesExecutionInternal(lock);
|
||||
StorageType* privStorage =
|
||||
static_cast<StorageType*>(this->Internals->Internals->GetControlArray(lock));
|
||||
token.Attach(this->Internals,
|
||||
this->Internals->Internals->GetWriteCount(lock),
|
||||
lock,
|
||||
&this->Internals->Internals->ConditionVariable);
|
||||
return ArrayHandle<T, StorageTagBasic>::WritePortalType(std::move(token),
|
||||
privStorage->GetPortal());
|
||||
return ArrayHandle<T, StorageTagBasic>::WritePortalType(
|
||||
this->Internals->Internals->GetControlArrayValidPointer(lock), privStorage->GetPortal());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
129
vtkm/cont/internal/ArrayPortalCheck.h
Normal file
129
vtkm/cont/internal/ArrayPortalCheck.h
Normal file
@ -0,0 +1,129 @@
|
||||
//============================================================================
|
||||
// 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.
|
||||
//============================================================================
|
||||
#ifndef vtk_m_cont_internal_ArrayPortalCheck_h
|
||||
#define vtk_m_cont_internal_ArrayPortalCheck_h
|
||||
|
||||
#include <vtkm/cont/ArrayPortal.h>
|
||||
#include <vtkm/cont/Logging.h>
|
||||
|
||||
#include <vtkm/internal/ArrayPortalHelpers.h>
|
||||
|
||||
namespace vtkm
|
||||
{
|
||||
namespace cont
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
|
||||
/// \brief A wrapper around an `ArrayPortal` that checks to status from the originating array.
|
||||
///
|
||||
/// After you get an `ArrayPortal` from an `ArrayHandle`, there is a possibility that the
|
||||
/// `ArrayHandle` could change in a way that would invalidate the `ArrayPortal` (for example
|
||||
/// freeing the memory). You could lock the `ArrayHandle`, but that encourages deadlocks.
|
||||
/// Instead, this `ArrayPortal` keeps a reference to a flag from the `ArrayHandle` that tells
|
||||
/// whether the memory referenced by the `ArrayPortal is still valid.
|
||||
///
|
||||
template <typename PortalType_>
|
||||
class VTKM_ALWAYS_EXPORT ArrayPortalCheck : public PortalType_
|
||||
{
|
||||
std::shared_ptr<bool> Valid;
|
||||
|
||||
using Superclass = PortalType_;
|
||||
|
||||
public:
|
||||
template <typename... PortalArgs>
|
||||
VTKM_CONT ArrayPortalCheck(const std::shared_ptr<bool>& valid, PortalArgs&&... args)
|
||||
: Superclass(std::forward<PortalArgs>(args)...)
|
||||
, Valid(valid)
|
||||
{
|
||||
}
|
||||
|
||||
VTKM_CONT ArrayPortalCheck()
|
||||
: Valid(new bool)
|
||||
{
|
||||
*this->Valid = false;
|
||||
}
|
||||
|
||||
// Even though these do not do anything the default implementation does not, they are defined so
|
||||
// that the CUDA compiler does not try to compile it for devices and then fail because the
|
||||
// std::shared_ptr does not work on CUDA devices.
|
||||
VTKM_CONT ArrayPortalCheck(const ArrayPortalCheck& src)
|
||||
: Superclass(src)
|
||||
, Valid(src.Valid)
|
||||
{
|
||||
}
|
||||
VTKM_CONT ArrayPortalCheck(ArrayPortalCheck&& rhs)
|
||||
: Superclass(std::move(static_cast<Superclass&&>(rhs)))
|
||||
, Valid(std::move(rhs.Valid))
|
||||
{
|
||||
}
|
||||
VTKM_CONT ArrayPortalCheck& operator=(const ArrayPortalCheck& src)
|
||||
{
|
||||
this->Superclass::operator=(src);
|
||||
this->Valid = src.Valid;
|
||||
return *this;
|
||||
}
|
||||
VTKM_CONT ArrayPortalCheck& operator=(ArrayPortalCheck&& rhs)
|
||||
{
|
||||
this->Superclass::operator=(std::move(static_cast<Superclass&&>(rhs)));
|
||||
this->Valid = std::move(rhs.Valid);
|
||||
return *this;
|
||||
}
|
||||
VTKM_CONT ~ArrayPortalCheck() {}
|
||||
|
||||
// The Get and Set methods are marked for execution environment even though they won't
|
||||
// work there. This is so that this class can be used in classes that work in both
|
||||
// control and execution environments without having to suppress warnings in them all.
|
||||
|
||||
VTKM_SUPPRESS_EXEC_WARNINGS
|
||||
template <typename PT = Superclass,
|
||||
typename std::enable_if<vtkm::internal::PortalSupportsGets<PT>::value, int>::type = 0>
|
||||
VTKM_EXEC_CONT typename Superclass::ValueType Get(vtkm::Id index) const
|
||||
{
|
||||
if (!(*this->Valid))
|
||||
{
|
||||
VTKM_LOG_F(vtkm::cont::LogLevel::Fatal,
|
||||
"Attempted to read from an ArrayPortal whose data has been deleted.");
|
||||
return typename Superclass::ValueType{};
|
||||
}
|
||||
// Technically, this is not perfectly thread safe. It is possible that the check above
|
||||
// passed and then another thread has deleted the underlying array before we got here.
|
||||
// However, any case in which this portal is used where such a condition can possible
|
||||
// happen is a grevious error. In which case a different run is likely to give the
|
||||
// correct error and the problem can be fixed.
|
||||
return this->Superclass::Get(index);
|
||||
}
|
||||
|
||||
VTKM_SUPPRESS_EXEC_WARNINGS
|
||||
template <typename PT = Superclass,
|
||||
typename std::enable_if<vtkm::internal::PortalSupportsSets<PT>::value, int>::type = 0>
|
||||
VTKM_EXEC_CONT void Set(vtkm::Id index, typename Superclass::ValueType value) const
|
||||
{
|
||||
if (!(*this->Valid))
|
||||
{
|
||||
VTKM_LOG_F(vtkm::cont::LogLevel::Fatal,
|
||||
"Attempted to write to an ArrayPortal whose data has been deleted.");
|
||||
return;
|
||||
}
|
||||
// Technically, this is not perfectly thread safe. It is possible that the check above
|
||||
// passed and then another thread has deleted the underlying array before we got here.
|
||||
// However, any case in which this portal is used where such a condition can possible
|
||||
// happen is a grevious error. In which case a different run is likely to give the
|
||||
// correct error and the problem can be fixed.
|
||||
this->Superclass::Set(index, value);
|
||||
}
|
||||
|
||||
VTKM_CONT std::shared_ptr<bool> GetValidPointer() const { return this->Valid; }
|
||||
};
|
||||
}
|
||||
}
|
||||
} // namespace vtkm::cont::internal
|
||||
|
||||
#endif //vtk_m_cont_internal_ArrayPortalCheck_h
|
@ -1,114 +0,0 @@
|
||||
//============================================================================
|
||||
// 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.
|
||||
//============================================================================
|
||||
#ifndef vtk_m_cont_internal_ArrayPortalToken_h
|
||||
#define vtk_m_cont_internal_ArrayPortalToken_h
|
||||
|
||||
#include <vtkm/cont/ArrayPortal.h>
|
||||
#include <vtkm/cont/Token.h>
|
||||
|
||||
#include <vtkm/internal/ArrayPortalHelpers.h>
|
||||
|
||||
namespace vtkm
|
||||
{
|
||||
namespace cont
|
||||
{
|
||||
namespace internal
|
||||
{
|
||||
|
||||
/// \brief A wrapper around an `ArrayPortal` that also holds its state with a token.
|
||||
///
|
||||
/// Usually when you get an `ArrayPortal`, you have to associate it with a `Token`
|
||||
/// object to define its scope. This class wraps around an `ArrayPortal` and also
|
||||
/// holds its own `Token` that should be attached appropriately to the source
|
||||
/// `ArrayHandle`. When this class goes out of scope, so will its `Token`.
|
||||
///
|
||||
/// Because `Token`s only work in the control environment, so it is for this class.
|
||||
///
|
||||
template <typename PortalType_>
|
||||
class VTKM_ALWAYS_EXPORT ArrayPortalToken : public PortalType_
|
||||
{
|
||||
std::shared_ptr<vtkm::cont::Token> Token;
|
||||
|
||||
using Superclass = PortalType_;
|
||||
|
||||
public:
|
||||
template <typename... PortalArgs>
|
||||
VTKM_CONT ArrayPortalToken(vtkm::cont::Token&& token, PortalArgs&&... args)
|
||||
: Superclass(std::forward<PortalArgs>(args)...)
|
||||
, Token(new vtkm::cont::Token(std::move(token)))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... PortalArgs>
|
||||
VTKM_CONT ArrayPortalToken(std::shared_ptr<vtkm::cont::Token> token, PortalArgs&&... args)
|
||||
: Superclass(std::forward<PortalArgs>(args)...)
|
||||
, Token(token)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... PortalArgs>
|
||||
VTKM_CONT ArrayPortalToken(PortalArgs&&... args)
|
||||
: Superclass(std::forward<PortalArgs>(args)...)
|
||||
, Token(new vtkm::cont::Token)
|
||||
{
|
||||
}
|
||||
|
||||
// Even though these do not do anything the default implementation does not, they are defined so
|
||||
// that the CUDA compiler does not try to compile it for devices and then fail because the
|
||||
// std::shared_ptr does not work on CUDA devices.
|
||||
VTKM_CONT ArrayPortalToken(const ArrayPortalToken& src)
|
||||
: Superclass(src)
|
||||
, Token(src.Token)
|
||||
{
|
||||
}
|
||||
VTKM_CONT ArrayPortalToken(ArrayPortalToken&& rhs)
|
||||
: Superclass(std::move(static_cast<Superclass&&>(rhs)))
|
||||
, Token(std::move(rhs.Token))
|
||||
{
|
||||
}
|
||||
VTKM_CONT ArrayPortalToken& operator=(const ArrayPortalToken& src)
|
||||
{
|
||||
this->Superclass::operator=(src);
|
||||
this->Token = src.Token;
|
||||
return *this;
|
||||
}
|
||||
VTKM_CONT ArrayPortalToken& operator=(ArrayPortalToken&& rhs)
|
||||
{
|
||||
this->Superclass::operator=(std::move(static_cast<Superclass&&>(rhs)));
|
||||
this->Token = std::move(rhs.Token);
|
||||
return *this;
|
||||
}
|
||||
VTKM_CONT ~ArrayPortalToken() {}
|
||||
|
||||
/// \brief Detach this portal from the `ArrayHandle`.
|
||||
///
|
||||
/// This will open up the `ArrayHandle` for reading and/or writing.
|
||||
///
|
||||
VTKM_CONT void Detach()
|
||||
{
|
||||
this->Token->DetachFromAll();
|
||||
|
||||
// Reset this portal in case the superclass is holding other array portals with their own
|
||||
// tokens. Detach is supposed to invalidate the array portal, so it is OK to do this.
|
||||
*this = ArrayPortalToken<PortalType_>();
|
||||
}
|
||||
|
||||
/// \brief Get the `Token` of the `ArrayPortal`.
|
||||
///
|
||||
/// You can keep a copy of this shared pointer to keep the scope around longer than this
|
||||
/// object exists (unless, of course, the `Token` is explicitly detached).
|
||||
///
|
||||
VTKM_CONT std::shared_ptr<vtkm::cont::Token> GetToken() const { return this->Token; }
|
||||
};
|
||||
}
|
||||
}
|
||||
} // namespace vtkm::cont::internal
|
||||
|
||||
#endif //vtk_m_cont_internal_ArrayPortalToken_h
|
@ -15,8 +15,8 @@ set(headers
|
||||
ArrayHandleExecutionManager.h
|
||||
ArrayManagerExecution.h
|
||||
ArrayManagerExecutionShareWithControl.h
|
||||
ArrayPortalCheck.h
|
||||
ArrayPortalFromIterators.h
|
||||
ArrayPortalToken.h
|
||||
ArrayTransfer.h
|
||||
AtomicInterfaceControl.h
|
||||
AtomicInterfaceExecution.h
|
||||
|
@ -2704,7 +2704,6 @@ private:
|
||||
portal.SetWord(2, 0x00100000ul);
|
||||
portal.SetWord(8, 0x00100010ul);
|
||||
portal.SetWord(11, 0x10000000ul);
|
||||
portal.Detach();
|
||||
testIndexArray(bits);
|
||||
}
|
||||
}
|
||||
@ -2795,7 +2794,6 @@ private:
|
||||
portal.SetWord(2, 0x00100000ul);
|
||||
portal.SetWord(8, 0x00100010ul);
|
||||
portal.SetWord(11, 0x10000000ul);
|
||||
portal.Detach();
|
||||
verifyPopCount(bits);
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,9 @@ struct TemplatedTests
|
||||
|
||||
using ArrayHandleType2 = vtkm::cont::ArrayHandle<ValueType, vtkm::cont::StorageTagCounting>;
|
||||
|
||||
using PortalType = typename ArrayHandleType::ReadPortalType;
|
||||
using PortalType =
|
||||
typename vtkm::cont::internal::Storage<ValueType,
|
||||
typename ArrayHandleType::StorageTag>::PortalConstType;
|
||||
|
||||
void operator()(const ValueType& startingValue, const ValueType& step)
|
||||
{
|
||||
|
@ -180,12 +180,46 @@ void ThreadsDecrementArray(vtkm::cont::ArrayHandle<ValueType, Storage> array)
|
||||
CheckPortal(array.ReadPortal());
|
||||
}
|
||||
|
||||
template <typename Storage>
|
||||
void InvalidateControlPortal(vtkm::cont::ArrayHandle<ValueType, Storage> array)
|
||||
{
|
||||
std::cout << " Starting invalidate control portal" << std::endl;
|
||||
auto writePortal = array.WritePortal();
|
||||
|
||||
{
|
||||
// PrepareForInPlace should invalidate the local control portal. It should work, but
|
||||
// further use of writePortal will be invalid.
|
||||
vtkm::cont::Token token;
|
||||
array.PrepareForInPlace(vtkm::cont::DeviceAdapterTagSerial{}, token);
|
||||
|
||||
#if 0
|
||||
std::cout << " This should result in an error in the log and possibly a crash." << std::endl;
|
||||
// Unfortunately, there is no way to check this other than visual inspection or outright crash
|
||||
writePortal.Get(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto readPortal = array.ReadPortal();
|
||||
|
||||
{
|
||||
vtkm::cont::Token token;
|
||||
array.PrepareForInPlace(vtkm::cont::DeviceAdapterTagSerial{}, token);
|
||||
|
||||
#if 0
|
||||
std::cout << " This should result in an error in the log and possibly a crash." << std::endl;
|
||||
// Unfortunately, there is no way to check this other than visual inspection or outright crash
|
||||
readPortal.Get(0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Storage>
|
||||
void DoThreadSafetyTest(vtkm::cont::ArrayHandle<ValueType, Storage> array)
|
||||
{
|
||||
ThreadsIncrementToArray(array);
|
||||
ThreadsCheckArray(array);
|
||||
ThreadsDecrementArray(array);
|
||||
InvalidateControlPortal(array);
|
||||
}
|
||||
|
||||
void DoTest()
|
||||
|
@ -217,15 +217,6 @@ struct Test
|
||||
// the concreteArray getting locked up.
|
||||
CheckPortal(virtualArray.ReadPortal());
|
||||
SetPortal(concreteArray.WritePortal());
|
||||
|
||||
// Make sure Detach also correctly unlocks the concrete array.
|
||||
auto readPortal = virtualArray.ReadPortal();
|
||||
readPortal.Detach();
|
||||
concreteArray.WritePortal();
|
||||
|
||||
auto writePortal = virtualArray.WritePortal();
|
||||
writePortal.Detach();
|
||||
concreteArray.WritePortal();
|
||||
}
|
||||
|
||||
void operator()()
|
||||
|
@ -59,7 +59,6 @@ vtkm::cont::DataSet MakeTestUniformDataSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
velocityPortal.Detach();
|
||||
dsf.AddPointField(dataset, "velocity", velocityField);
|
||||
return dataset;
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ void TestScatterPermutation()
|
||||
portal.Set(j, val);
|
||||
}
|
||||
std::cout << "\n";
|
||||
portal.Detach();
|
||||
|
||||
RunTest(cellset, permutation);
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ void Test1D(int rate)
|
||||
{
|
||||
hPortal.Set(i, static_cast<Scalar>(fPortal.Get(i)));
|
||||
}
|
||||
hPortal.Detach();
|
||||
|
||||
auto compressed = compressor.Compress(handle, rate, dims);
|
||||
//writeArray(compressed, "output.zfp");
|
||||
@ -104,7 +103,6 @@ void Test2D(int rate)
|
||||
{
|
||||
hPortal.Set(i, static_cast<Scalar>(fPortal.Get(i)));
|
||||
}
|
||||
hPortal.Detach();
|
||||
|
||||
auto compressed = compressor.Compress(handle, rate, dims);
|
||||
vtkm::cont::ArrayHandle<Scalar> decoded;
|
||||
@ -145,7 +143,6 @@ void Test3D(int rate)
|
||||
{
|
||||
hPortal.Set(i, static_cast<Scalar>(fPortal.Get(i)));
|
||||
}
|
||||
hPortal.Detach();
|
||||
|
||||
auto compressed = compressor.Compress(handle, rate, dims);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user