xref: /aosp_15_r20/external/pigweed/pw_allocator/synchronized_allocator_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_allocator/synchronized_allocator.h"
16 
17 #include <cstddef>
18 #include <cstdint>
19 #include <limits>
20 
21 #include "pw_allocator/test_harness.h"
22 #include "pw_allocator/testing.h"
23 #include "pw_containers/vector.h"
24 #include "pw_status/status_with_size.h"
25 #include "pw_sync/binary_semaphore.h"
26 #include "pw_sync/interrupt_spin_lock.h"
27 #include "pw_sync/mutex.h"
28 #include "pw_thread/test_thread_context.h"
29 #include "pw_thread/thread.h"
30 #include "pw_thread/thread_core.h"
31 #include "pw_thread/yield.h"
32 #include "pw_unit_test/framework.h"
33 
34 // TODO: https://pwbug.dev/365161669 - Express joinability as a build-system
35 // constraint.
36 #if PW_THREAD_JOINING_ENABLED
37 
38 namespace {
39 
40 // Test fixtures.
41 
42 static constexpr size_t kCapacity = 8192;
43 static constexpr size_t kMaxSize = 512;
44 static constexpr size_t kNumAllocations = 128;
45 static constexpr size_t kSize = 64;
46 static constexpr size_t kAlignment = 4;
47 static constexpr size_t kBackgroundRequests = 8;
48 
49 using ::pw::allocator::Layout;
50 using ::pw::allocator::SynchronizedAllocator;
51 using AllocatorForTest = ::pw::allocator::test::AllocatorForTest<kCapacity>;
52 
53 struct Allocation {
54   void* ptr;
55   Layout layout;
56 
57   /// Fills a valid allocation with a pattern of data.
Paint__anone07a60840111::Allocation58   void Paint() {
59     if (ptr != nullptr) {
60       auto* u8 = static_cast<uint8_t*>(ptr);
61       for (size_t i = 0; i < layout.size(); ++i) {
62         u8[i] = static_cast<uint8_t>(i & 0xFF);
63       }
64     }
65   }
66 
67   /// Checks that a valid allocation has been properly `Paint`ed. Returns the
68   /// first index where the expected pattern doesn't match, e.g. where memory
69   /// has been corrupted, or `layout.size()` if the memory is all correct.
Inspect__anone07a60840111::Allocation70   size_t Inspect() const {
71     if (ptr != nullptr) {
72       auto* u8 = static_cast<uint8_t*>(ptr);
73       for (size_t i = 0; i < layout.size(); ++i) {
74         if (u8[i] != (i & 0xFF)) {
75           return i;
76         }
77       }
78     }
79     return layout.size();
80   }
81 };
82 
83 /// Test fixture that manages a background allocation thread.
84 class Background final {
85  public:
Background(pw::Allocator & allocator)86   Background(pw::Allocator& allocator)
87       : Background(allocator, 1, std::numeric_limits<size_t>::max()) {}
88 
Background(pw::Allocator & allocator,uint64_t seed,size_t iterations)89   Background(pw::Allocator& allocator, uint64_t seed, size_t iterations)
90       : background_(allocator, seed, iterations) {
91     background_thread_ = pw::Thread(context_.options(), background_);
92   }
93 
~Background()94   ~Background() {
95     background_.Stop();
96     Await();
97   }
98 
Await()99   void Await() {
100     background_.Await();
101     if (background_thread_.joinable()) {
102       background_thread_.join();
103     }
104   }
105 
106  private:
107   struct TestHarness : public pw::allocator::test::TestHarness {
108     pw::Allocator* allocator = nullptr;
Init__anone07a60840111::Background::TestHarness109     pw::Allocator* Init() override { return allocator; }
110   };
111 
112   /// Thread body that uses a test harness to perform random sequences of
113   /// allocations on a synchronous allocator.
114   class BackgroundThreadCore : public pw::thread::ThreadCore {
115    public:
BackgroundThreadCore(pw::Allocator & allocator,uint64_t seed,size_t iterations)116     BackgroundThreadCore(pw::Allocator& allocator,
117                          uint64_t seed,
118                          size_t iterations)
119         : iterations_(iterations) {
120       test_harness_.allocator = &allocator;
121       test_harness_.set_prng_seed(seed);
122     }
123 
Stop()124     void Stop() { semaphore_.release(); }
125 
Await()126     void Await() {
127       semaphore_.acquire();
128       semaphore_.release();
129     }
130 
131    private:
Run()132     void Run() override {
133       for (size_t i = 0; i < iterations_ && !semaphore_.try_acquire(); ++i) {
134         test_harness_.GenerateRequests(kMaxSize, kBackgroundRequests);
135         pw::this_thread::yield();
136       }
137       semaphore_.release();
138     }
139 
140     TestHarness test_harness_;
141     pw::sync::BinarySemaphore semaphore_;
142     size_t iterations_;
143   } background_;
144 
145   pw::thread::test::TestThreadContext context_;
146   pw::Thread background_thread_;
147 };
148 
149 // Unit tests.
150 
151 // The tests below manipulate dynamically allocated memory while a background
152 // thread simultaneously exercises the allocator. Allocations, queries and
153 // resizes may fail, but memory must not be corrupted and the test must not
154 // deadlock.
155 
156 template <typename LockType>
TestGetCapacity()157 void TestGetCapacity() {
158   AllocatorForTest allocator;
159   SynchronizedAllocator<LockType> synchronized(allocator);
160   Background background(synchronized);
161 
162   pw::StatusWithSize capacity = synchronized.GetCapacity();
163   EXPECT_EQ(capacity.status(), pw::OkStatus());
164   EXPECT_EQ(capacity.size(), kCapacity);
165 }
166 
TEST(SynchronizedAllocatorTest,GetCapacitySpinLock)167 TEST(SynchronizedAllocatorTest, GetCapacitySpinLock) {
168   TestGetCapacity<pw::sync::InterruptSpinLock>();
169 }
170 
TEST(SynchronizedAllocatorTest,GetCapacityMutex)171 TEST(SynchronizedAllocatorTest, GetCapacityMutex) {
172   TestGetCapacity<pw::sync::Mutex>();
173 }
174 
175 template <typename LockType>
TestAllocate()176 void TestAllocate() {
177   AllocatorForTest allocator;
178   SynchronizedAllocator<LockType> synchronized(allocator);
179   Background background(synchronized);
180 
181   pw::Vector<Allocation, kNumAllocations> allocations;
182   while (!allocations.full()) {
183     Layout layout(kSize, kAlignment);
184     void* ptr = synchronized.Allocate(layout);
185     Allocation allocation{ptr, layout};
186     allocation.Paint();
187     allocations.push_back(allocation);
188     pw::this_thread::yield();
189   }
190 
191   for (const auto& allocation : allocations) {
192     EXPECT_EQ(allocation.Inspect(), allocation.layout.size());
193     synchronized.Deallocate(allocation.ptr);
194   }
195 }
196 
TEST(SynchronizedAllocatorTest,AllocateDeallocateInterruptSpinLock)197 TEST(SynchronizedAllocatorTest, AllocateDeallocateInterruptSpinLock) {
198   TestAllocate<pw::sync::InterruptSpinLock>();
199 }
200 
TEST(SynchronizedAllocatorTest,AllocateDeallocateMutex)201 TEST(SynchronizedAllocatorTest, AllocateDeallocateMutex) {
202   TestAllocate<pw::sync::Mutex>();
203 }
204 
205 template <typename LockType>
TestResize()206 void TestResize() {
207   AllocatorForTest allocator;
208   SynchronizedAllocator<LockType> synchronized(allocator);
209   Background background(synchronized);
210 
211   pw::Vector<Allocation, kNumAllocations> allocations;
212   while (!allocations.full()) {
213     Layout layout(kSize, kAlignment);
214     void* ptr = synchronized.Allocate(layout);
215     Allocation allocation{ptr, layout};
216     allocation.Paint();
217     allocations.push_back(allocation);
218     pw::this_thread::yield();
219   }
220 
221   // First, resize them smaller.
222   for (auto& allocation : allocations) {
223     EXPECT_EQ(allocation.Inspect(), allocation.layout.size());
224     size_t new_size = allocation.layout.size() / 2;
225     if (!synchronized.Resize(allocation.ptr, new_size)) {
226       continue;
227     }
228     allocation.layout = Layout(new_size, allocation.layout.alignment());
229     allocation.Paint();
230     pw::this_thread::yield();
231   }
232 
233   // Then, resize them back to their original size.
234   for (auto& allocation : allocations) {
235     EXPECT_EQ(allocation.Inspect(), allocation.layout.size());
236     size_t old_size = allocation.layout.size() * 2;
237     if (!synchronized.Resize(allocation.ptr, old_size)) {
238       continue;
239     }
240     allocation.layout = Layout(old_size, allocation.layout.alignment());
241     allocation.Paint();
242     pw::this_thread::yield();
243   }
244 }
245 
TEST(SynchronizedAllocatorTest,ResizeInterruptSpinLock)246 TEST(SynchronizedAllocatorTest, ResizeInterruptSpinLock) {
247   TestResize<pw::sync::InterruptSpinLock>();
248 }
249 
TEST(SynchronizedAllocatorTest,ResizeMutex)250 TEST(SynchronizedAllocatorTest, ResizeMutex) { TestResize<pw::sync::Mutex>(); }
251 
252 template <typename LockType>
TestReallocate()253 void TestReallocate() {
254   AllocatorForTest allocator;
255   SynchronizedAllocator<LockType> synchronized(allocator);
256   Background background(synchronized);
257 
258   pw::Vector<Allocation, kNumAllocations> allocations;
259   while (!allocations.full()) {
260     Layout layout(kSize, kAlignment);
261     void* ptr = synchronized.Allocate(layout);
262     Allocation allocation{ptr, layout};
263     allocation.Paint();
264     allocations.push_back(allocation);
265     pw::this_thread::yield();
266   }
267 
268   for (auto& allocation : allocations) {
269     EXPECT_EQ(allocation.Inspect(), allocation.layout.size());
270     Layout new_layout = allocation.layout.Extend(1);
271     void* new_ptr = synchronized.Reallocate(allocation.ptr, new_layout);
272     if (new_ptr == nullptr) {
273       continue;
274     }
275     allocation.ptr = new_ptr;
276     allocation.layout = new_layout;
277     allocation.Paint();
278     pw::this_thread::yield();
279   }
280 }
281 
TEST(SynchronizedAllocatorTest,ReallocateInterruptSpinLock)282 TEST(SynchronizedAllocatorTest, ReallocateInterruptSpinLock) {
283   TestReallocate<pw::sync::InterruptSpinLock>();
284 }
285 
TEST(SynchronizedAllocatorTest,ReallocateMutex)286 TEST(SynchronizedAllocatorTest, ReallocateMutex) {
287   TestReallocate<pw::sync::Mutex>();
288 }
289 
290 template <typename LockType>
TestGenerateRequests()291 void TestGenerateRequests() {
292   constexpr size_t kNumIterations = 10000;
293   AllocatorForTest allocator;
294   SynchronizedAllocator<LockType> synchronized(allocator);
295   Background background1(synchronized, 1, kNumIterations);
296   Background background2(synchronized, 2, kNumIterations);
297   background1.Await();
298   background2.Await();
299 }
300 
TEST(SynchronizedAllocatorTest,GenerateRequestsSpinLock)301 TEST(SynchronizedAllocatorTest, GenerateRequestsSpinLock) {
302   TestGenerateRequests<pw::sync::InterruptSpinLock>();
303 }
304 
TEST(SynchronizedAllocatorTest,GenerateRequestsMutex)305 TEST(SynchronizedAllocatorTest, GenerateRequestsMutex) {
306   TestGenerateRequests<pw::sync::Mutex>();
307 }
308 
309 }  // namespace
310 
311 #endif  // PW_THREAD_JOINING_ENABLED
312