1 //
2 // Copyright 2023 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // Unit tests for ContextMutex.
7 //
8
9 #include "gtest/gtest.h"
10
11 #include "libANGLE/ContextMutex.h"
12
13 namespace
14 {
15
16 template <class GetContextMutex>
runBasicContextMutexTest(bool expectToPass,GetContextMutex && getContextMutex)17 void runBasicContextMutexTest(bool expectToPass, GetContextMutex &&getContextMutex)
18 {
19 constexpr size_t kThreadCount = 16;
20 constexpr size_t kIterationCount = 50'000;
21
22 std::array<std::thread, kThreadCount> threads;
23 std::array<egl::ContextMutex *, kThreadCount> contextMutexes = {};
24
25 std::mutex mutex;
26 std::condition_variable condVar;
27 size_t readyCount = 0;
28
29 std::atomic<size_t> testVar;
30
31 for (size_t i = 0; i < kThreadCount; ++i)
32 {
33 threads[i] = std::thread([&, i]() {
34 {
35 std::unique_lock<std::mutex> lock(mutex);
36 contextMutexes[i] = getContextMutex();
37 contextMutexes[i]->addRef();
38 ++readyCount;
39 if (readyCount < kThreadCount)
40 {
41 condVar.wait(lock, [&]() { return readyCount == kThreadCount; });
42 }
43 else
44 {
45 condVar.notify_all();
46 }
47 }
48 for (size_t j = 0; j < kIterationCount; ++j)
49 {
50 egl::ScopedContextMutexLock lock(contextMutexes[i]);
51 const int local = testVar.load(std::memory_order_relaxed);
52 const int newValue = local + 1;
53 testVar.store(newValue, std::memory_order_relaxed);
54 }
55 });
56 }
57
58 for (size_t i = 0; i < kThreadCount; ++i)
59 {
60 threads[i].join();
61 contextMutexes[i]->release();
62 }
63
64 if (expectToPass)
65 {
66 EXPECT_EQ(testVar.load(), kThreadCount * kIterationCount);
67 }
68 else
69 {
70 EXPECT_LE(testVar.load(), kThreadCount * kIterationCount);
71 }
72 }
73
74 // Tests locking of single ContextMutex mutex.
TEST(ContextMutexTest,SingleMutexLock)75 TEST(ContextMutexTest, SingleMutexLock)
76 {
77 egl::ContextMutex *contextMutex = new egl::ContextMutex();
78 contextMutex->addRef();
79 runBasicContextMutexTest(true, [&]() { return contextMutex; });
80 contextMutex->release();
81 }
82
83 // Tests locking of multiple merged ContextMutex mutexes.
TEST(ContextMutexTest,MultipleMergedMutexLock)84 TEST(ContextMutexTest, MultipleMergedMutexLock)
85 {
86 egl::ContextMutex *contextMutex = new egl::ContextMutex();
87 contextMutex->addRef();
88 runBasicContextMutexTest(true, [&]() {
89 egl::ScopedContextMutexLock lock(contextMutex);
90 egl::ContextMutex *threadMutex = new egl::ContextMutex();
91 egl::ContextMutex::Merge(contextMutex, threadMutex);
92 return threadMutex;
93 });
94 contextMutex->release();
95 }
96
97 // Tests locking of multiple unmerged ContextMutex mutexes.
TEST(ContextMutexTest,MultipleUnmergedMutexLock)98 TEST(ContextMutexTest, MultipleUnmergedMutexLock)
99 {
100 runBasicContextMutexTest(false, [&]() { return new egl::ContextMutex(); });
101 }
102
103 // Creates 2N mutexes and 2 threads, then merges N mutex pairs in each thread. Merging order of
104 // the first thread is reversed in the second thread.
TEST(ContextMutexTest,TwoThreadsCrossMerge)105 TEST(ContextMutexTest, TwoThreadsCrossMerge)
106 {
107 constexpr size_t kThreadCount = 2;
108 constexpr size_t kIterationCount = 100;
109 static_assert(kThreadCount % 2 == 0);
110
111 std::array<std::thread, kThreadCount> threads;
112 std::array<std::array<egl::ContextMutex *, kThreadCount>, kIterationCount> mutexParis = {};
113
114 // Create mutexes.
115 for (uint32_t i = 0; i < kIterationCount; ++i)
116 {
117 for (uint32_t j = 0; j < kThreadCount; ++j)
118 {
119 mutexParis[i][j] = new egl::ContextMutex();
120 // Call without a lock because no concurrent access is possible.
121 mutexParis[i][j]->addRef();
122 }
123 }
124
125 std::mutex mutex;
126 std::condition_variable condVar;
127 size_t readyCount = 0;
128 bool flipFlop = false;
129
130 auto threadJob = [&](size_t lockMutexIndex) {
131 for (size_t i = 0; i < kIterationCount; ++i)
132 {
133 // Lock the first mutex.
134 egl::ScopedContextMutexLock contextMutexLock(mutexParis[i][lockMutexIndex]);
135 // Wait until all threads are ready...
136 {
137 std::unique_lock<std::mutex> lock(mutex);
138 ++readyCount;
139 if (readyCount == kThreadCount)
140 {
141 readyCount = 0;
142 flipFlop = !flipFlop;
143 condVar.notify_all();
144 }
145 else
146 {
147 const size_t prevFlipFlop = flipFlop;
148 condVar.wait(lock, [&]() { return flipFlop != prevFlipFlop; });
149 }
150 }
151 // Merge mutexes.
152 egl::ContextMutex::Merge(mutexParis[i][lockMutexIndex],
153 mutexParis[i][kThreadCount - lockMutexIndex - 1]);
154 }
155 };
156
157 // Start threads...
158 for (size_t i = 0; i < kThreadCount; ++i)
159 {
160 threads[i] = std::thread([&, i]() { threadJob(i); });
161 }
162
163 // Join with threads...
164 for (std::thread &thread : threads)
165 {
166 thread.join();
167 }
168
169 // Destroy mutexes.
170 for (size_t i = 0; i < kIterationCount; ++i)
171 {
172 for (size_t j = 0; j < kThreadCount; ++j)
173 {
174 // Call without a lock because no concurrent access is possible.
175 mutexParis[i][j]->release();
176 }
177 }
178 }
179
180 } // anonymous namespace
181