1 /*
2 Copyright (c) 2005-2020 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #include "tbb/scalable_allocator.h"
18 #include "tbb/atomic.h"
19 #include "tbb/aligned_space.h"
20
21 #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1
22 #include "harness.h"
23 #include "harness_barrier.h"
24 #if !__TBB_SOURCE_DIRECTLY_INCLUDED
25 #include "harness_tbb_independence.h"
26 #endif
27
28 tbb::atomic<int> FinishedTasks;
29 const int MaxTasks = 16;
30
31 /*--------------------------------------------------------------------*/
32 // The regression test against a bug triggered when malloc initialization
33 // and thread shutdown were called simultaneously, in which case
34 // Windows dynamic loader lock and allocator initialization/termination lock
35 // were taken in different order.
36
37 class TestFunc1 {
38 Harness::SpinBarrier* my_barr;
39 public:
TestFunc1(Harness::SpinBarrier & barr)40 TestFunc1 (Harness::SpinBarrier& barr) : my_barr(&barr) {}
operator ()(bool do_malloc) const41 void operator() (bool do_malloc) const {
42 my_barr->wait();
43 if (do_malloc) scalable_malloc(10);
44 ++FinishedTasks;
45 }
46 };
47
48 typedef NativeParallelForTask<bool,TestFunc1> TestTask1;
49
Test1()50 void Test1 () {
51 int NTasks = min(MaxTasks, max(2, MaxThread));
52 Harness::SpinBarrier barr(NTasks);
53 TestFunc1 tf(barr);
54 FinishedTasks = 0;
55 tbb::aligned_space<TestTask1,MaxTasks> tasks;
56
57 for(int i=0; i<NTasks; ++i) {
58 TestTask1* t = tasks.begin()+i;
59 new(t) TestTask1(i%2==0, tf);
60 t->start();
61 }
62
63 Harness::Sleep(1000); // wait a second :)
64 ASSERT( FinishedTasks==NTasks, "Some threads appear to deadlock" );
65
66 for(int i=0; i<NTasks; ++i) {
67 TestTask1* t = tasks.begin()+i;
68 t->wait_to_finish();
69 t->~TestTask1();
70 }
71 }
72
73 /*--------------------------------------------------------------------*/
74 // The regression test against a bug when cross-thread deallocation
75 // caused livelock at thread shutdown.
76
77 void* gPtr = NULL;
78
79 class TestFunc2a {
80 Harness::SpinBarrier* my_barr;
81 public:
TestFunc2a(Harness::SpinBarrier & barr)82 TestFunc2a (Harness::SpinBarrier& barr) : my_barr(&barr) {}
operator ()(int) const83 void operator() (int) const {
84 gPtr = scalable_malloc(8);
85 my_barr->wait();
86 ++FinishedTasks;
87 }
88 };
89
90 typedef NativeParallelForTask<int,TestFunc2a> TestTask2a;
91
92 class TestFunc2b: NoAssign {
93 Harness::SpinBarrier* my_barr;
94 TestTask2a& my_ward;
95 public:
TestFunc2b(Harness::SpinBarrier & barr,TestTask2a & t)96 TestFunc2b (Harness::SpinBarrier& barr, TestTask2a& t) : my_barr(&barr), my_ward(t) {}
operator ()(int) const97 void operator() (int) const {
98 tbb::internal::spin_wait_while_eq(gPtr, (void*)NULL);
99 scalable_free(gPtr);
100 my_barr->wait();
101 my_ward.wait_to_finish();
102 ++FinishedTasks;
103 }
104 };
Test2()105 void Test2() {
106 Harness::SpinBarrier barr(2);
107 TestFunc2a func2a(barr);
108 TestTask2a t2a(0, func2a);
109 TestFunc2b func2b(barr, t2a);
110 NativeParallelForTask<int,TestFunc2b> t2b(1, func2b);
111 FinishedTasks = 0;
112 t2a.start(); t2b.start();
113 Harness::Sleep(1000); // wait a second :)
114 ASSERT( FinishedTasks==2, "Threads appear to deadlock" );
115 t2b.wait_to_finish(); // t2a is monitored by t2b
116 }
117
118 #if _WIN32||_WIN64
119
TestKeyDtor()120 void TestKeyDtor() {}
121
122 #else
123
124 void *currSmall, *prevSmall, *currLarge, *prevLarge;
125
threadDtor(void *)126 extern "C" void threadDtor(void*) {
127 // First, release memory that was allocated before;
128 // it will not re-initialize the thread-local data if already deleted
129 prevSmall = currSmall;
130 scalable_free(currSmall);
131 prevLarge = currLarge;
132 scalable_free(currLarge);
133 // Then, allocate more memory.
134 // It will re-initialize the allocator data in the thread.
135 scalable_free(scalable_malloc(8));
136 }
137
intersectingObjects(const void * p1,const void * p2,size_t n)138 inline bool intersectingObjects(const void *p1, const void *p2, size_t n)
139 {
140 return p1>p2 ? ((uintptr_t)p1-(uintptr_t)p2)<n : ((uintptr_t)p2-(uintptr_t)p1)<n;
141 }
142
143 struct TestThread: NoAssign {
TestThreadTestThread144 TestThread(int ) {}
145
operator ()TestThread146 void operator()( int /*id*/ ) const {
147 pthread_key_t key;
148
149 currSmall = scalable_malloc(8);
150 ASSERT(!prevSmall || currSmall==prevSmall, "Possible memory leak");
151 currLarge = scalable_malloc(32*1024);
152 // intersectingObjects takes into account object shuffle
153 ASSERT(!prevLarge || intersectingObjects(currLarge, prevLarge, 32*1024), "Possible memory leak");
154 pthread_key_create( &key, &threadDtor );
155 pthread_setspecific(key, (const void*)42);
156 }
157 };
158
159 // test releasing memory from pthread key destructor
TestKeyDtor()160 void TestKeyDtor() {
161 // Allocate region for large objects to prevent whole region release
162 // on scalable_free(currLarge) call, which result in wrong assert inside intersectingObjects check
163 void* preventLargeRelease = scalable_malloc(32*1024);
164 for (int i=0; i<4; i++)
165 NativeParallelFor( 1, TestThread(1) );
166 scalable_free(preventLargeRelease);
167 }
168
169 #endif // _WIN32||_WIN64
170
TestMain()171 int TestMain () {
172 Test1(); // requires malloc initialization so should be first
173 Test2();
174 TestKeyDtor();
175 return Harness::Done;
176 }
177