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