//============================================================================ // 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 #include #include #include #include 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 lock(*this->Mutex); (*this->ReferenceCount)++; } ~TestObject() { std::unique_lock 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 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 500 milliseconds for thread to lock" << std::endl; // 500 milliseconds 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::milliseconds(500)); 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); }