xref: /aosp_15_r20/external/angle/src/libANGLE/ContextMutex_unittest.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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