xref: /6.6.0/platform/tests/rwlock/rwlock_test.cc (revision 4ef49779)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2019 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18#include <platform/rwlock.h>
19
20#include <folly/SharedMutex.h>
21#include <folly/portability/GTest.h>
22#include <shared_mutex>
23
24/**
25 * Templated death test which tests the interoperability of two lock types.
26 * When run under TSan (they are skipped otherwise), tests should crash due to
27 * TSan detecting the expected threading errors.
28 *
29 * @tparam LOCKS The two locks to test. Should expose LockA and LockB nested
30 * types.
31 */
32template <typename Locks>
33class LockDeathTest : public ::testing::Test {
34public:
35    void SetUp() {
36        // Check out preconditions - require that TSan is configured to halt
37        // immediately on an error.
38        auto* tsanOptions = getenv("TSAN_OPTIONS");
39        ASSERT_TRUE(tsanOptions) << "LockDeathTests require that TSAN_OPTIONS "
40                                    "is defined (and contains "
41                                    "'halt_on_error=1')";
42        ASSERT_TRUE(std::string(tsanOptions).find("halt_on_error=1") !=
43                    std::string::npos)
44                << "LockDeathTests require that ThreadSanitizer is run with "
45                   "'halt_on_error' enabled. Check that the TSAN_OPTIONS env "
46                   "var contains 'halt_on_error=1'";
47    }
48    typename Locks::LockA lockA;
49    typename Locks::LockB lockB;
50};
51
52/// Subclass for exclusive locks
53template <typename Locks>
54class ExclusiveLockDeathTest : public LockDeathTest<Locks> {};
55
56/// Subclass for shared (reader-writer) locks
57template <typename Locks>
58class SharedLockDeathTest : public LockDeathTest<Locks> {};
59
60template <typename A, typename B>
61struct LockPair {
62    using LockA = A;
63    using LockB = B;
64};
65
66/// Lock types to test for exclusive access.
67// macOS's C++ standard library doesn't appear to have annotations
68// for std::mutex etc - so TSan fails to detect lock-order issues.
69// As such some combinations cannot be checked on macOS.
70using LockTypes = ::testing::Types<
71// Identity tests (same - same)
72#if !defined(__APPLE__)
73        LockPair<std::mutex, std::mutex>,
74        LockPair<std::shared_timed_mutex, std::shared_timed_mutex>,
75        LockPair<folly::SharedMutex, folly::SharedMutex>,
76#endif
77        LockPair<cb::RWLock, cb::RWLock>,
78
79// Each mutex with each other mutex, in both orders (A,B) & (B,A).
80// While you'd _think_ that it would be sufficient to just check each
81// pair once, given in the test we call:
82//     A.lock(),
83//     B.lock(),
84//     unlock
85//     ...
86//     B.lock(),
87//     A.lock()
88// It turns out that on macOS at least TSan can detect lock inversions
89// in _one_ order but not the other -  ¯\_(ツ)_/¯
90#if !defined(__APPLE__)
91        LockPair<std::mutex, std::shared_timed_mutex>,
92        LockPair<std::mutex, cb::RWLock>,
93        LockPair<std::mutex, folly::SharedMutex>,
94
95        LockPair<std::shared_timed_mutex, std::mutex>,
96        LockPair<std::shared_timed_mutex, cb::RWLock>,
97        LockPair<std::shared_timed_mutex, folly::SharedMutex>,
98#endif
99
100        LockPair<cb::RWLock, std::mutex>,
101#if !defined(__APPLE__)
102        LockPair<cb::RWLock, std::shared_timed_mutex>,
103#endif
104        LockPair<cb::RWLock, folly::SharedMutex>,
105
106        LockPair<folly::SharedMutex, std::mutex>,
107#if !defined(__APPLE__)
108        LockPair<folly::SharedMutex, std::shared_timed_mutex>,
109#endif
110        LockPair<folly::SharedMutex, cb::RWLock>>;
111
112/// Lock types to test for shared access
113using SharedLockTypes = ::testing::Types<
114#if !defined(__APPLE__)
115        LockPair<std::shared_timed_mutex, std::shared_timed_mutex>,
116        LockPair<cb::RWLock, cb::RWLock>,
117        LockPair<cb::RWLock, std::shared_timed_mutex>,
118        LockPair<cb::RWLock, folly::SharedMutex>,
119        LockPair<folly::SharedMutex, std::shared_timed_mutex>,
120#endif // defined(__APPLE__)
121        LockPair<folly::SharedMutex, folly::SharedMutex>>;
122
123TYPED_TEST_CASE(ExclusiveLockDeathTest, LockTypes);
124TYPED_TEST_CASE(SharedLockDeathTest, SharedLockTypes);
125
126// The following tests all rely on ThreadSanitizer (they are checking
127// that our different locks interact correctly with TSan)
128#if defined(THREAD_SANITIZER)
129
130// Acquire Lock1 and Lock2, and release; then acquire in opposite order.
131// Expect TSan to report a lock inversion order.
132TYPED_TEST(ExclusiveLockDeathTest, LockOrderInversion12) {
133    typename TypeParam::LockA lock1;
134    typename TypeParam::LockB lock2;
135
136    ASSERT_DEATH(
137            {
138                lock1.lock();
139                lock2.lock();
140                lock2.unlock();
141                lock1.unlock();
142
143                lock2.lock();
144                lock1.lock();
145                lock1.unlock();
146                lock2.unlock();
147            },
148            ".*WARNING: ThreadSanitizer: lock-order-inversion .*");
149}
150
151// Acquire Lock1(read) and Lock2(write), and release; then acquire in opposite
152// order.
153// Expect TSan to report a lock inversion order.
154TYPED_TEST(SharedLockDeathTest, RWInversion) {
155    typename TypeParam::LockA lock1;
156    typename TypeParam::LockB lock2;
157
158    ASSERT_DEATH(
159            {
160                lock1.lock_shared();
161                lock2.lock();
162                lock2.unlock();
163                lock1.unlock_shared();
164
165                lock2.lock();
166                lock1.lock_shared();
167                lock1.unlock_shared();
168                lock2.unlock();
169            },
170            ".*WARNING: ThreadSanitizer: lock-order-inversion .*");
171}
172
173// Acquire Lock1(write) and Lock2(read), and release; then acquire in opposite
174// order.
175// Expect TSan to report a lock inversion order.
176TYPED_TEST(SharedLockDeathTest, WRInversion) {
177    typename TypeParam::LockA lock1;
178    typename TypeParam::LockB lock2;
179
180    ASSERT_DEATH(
181            {
182                lock1.lock();
183                lock2.lock_shared();
184                lock2.unlock_shared();
185                lock1.unlock();
186
187                lock2.lock_shared();
188                lock1.lock();
189                lock1.unlock();
190                lock2.unlock_shared();
191            },
192            ".*WARNING: ThreadSanitizer: lock-order-inversion .*");
193}
194
195#endif // defined(THREAD_SANITIZER)
196