vtk-m/vtkm/cont/testing/UnitTestToken.cxx
Kenneth Moreland 14c3d90ac3 Fix race condition in UnitTestToken
Apparently when future::get returns, it is not the case that all
resources of the future are cleaned up (only that the calling function
has returned). Do not rely on this resource cleanup for the test to
pass.
2020-02-26 13:10:48 -07:00

185 lines
5.5 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 <vtkm/cont/testing/Testing.h>
#include <chrono>
#include <future>
#include <thread>
namespace
{
struct TestObject
{
vtkm::cont::Token::ReferenceCount* TokenCount;
std::mutex* Mutex;
std::condition_variable* ConditionVariable;
vtkm::Id* ReferenceCount;
TestObject()
: TokenCount(new vtkm::cont::Token::ReferenceCount(0))
, Mutex(new std::mutex)
, ConditionVariable(new std::condition_variable)
, ReferenceCount(new vtkm::Id(1))
{
}
TestObject(const TestObject& src)
: TokenCount(src.TokenCount)
, Mutex(src.Mutex)
, ConditionVariable(src.ConditionVariable)
, ReferenceCount(src.ReferenceCount)
{
std::unique_lock<std::mutex> lock(*this->Mutex);
(*this->ReferenceCount)++;
}
~TestObject()
{
std::unique_lock<std::mutex> lock(*this->Mutex);
(*this->ReferenceCount)--;
if (*this->ReferenceCount == 0)
{
lock.unlock();
delete this->TokenCount;
delete this->Mutex;
delete this->ConditionVariable;
delete this->ReferenceCount;
}
}
void Attach(vtkm::cont::Token& token)
{
token.Attach(*this, this->TokenCount, this->Mutex, this->ConditionVariable);
}
};
#define CHECK_OBJECT(object, expectedTokens, expectedRefs) \
VTKM_TEST_ASSERT(*(object).TokenCount == (expectedTokens), \
"Expected object to have token count of ", \
(expectedTokens), \
". It actually was ", \
*(object).TokenCount); \
VTKM_TEST_ASSERT(*(object).ReferenceCount == (expectedRefs), \
"Expected object to have reference count of ", \
(expectedRefs), \
". It actually was ", \
*(object).ReferenceCount)
void TestBasicAttachDetatch()
{
std::cout << "Test basic attach detach." << std::endl;
std::cout << " Create objects" << std::endl;
TestObject object1;
TestObject object2;
TestObject object3;
CHECK_OBJECT(object1, 0, 1);
CHECK_OBJECT(object2, 0, 1);
CHECK_OBJECT(object3, 0, 1);
std::cout << " Create outer token" << std::endl;
vtkm::cont::Token outerToken;
std::cout << " Attach outer token" << std::endl;
object1.Attach(outerToken);
object2.Attach(outerToken);
object3.Attach(outerToken);
CHECK_OBJECT(object1, 1, 2);
CHECK_OBJECT(object2, 1, 2);
CHECK_OBJECT(object3, 1, 2);
{
std::cout << " Create/Attach inner token" << std::endl;
vtkm::cont::Token innerToken;
object1.Attach(innerToken);
object2.Attach(innerToken);
object3.Attach(innerToken);
CHECK_OBJECT(object1, 2, 3);
CHECK_OBJECT(object2, 2, 3);
CHECK_OBJECT(object3, 2, 3);
std::cout << " Recursively attach outer token" << std::endl;
object1.Attach(outerToken);
CHECK_OBJECT(object1, 2, 3);
CHECK_OBJECT(object2, 2, 3);
CHECK_OBJECT(object3, 2, 3);
std::cout << " Detach from inner token (through scoping)" << std::endl;
}
CHECK_OBJECT(object1, 1, 2);
CHECK_OBJECT(object2, 1, 2);
CHECK_OBJECT(object3, 1, 2);
std::cout << " Detatch outer token" << std::endl;
outerToken.DetachFromAll();
CHECK_OBJECT(object1, 0, 1);
CHECK_OBJECT(object2, 0, 1);
CHECK_OBJECT(object3, 0, 1);
}
void WaitForDetachment(TestObject* object)
{
std::unique_lock<std::mutex> lock(*object->Mutex);
object->ConditionVariable->wait(lock, [object] { return *object->TokenCount < 1; });
std::cout << " Thread woke up" << std::endl;
}
void TestThreadWake()
{
std::cout << "Testing thread wakeup" << std::endl;
TestObject object;
CHECK_OBJECT(object, 0, 1);
vtkm::cont::Token token;
object.Attach(token);
CHECK_OBJECT(object, 1, 2);
std::cout << " Launching coordinated thread" << std::endl;
auto future = std::async(std::launch::async, WaitForDetachment, &object);
std::cout << " Sleep 2 seconds for thread to lock" << std::endl;
// 2 seconds should be ample time for the spawned thread to launch. If the systems busy then
// we might actually unlock the object before the thread gets there, but hopefully on some
// systems it will test correctly.
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << " Main thread woke up. Detach token." << std::endl;
token.DetachFromAll();
std::cout << " Wait for thread to finish. Could deadlock if did not properly wake." << std::endl;
future.get();
std::cout << " Returned to main thread" << std::endl;
CHECK_OBJECT(object, 0, 1);
}
void DoTest()
{
TestBasicAttachDetatch();
TestThreadWake();
}
} // anonymous namespace
int UnitTestToken(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(DoTest, argc, argv);
}