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:
Kenneth Moreland 2020-03-16 21:52:24 +00:00 committed by Kitware Robot
commit d0d1f750c2
19 changed files with 295 additions and 229 deletions

@ -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);
{
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);
{
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);
{
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);
{
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>

@ -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);