1/*
2 Copyright (c) 2005-2019 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
28tbb::atomic<int> FinishedTasks;
29const 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
37class TestFunc1 {
38 Harness::SpinBarrier* my_barr;
39public:
40 TestFunc1 (Harness::SpinBarrier& barr) : my_barr(&barr) {}
41 void operator() (bool do_malloc) const {
42 my_barr->wait();
43 if (do_malloc) scalable_malloc(10);
44 ++FinishedTasks;
45 }
46};
47
48typedef NativeParallelForTask<bool,TestFunc1> TestTask1;
49
50void 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
77void* gPtr = NULL;
78
79class TestFunc2a {
80 Harness::SpinBarrier* my_barr;
81public:
82 TestFunc2a (Harness::SpinBarrier& barr) : my_barr(&barr) {}
83 void operator() (int) const {
84 gPtr = scalable_malloc(8);
85 my_barr->wait();
86 ++FinishedTasks;
87 }
88};
89
90typedef NativeParallelForTask<int,TestFunc2a> TestTask2a;
91
92class TestFunc2b: NoAssign {
93 Harness::SpinBarrier* my_barr;
94 TestTask2a& my_ward;
95public:
96 TestFunc2b (Harness::SpinBarrier& barr, TestTask2a& t) : my_barr(&barr), my_ward(t) {}
97 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};
105void 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
120void TestKeyDtor() {}
121
122#else
123
124void *currSmall, *prevSmall, *currLarge, *prevLarge;
125
126extern "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
138inline 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
143struct TestThread: NoAssign {
144 TestThread(int ) {}
145
146 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
160void 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
171int TestMain () {
172 Test1(); // requires malloc initialization so should be first
173 Test2();
174 TestKeyDtor();
175 return Harness::Done;
176}
177