xref: /6.6.0/platform/tests/rwlock/rwlock_test.cc (revision 5441ba94)
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.
67using LockTypes = ::testing::Types<
68// macOS's C++ standard library doesn't appear to have annotations
69// for std::mutex etc - so TSan fails to detect lock-order issues.
70// Only test these mutext types on non-macOS.
71#if !defined(__APPLE__)
72        // Identity tests (same - same)
73        LockPair<std::mutex, std::mutex>,
74        LockPair<std::shared_timed_mutex, std::shared_timed_mutex>,
75        LockPair<cb::RWLock, cb::RWLock>,
76
77        // Combination of each mutex type with every other.
78        LockPair<std::mutex, std::shared_timed_mutex>,
79        LockPair<std::mutex, cb::RWLock>,
80        LockPair<std::mutex, folly::SharedMutex>,
81        LockPair<cb::RWLock, std::shared_timed_mutex>,
82        LockPair<cb::RWLock, folly::SharedMutex>,
83        LockPair<folly::SharedMutex, std::shared_timed_mutex>,
84#endif // defined(__APPLE__)
85        // Curiously, macOS _can_ detect issues with
86        // <folly::SharedMutex,std::mutex>, but not in the reverse order
87        // <std::mutex, folly::SharedMutex>
88        LockPair<folly::SharedMutex, std::mutex>,
89        LockPair<folly::SharedMutex, folly::SharedMutex>>;
90
91/// Lock types to test for shared access
92using SharedLockTypes = ::testing::Types<
93#if !defined(__APPLE__)
94        LockPair<std::shared_timed_mutex, std::shared_timed_mutex>,
95        LockPair<cb::RWLock, cb::RWLock>,
96        LockPair<cb::RWLock, std::shared_timed_mutex>,
97        LockPair<cb::RWLock, folly::SharedMutex>,
98        LockPair<folly::SharedMutex, std::shared_timed_mutex>,
99#endif // defined(__APPLE__)
100        LockPair<folly::SharedMutex, folly::SharedMutex>>;
101
102TYPED_TEST_CASE(ExclusiveLockDeathTest, LockTypes);
103TYPED_TEST_CASE(SharedLockDeathTest, SharedLockTypes);
104
105// The following tests all rely on ThreadSanitizer (they are checking
106// that our different locks interact correctly with TSan)
107#if defined(THREAD_SANITIZER)
108
109// Acquire Lock1 and Lock2, and release; then acquire in opposite order.
110// Expect TSan to report a lock inversion order.
111TYPED_TEST(ExclusiveLockDeathTest, LockOrderInversion12) {
112    typename TypeParam::LockA lock1;
113    typename TypeParam::LockB lock2;
114
115    ASSERT_DEATH(
116            {
117                lock1.lock();
118                lock2.lock();
119                lock2.unlock();
120                lock1.unlock();
121
122                lock2.lock();
123                lock1.lock();
124                lock1.unlock();
125                lock2.unlock();
126            },
127            ".*WARNING: ThreadSanitizer: lock-order-inversion .*");
128}
129
130// Acquire Lock1(read) and Lock2(write), and release; then acquire in opposite
131// order.
132// Expect TSan to report a lock inversion order.
133TYPED_TEST(SharedLockDeathTest, RWInversion) {
134    typename TypeParam::LockA lock1;
135    typename TypeParam::LockB lock2;
136
137    ASSERT_DEATH(
138            {
139                lock1.lock_shared();
140                lock2.lock();
141                lock2.unlock();
142                lock1.unlock_shared();
143
144                lock2.lock();
145                lock1.lock_shared();
146                lock1.unlock_shared();
147                lock2.unlock();
148            },
149            ".*WARNING: ThreadSanitizer: lock-order-inversion .*");
150}
151
152// Acquire Lock1(write) and Lock2(read), and release; then acquire in opposite
153// order.
154// Expect TSan to report a lock inversion order.
155TYPED_TEST(SharedLockDeathTest, WRInversion) {
156    typename TypeParam::LockA lock1;
157    typename TypeParam::LockB lock2;
158
159    ASSERT_DEATH(
160            {
161                lock1.lock();
162                lock2.lock_shared();
163                lock2.unlock_shared();
164                lock1.unlock();
165
166                lock2.lock_shared();
167                lock1.lock();
168                lock1.unlock();
169                lock2.unlock_shared();
170            },
171            ".*WARNING: ThreadSanitizer: lock-order-inversion .*");
172}
173
174#endif // defined(THREAD_SANITIZER)
175