151c0b2f7Stbbdev /* 251c0b2f7Stbbdev Copyright (c) 2005-2020 Intel Corporation 351c0b2f7Stbbdev 451c0b2f7Stbbdev Licensed under the Apache License, Version 2.0 (the "License"); 551c0b2f7Stbbdev you may not use this file except in compliance with the License. 651c0b2f7Stbbdev You may obtain a copy of the License at 751c0b2f7Stbbdev 851c0b2f7Stbbdev http://www.apache.org/licenses/LICENSE-2.0 951c0b2f7Stbbdev 1051c0b2f7Stbbdev Unless required by applicable law or agreed to in writing, software 1151c0b2f7Stbbdev distributed under the License is distributed on an "AS IS" BASIS, 1251c0b2f7Stbbdev WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1351c0b2f7Stbbdev See the License for the specific language governing permissions and 1451c0b2f7Stbbdev limitations under the License. 1551c0b2f7Stbbdev */ 1651c0b2f7Stbbdev 1751c0b2f7Stbbdev #define __TBB_NO_IMPLICIT_LINKAGE 1 1851c0b2f7Stbbdev 1951c0b2f7Stbbdev #include "common/test.h" 2051c0b2f7Stbbdev #include "common/utils.h" 2151c0b2f7Stbbdev #include "common/spin_barrier.h" 22*49e08aacStbbdev #include "oneapi/tbb/detail/_utils.h" 2351c0b2f7Stbbdev #include "tbb/scalable_allocator.h" 2451c0b2f7Stbbdev #include <thread> 2551c0b2f7Stbbdev 2651c0b2f7Stbbdev static constexpr std::size_t MaxTasks = 16; 2751c0b2f7Stbbdev std::atomic<std::size_t> FinishedTasks; 2851c0b2f7Stbbdev 2951c0b2f7Stbbdev static constexpr std::size_t MaxThread = 4; 3051c0b2f7Stbbdev 3151c0b2f7Stbbdev /*--------------------------------------------------------------------*/ 3251c0b2f7Stbbdev // The regression test against a bug triggered when malloc initialization 3351c0b2f7Stbbdev // and thread shutdown were called simultaneously, in which case 3451c0b2f7Stbbdev // Windows dynamic loader lock and allocator initialization/termination lock 3551c0b2f7Stbbdev // were taken in different order. 3651c0b2f7Stbbdev 3751c0b2f7Stbbdev 3851c0b2f7Stbbdev 3951c0b2f7Stbbdev class TestFunc1 { 4051c0b2f7Stbbdev utils::SpinBarrier* my_barr; 4151c0b2f7Stbbdev public: 4251c0b2f7Stbbdev TestFunc1 (utils::SpinBarrier& barr) : my_barr(&barr) {} 4351c0b2f7Stbbdev void operator() (bool do_malloc) const { 4451c0b2f7Stbbdev my_barr->wait(); 4551c0b2f7Stbbdev if (do_malloc) scalable_malloc(10); 4651c0b2f7Stbbdev ++FinishedTasks; 4751c0b2f7Stbbdev } 4851c0b2f7Stbbdev }; 4951c0b2f7Stbbdev 5051c0b2f7Stbbdev void Test1 () { 5151c0b2f7Stbbdev std::size_t NTasks = utils::min(MaxTasks, utils::max(std::size_t(2), MaxThread)); 5251c0b2f7Stbbdev utils::SpinBarrier barr(NTasks); 5351c0b2f7Stbbdev TestFunc1 tf(barr); 5451c0b2f7Stbbdev FinishedTasks = 0; 5551c0b2f7Stbbdev 5651c0b2f7Stbbdev utils::NativeParallelFor(NTasks, [&] (std::size_t thread_idx) { 5751c0b2f7Stbbdev tf(thread_idx % 2 == 0); 5851c0b2f7Stbbdev utils::Sleep(1000); // wait a second :) 5951c0b2f7Stbbdev REQUIRE_MESSAGE(FinishedTasks == NTasks, "Some threads appear to deadlock" ); 6051c0b2f7Stbbdev }); 6151c0b2f7Stbbdev } 6251c0b2f7Stbbdev 6351c0b2f7Stbbdev /*--------------------------------------------------------------------*/ 6451c0b2f7Stbbdev // The regression test against a bug when cross-thread deallocation 6551c0b2f7Stbbdev // caused livelock at thread shutdown. 6651c0b2f7Stbbdev 6751c0b2f7Stbbdev std::atomic<void*> gPtr(nullptr); 6851c0b2f7Stbbdev 6951c0b2f7Stbbdev class TestFunc2a { 7051c0b2f7Stbbdev utils::SpinBarrier* my_barr; 7151c0b2f7Stbbdev public: 7251c0b2f7Stbbdev TestFunc2a (utils::SpinBarrier& barr) : my_barr(&barr) {} 7351c0b2f7Stbbdev void operator() (std::size_t) const { 7451c0b2f7Stbbdev gPtr = scalable_malloc(8); 7551c0b2f7Stbbdev my_barr->wait(); 7651c0b2f7Stbbdev ++FinishedTasks; 7751c0b2f7Stbbdev } 7851c0b2f7Stbbdev }; 7951c0b2f7Stbbdev 8051c0b2f7Stbbdev class TestFunc2b { 8151c0b2f7Stbbdev utils::SpinBarrier* my_barr; 8251c0b2f7Stbbdev std::thread& my_ward; 8351c0b2f7Stbbdev public: 8451c0b2f7Stbbdev TestFunc2b (utils::SpinBarrier& barr, std::thread& t) : my_barr(&barr), my_ward(t) {} 8551c0b2f7Stbbdev void operator() (std::size_t) const { 8651c0b2f7Stbbdev utils::SpinWaitWhileEq(gPtr, (void*)nullptr); 8751c0b2f7Stbbdev scalable_free(gPtr); 8851c0b2f7Stbbdev my_barr->wait(); 8951c0b2f7Stbbdev my_ward.join(); 9051c0b2f7Stbbdev ++FinishedTasks; 9151c0b2f7Stbbdev } 9251c0b2f7Stbbdev }; 9351c0b2f7Stbbdev void Test2() { 9451c0b2f7Stbbdev utils::SpinBarrier barr(2); 9551c0b2f7Stbbdev TestFunc2a func2a(barr); 9651c0b2f7Stbbdev std::thread t2a; 9751c0b2f7Stbbdev TestFunc2b func2b(barr, t2a); 9851c0b2f7Stbbdev FinishedTasks = 0; 9951c0b2f7Stbbdev t2a = std::thread(func2a, std::size_t(0)); 10051c0b2f7Stbbdev std::thread t2b(func2b, std::size_t(1)); 10151c0b2f7Stbbdev utils::Sleep(1000); // wait a second :) 10251c0b2f7Stbbdev REQUIRE_MESSAGE( FinishedTasks==2, "Threads appear to deadlock" ); 10351c0b2f7Stbbdev 10451c0b2f7Stbbdev t2b.join(); // t2a is monitored by t2b 10551c0b2f7Stbbdev 10651c0b2f7Stbbdev if (t2a.joinable()) t2a.join(); 10751c0b2f7Stbbdev } 10851c0b2f7Stbbdev 10951c0b2f7Stbbdev #if _WIN32||_WIN64 11051c0b2f7Stbbdev 11151c0b2f7Stbbdev void TestKeyDtor() {} 11251c0b2f7Stbbdev 11351c0b2f7Stbbdev #else 11451c0b2f7Stbbdev 11551c0b2f7Stbbdev void *currSmall, *prevSmall, *currLarge, *prevLarge; 11651c0b2f7Stbbdev 11751c0b2f7Stbbdev extern "C" void threadDtor(void*) { 11851c0b2f7Stbbdev // First, release memory that was allocated before; 11951c0b2f7Stbbdev // it will not re-initialize the thread-local data if already deleted 12051c0b2f7Stbbdev prevSmall = currSmall; 12151c0b2f7Stbbdev scalable_free(currSmall); 12251c0b2f7Stbbdev prevLarge = currLarge; 12351c0b2f7Stbbdev scalable_free(currLarge); 12451c0b2f7Stbbdev // Then, allocate more memory. 12551c0b2f7Stbbdev // It will re-initialize the allocator data in the thread. 12651c0b2f7Stbbdev scalable_free(scalable_malloc(8)); 12751c0b2f7Stbbdev } 12851c0b2f7Stbbdev 12951c0b2f7Stbbdev inline bool intersectingObjects(const void *p1, const void *p2, size_t n) 13051c0b2f7Stbbdev { 13151c0b2f7Stbbdev return p1>p2 ? ((uintptr_t)p1-(uintptr_t)p2)<n : ((uintptr_t)p2-(uintptr_t)p1)<n; 13251c0b2f7Stbbdev } 13351c0b2f7Stbbdev 13451c0b2f7Stbbdev struct TestThread: utils::NoAssign { 13551c0b2f7Stbbdev TestThread(int ) {} 13651c0b2f7Stbbdev 13751c0b2f7Stbbdev void operator()( std::size_t /*id*/ ) const { 13851c0b2f7Stbbdev pthread_key_t key; 13951c0b2f7Stbbdev 14051c0b2f7Stbbdev currSmall = scalable_malloc(8); 14151c0b2f7Stbbdev REQUIRE_MESSAGE((!prevSmall || currSmall==prevSmall), "Possible memory leak"); 14251c0b2f7Stbbdev currLarge = scalable_malloc(32*1024); 14351c0b2f7Stbbdev // intersectingObjects takes into account object shuffle 14451c0b2f7Stbbdev REQUIRE_MESSAGE((!prevLarge || intersectingObjects(currLarge, prevLarge, 32*1024)), "Possible memory leak"); 14551c0b2f7Stbbdev pthread_key_create( &key, &threadDtor ); 14651c0b2f7Stbbdev pthread_setspecific(key, (const void*)42); 14751c0b2f7Stbbdev } 14851c0b2f7Stbbdev }; 14951c0b2f7Stbbdev 15051c0b2f7Stbbdev // test releasing memory from pthread key destructor 15151c0b2f7Stbbdev void TestKeyDtor() { 15251c0b2f7Stbbdev // Allocate region for large objects to prevent whole region release 15351c0b2f7Stbbdev // on scalable_free(currLarge) call, which result in wrong assert inside intersectingObjects check 15451c0b2f7Stbbdev void* preventLargeRelease = scalable_malloc(32*1024); 15551c0b2f7Stbbdev for (int i=0; i<4; i++) 15651c0b2f7Stbbdev utils::NativeParallelFor( 1, TestThread(1) ); 15751c0b2f7Stbbdev scalable_free(preventLargeRelease); 15851c0b2f7Stbbdev } 15951c0b2f7Stbbdev 16051c0b2f7Stbbdev #endif // _WIN32||_WIN64 16151c0b2f7Stbbdev 16251c0b2f7Stbbdev 16351c0b2f7Stbbdev //! \brief \ref error_guessing 16451c0b2f7Stbbdev TEST_CASE("test1") { 16551c0b2f7Stbbdev Test1(); // requires malloc initialization so should be first 16651c0b2f7Stbbdev } 16751c0b2f7Stbbdev 16851c0b2f7Stbbdev //! \brief \ref error_guessing 16951c0b2f7Stbbdev TEST_CASE("test2") { 17051c0b2f7Stbbdev Test2(); 17151c0b2f7Stbbdev } 17251c0b2f7Stbbdev 17351c0b2f7Stbbdev //! \brief \ref error_guessing 17451c0b2f7Stbbdev TEST_CASE("test key dtor") { 17551c0b2f7Stbbdev TestKeyDtor(); 17651c0b2f7Stbbdev } 177