xref: /oneTBB/test/tbb/test_mutex.h (revision 478de5b1)
1 /*
2     Copyright (c) 2005-2021 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 "common/test.h"
18 #include "common/utils.h"
19 #include "common/utils_concurrency_limit.h"
20 #include "common/config.h"
21 #include "common/rwm_upgrade_downgrade.h"
22 #include "common/concepts_common.h"
23 #include <tbb/null_rw_mutex.h>
24 
25 #include <atomic>
26 
27 namespace test_with_native_threads {
28 
29 template <typename M>
30 struct Counter {
31     using mutex_type = M;
32 
33     M mutex;
34     long value;
35 
flog_onceCounter36     void flog_once( std::size_t mode ) {
37         // Increments counter once for each iteration in the iteration space
38         if (mode & 1) {
39             // Try implicit acquire and explicit release
40             typename mutex_type::scoped_lock lock(mutex);
41             value += 1;
42             lock.release();
43         } else {
44             // Try explicit acquire and implicit release
45             typename mutex_type::scoped_lock lock;
46             lock.acquire(mutex);
47             value += 1;
48         }
49     }
50 }; // struct Counter
51 
52 template <typename M, long N>
53 struct Invariant {
54     using mutex_type = M;
55 
56     M mutex;
57     long value[N];
58 
InvariantInvariant59     Invariant() {
60         for (long k = 0; k < N; ++k) {
61             value[k] = 0;
62         }
63     }
64 
updateInvariant65     void update() {
66         for (long k = 0; k < N; ++k) {
67             ++value[k];
68         }
69     }
70 
value_isInvariant71     bool value_is( long expected_value ) const {
72         long tmp;
73 
74         for (long k = 0; k < N; ++k) {
75             if ((tmp = value[k]) != expected_value) {
76                 return false;
77             }
78         }
79         return true;
80     }
81 
is_okayInvariant82     bool is_okay() {
83         return value_is(value[0]);
84     }
85 
flog_onceInvariant86     void flog_once( std::size_t mode ) {
87         // Every 8th access is a write access
88         bool write = (mode % 8) == 7;
89         bool okay = true;
90         bool lock_kept = true;
91 
92         if ((mode / 8) & 1) {
93             // Try implicit acquire and explicit release
94             typename mutex_type::scoped_lock lock(mutex, write);
95             if (write) {
96                 long my_value = value[0];
97                 update();
98                 if (mode % 16 == 7) {
99                     lock_kept = lock.downgrade_to_reader();
100                     if (!lock_kept) {
101                         my_value = value[0] - 1;
102                     }
103                     okay = value_is(my_value + 1);
104                 }
105             } else {
106                 okay = is_okay();
107                 if (mode % 8 == 3) {
108                     long my_value = value[0];
109                     lock_kept = lock.upgrade_to_writer();
110                     if (!lock_kept) {
111                         my_value = value[0];
112                     }
113                     update();
114                     okay = value_is(my_value + 1);
115                 }
116             }
117             lock.release();
118         } else {
119             // Try explicit acquire and implicit release
120             typename mutex_type::scoped_lock lock;
121             lock.acquire(mutex, write);
122             if (write) {
123                 long my_value = value[0];
124                 update();
125                 if (mode % 16 == 7) {
126                     lock_kept = lock.downgrade_to_reader();
127                     if (!lock_kept) {
128                         my_value = value[0] - 1;
129                     }
130                     okay = value_is(my_value + 1);
131                 }
132             } else {
133                 okay = is_okay();
134                 if (mode % 8 == 3) {
135                     long my_value = value[0];
136                     lock_kept = lock.upgrade_to_writer();
137                     if (!lock_kept) {
138                         my_value = value[0];
139                     }
140                     update();
141                     okay = value_is(my_value + 1);
142                 }
143             }
144         }
145         REQUIRE(okay);
146     }
147 }; // struct Invariant
148 
149 static std::atomic<std::size_t> Order;
150 
151 template <typename State, long TestSize>
152 struct Work : utils::NoAssign {
153     static constexpr std::size_t chunk = 100;
154     State& state;
155 
WorkWork156     Work( State& st ) : state(st){ Order = 0; }
157 
operatorWork158     void operator()(std::size_t) const {
159         std::size_t step;
160         while( (step = Order.fetch_add(chunk, std::memory_order_acquire)) < TestSize ) {
161             for (std::size_t i = 0; i < chunk && step < TestSize; ++i, ++step) {
162                 state.flog_once(step);
163             }
164         }
165     }
166 }; // struct Work
167 
168 constexpr std::size_t TEST_SIZE = 100000;
169 
170 template <typename M>
test_basic(std::size_t nthread)171 void test_basic( std::size_t nthread ) {
172     Counter<M> counter;
173     counter.value = 0;
174     Order = 0;
175     utils::NativeParallelFor(nthread, Work<Counter<M>, TEST_SIZE>(counter));
176 
177     REQUIRE(counter.value == TEST_SIZE);
178 }
179 
180 template <typename M>
test_rw_basic(std::size_t nthread)181 void test_rw_basic( std::size_t nthread ) {
182     Invariant<M, 8> invariant;
183     Order = 0;
184     // use the macro because of a gcc 4.6 issue
185     utils::NativeParallelFor(nthread, Work<Invariant<M, 8>, TEST_SIZE>(invariant));
186     // There is either a writer or a reader upgraded to a writer for each 4th iteration
187     long expected_value = TEST_SIZE / 4;
188     REQUIRE(invariant.value_is(expected_value));
189 }
190 
191 template <typename M>
test()192 void test() {
193     for (std::size_t p : utils::concurrency_range()) {
194         test_basic<M>(p);
195     }
196 }
197 
198 template <typename M>
test_rw()199 void test_rw() {
200     for (std::size_t p : utils::concurrency_range()) {
201         test_rw_basic<M>(p);
202     }
203 }
204 
205 } // namespace test_with_native_threads
206 
207 template <typename RWMutexType>
TestIsWriter(const char * mutex_name)208 void TestIsWriter(const char* mutex_name) {
209     using scoped_lock = typename RWMutexType::scoped_lock;
210 
211     RWMutexType rw_mutex;
212     std::string error_message_writer = std::string(mutex_name) + "::scoped_lock is not acquired for write, is_writer should return false";
213     std::string error_message_not_writer = std::string(mutex_name) + "::scoped_lock is acquired for write, is_writer should return true";
214     // Test is_writer after construction
215     {
216         scoped_lock lock(rw_mutex, /*writer = */false);
217         CHECK_MESSAGE(!lock.is_writer(), error_message_writer);
218     }
219     {
220         scoped_lock lock(rw_mutex, /*writer = */true);
221         CHECK_MESSAGE(lock.is_writer(), error_message_not_writer);
222     }
223     // Test is_writer after acquire
224     {
225         scoped_lock lock;
226         lock.acquire(rw_mutex, /*writer = */false);
227         CHECK_MESSAGE(!lock.is_writer(), error_message_writer);
228     }
229     {
230         scoped_lock lock;
231         lock.acquire(rw_mutex, /*writer = */true);
232         CHECK_MESSAGE(lock.is_writer(), error_message_not_writer);
233     }
234     // Test is_writer on upgrade/downgrade
235     {
236         scoped_lock lock(rw_mutex, /*writer = */false);
237         lock.upgrade_to_writer();
238         CHECK_MESSAGE(lock.is_writer(), error_message_not_writer);
239         lock.downgrade_to_reader();
240         CHECK_MESSAGE(!lock.is_writer(), error_message_writer);
241     }
242 }
243 
244 template <>
245 void TestIsWriter<oneapi::tbb::null_rw_mutex>( const char* ) {
246     using scoped_lock = typename oneapi::tbb::null_rw_mutex::scoped_lock;
247 
248     oneapi::tbb::null_rw_mutex nrw_mutex;
249     scoped_lock l(nrw_mutex);
250     CHECK(l.is_writer());
251 }
252