13cab2bb3Spatrick //===-- tsd_test.cpp --------------------------------------------*- C++ -*-===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick
93cab2bb3Spatrick #include "tests/scudo_unit_test.h"
103cab2bb3Spatrick
113cab2bb3Spatrick #include "tsd_exclusive.h"
123cab2bb3Spatrick #include "tsd_shared.h"
133cab2bb3Spatrick
14d89ec533Spatrick #include <stdlib.h>
15d89ec533Spatrick
163cab2bb3Spatrick #include <condition_variable>
173cab2bb3Spatrick #include <mutex>
18d89ec533Spatrick #include <set>
193cab2bb3Spatrick #include <thread>
203cab2bb3Spatrick
213cab2bb3Spatrick // We mock out an allocator with a TSD registry, mostly using empty stubs. The
223cab2bb3Spatrick // cache contains a single volatile uptr, to be able to test that several
233cab2bb3Spatrick // concurrent threads will not access or modify the same cache at the same time.
243cab2bb3Spatrick template <class Config> class MockAllocator {
253cab2bb3Spatrick public:
263cab2bb3Spatrick using ThisT = MockAllocator<Config>;
273cab2bb3Spatrick using TSDRegistryT = typename Config::template TSDRegistryT<ThisT>;
28*810390e3Srobert using CacheT = struct MockCache {
29*810390e3Srobert volatile scudo::uptr Canary;
30*810390e3Srobert };
313cab2bb3Spatrick using QuarantineCacheT = struct MockQuarantine {};
323cab2bb3Spatrick
init()33d89ec533Spatrick void init() {
343cab2bb3Spatrick // This should only be called once by the registry.
353cab2bb3Spatrick EXPECT_FALSE(Initialized);
363cab2bb3Spatrick Initialized = true;
373cab2bb3Spatrick }
383cab2bb3Spatrick
unmapTestOnly()39d89ec533Spatrick void unmapTestOnly() { TSDRegistry.unmapTestOnly(this); }
initCache(CacheT * Cache)40d89ec533Spatrick void initCache(CacheT *Cache) { *Cache = {}; }
commitBack(scudo::TSD<MockAllocator> * TSD)413cab2bb3Spatrick void commitBack(scudo::TSD<MockAllocator> *TSD) {}
getTSDRegistry()423cab2bb3Spatrick TSDRegistryT *getTSDRegistry() { return &TSDRegistry; }
callPostInitCallback()433cab2bb3Spatrick void callPostInitCallback() {}
443cab2bb3Spatrick
isInitialized()453cab2bb3Spatrick bool isInitialized() { return Initialized; }
463cab2bb3Spatrick
operator new(size_t Size)47d89ec533Spatrick void *operator new(size_t Size) {
48d89ec533Spatrick void *P = nullptr;
49d89ec533Spatrick EXPECT_EQ(0, posix_memalign(&P, alignof(ThisT), Size));
50d89ec533Spatrick return P;
51d89ec533Spatrick }
operator delete(void * P)52d89ec533Spatrick void operator delete(void *P) { free(P); }
53d89ec533Spatrick
543cab2bb3Spatrick private:
55d89ec533Spatrick bool Initialized = false;
563cab2bb3Spatrick TSDRegistryT TSDRegistry;
573cab2bb3Spatrick };
583cab2bb3Spatrick
593cab2bb3Spatrick struct OneCache {
603cab2bb3Spatrick template <class Allocator>
61d89ec533Spatrick using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 1U, 1U>;
623cab2bb3Spatrick };
633cab2bb3Spatrick
643cab2bb3Spatrick struct SharedCaches {
653cab2bb3Spatrick template <class Allocator>
66d89ec533Spatrick using TSDRegistryT = scudo::TSDRegistrySharedT<Allocator, 16U, 8U>;
673cab2bb3Spatrick };
683cab2bb3Spatrick
693cab2bb3Spatrick struct ExclusiveCaches {
703cab2bb3Spatrick template <class Allocator>
713cab2bb3Spatrick using TSDRegistryT = scudo::TSDRegistryExT<Allocator>;
723cab2bb3Spatrick };
733cab2bb3Spatrick
TEST(ScudoTSDTest,TSDRegistryInit)743cab2bb3Spatrick TEST(ScudoTSDTest, TSDRegistryInit) {
753cab2bb3Spatrick using AllocatorT = MockAllocator<OneCache>;
763cab2bb3Spatrick auto Deleter = [](AllocatorT *A) {
773cab2bb3Spatrick A->unmapTestOnly();
783cab2bb3Spatrick delete A;
793cab2bb3Spatrick };
803cab2bb3Spatrick std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
813cab2bb3Spatrick Deleter);
823cab2bb3Spatrick EXPECT_FALSE(Allocator->isInitialized());
833cab2bb3Spatrick
843cab2bb3Spatrick auto Registry = Allocator->getTSDRegistry();
85d89ec533Spatrick Registry->init(Allocator.get());
863cab2bb3Spatrick EXPECT_TRUE(Allocator->isInitialized());
873cab2bb3Spatrick }
883cab2bb3Spatrick
testRegistry()893cab2bb3Spatrick template <class AllocatorT> static void testRegistry() {
903cab2bb3Spatrick auto Deleter = [](AllocatorT *A) {
913cab2bb3Spatrick A->unmapTestOnly();
923cab2bb3Spatrick delete A;
933cab2bb3Spatrick };
943cab2bb3Spatrick std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
953cab2bb3Spatrick Deleter);
963cab2bb3Spatrick EXPECT_FALSE(Allocator->isInitialized());
973cab2bb3Spatrick
983cab2bb3Spatrick auto Registry = Allocator->getTSDRegistry();
993cab2bb3Spatrick Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/true);
1003cab2bb3Spatrick EXPECT_TRUE(Allocator->isInitialized());
1013cab2bb3Spatrick
1023cab2bb3Spatrick bool UnlockRequired;
1033cab2bb3Spatrick auto TSD = Registry->getTSDAndLock(&UnlockRequired);
1043cab2bb3Spatrick EXPECT_NE(TSD, nullptr);
1053cab2bb3Spatrick EXPECT_EQ(TSD->Cache.Canary, 0U);
1063cab2bb3Spatrick if (UnlockRequired)
1073cab2bb3Spatrick TSD->unlock();
1083cab2bb3Spatrick
1093cab2bb3Spatrick Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false);
1103cab2bb3Spatrick TSD = Registry->getTSDAndLock(&UnlockRequired);
1113cab2bb3Spatrick EXPECT_NE(TSD, nullptr);
1123cab2bb3Spatrick EXPECT_EQ(TSD->Cache.Canary, 0U);
1133cab2bb3Spatrick memset(&TSD->Cache, 0x42, sizeof(TSD->Cache));
1143cab2bb3Spatrick if (UnlockRequired)
1153cab2bb3Spatrick TSD->unlock();
1163cab2bb3Spatrick }
1173cab2bb3Spatrick
TEST(ScudoTSDTest,TSDRegistryBasic)1183cab2bb3Spatrick TEST(ScudoTSDTest, TSDRegistryBasic) {
1193cab2bb3Spatrick testRegistry<MockAllocator<OneCache>>();
1203cab2bb3Spatrick testRegistry<MockAllocator<SharedCaches>>();
1213cab2bb3Spatrick #if !SCUDO_FUCHSIA
1223cab2bb3Spatrick testRegistry<MockAllocator<ExclusiveCaches>>();
1233cab2bb3Spatrick #endif
1243cab2bb3Spatrick }
1253cab2bb3Spatrick
1263cab2bb3Spatrick static std::mutex Mutex;
1273cab2bb3Spatrick static std::condition_variable Cv;
128d89ec533Spatrick static bool Ready;
1293cab2bb3Spatrick
stressCache(AllocatorT * Allocator)1303cab2bb3Spatrick template <typename AllocatorT> static void stressCache(AllocatorT *Allocator) {
1313cab2bb3Spatrick auto Registry = Allocator->getTSDRegistry();
1323cab2bb3Spatrick {
1333cab2bb3Spatrick std::unique_lock<std::mutex> Lock(Mutex);
1343cab2bb3Spatrick while (!Ready)
1353cab2bb3Spatrick Cv.wait(Lock);
1363cab2bb3Spatrick }
1373cab2bb3Spatrick Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
1383cab2bb3Spatrick bool UnlockRequired;
1393cab2bb3Spatrick auto TSD = Registry->getTSDAndLock(&UnlockRequired);
1403cab2bb3Spatrick EXPECT_NE(TSD, nullptr);
1413cab2bb3Spatrick // For an exclusive TSD, the cache should be empty. We cannot guarantee the
1423cab2bb3Spatrick // same for a shared TSD.
1433cab2bb3Spatrick if (!UnlockRequired)
1443cab2bb3Spatrick EXPECT_EQ(TSD->Cache.Canary, 0U);
1453cab2bb3Spatrick // Transform the thread id to a uptr to use it as canary.
1463cab2bb3Spatrick const scudo::uptr Canary = static_cast<scudo::uptr>(
1473cab2bb3Spatrick std::hash<std::thread::id>{}(std::this_thread::get_id()));
1483cab2bb3Spatrick TSD->Cache.Canary = Canary;
1493cab2bb3Spatrick // Loop a few times to make sure that a concurrent thread isn't modifying it.
1503cab2bb3Spatrick for (scudo::uptr I = 0; I < 4096U; I++)
1513cab2bb3Spatrick EXPECT_EQ(TSD->Cache.Canary, Canary);
1523cab2bb3Spatrick if (UnlockRequired)
1533cab2bb3Spatrick TSD->unlock();
1543cab2bb3Spatrick }
1553cab2bb3Spatrick
testRegistryThreaded()1563cab2bb3Spatrick template <class AllocatorT> static void testRegistryThreaded() {
157d89ec533Spatrick Ready = false;
1583cab2bb3Spatrick auto Deleter = [](AllocatorT *A) {
1593cab2bb3Spatrick A->unmapTestOnly();
1603cab2bb3Spatrick delete A;
1613cab2bb3Spatrick };
1623cab2bb3Spatrick std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
1633cab2bb3Spatrick Deleter);
1643cab2bb3Spatrick std::thread Threads[32];
1653cab2bb3Spatrick for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
1663cab2bb3Spatrick Threads[I] = std::thread(stressCache<AllocatorT>, Allocator.get());
1673cab2bb3Spatrick {
1683cab2bb3Spatrick std::unique_lock<std::mutex> Lock(Mutex);
1693cab2bb3Spatrick Ready = true;
1703cab2bb3Spatrick Cv.notify_all();
1713cab2bb3Spatrick }
1723cab2bb3Spatrick for (auto &T : Threads)
1733cab2bb3Spatrick T.join();
1743cab2bb3Spatrick }
1753cab2bb3Spatrick
TEST(ScudoTSDTest,TSDRegistryThreaded)1763cab2bb3Spatrick TEST(ScudoTSDTest, TSDRegistryThreaded) {
1773cab2bb3Spatrick testRegistryThreaded<MockAllocator<OneCache>>();
1783cab2bb3Spatrick testRegistryThreaded<MockAllocator<SharedCaches>>();
1793cab2bb3Spatrick #if !SCUDO_FUCHSIA
1803cab2bb3Spatrick testRegistryThreaded<MockAllocator<ExclusiveCaches>>();
1813cab2bb3Spatrick #endif
1823cab2bb3Spatrick }
183d89ec533Spatrick
184d89ec533Spatrick static std::set<void *> Pointers;
185d89ec533Spatrick
stressSharedRegistry(MockAllocator<SharedCaches> * Allocator)186d89ec533Spatrick static void stressSharedRegistry(MockAllocator<SharedCaches> *Allocator) {
187d89ec533Spatrick std::set<void *> Set;
188d89ec533Spatrick auto Registry = Allocator->getTSDRegistry();
189d89ec533Spatrick {
190d89ec533Spatrick std::unique_lock<std::mutex> Lock(Mutex);
191d89ec533Spatrick while (!Ready)
192d89ec533Spatrick Cv.wait(Lock);
193d89ec533Spatrick }
194d89ec533Spatrick Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
195d89ec533Spatrick bool UnlockRequired;
196d89ec533Spatrick for (scudo::uptr I = 0; I < 4096U; I++) {
197d89ec533Spatrick auto TSD = Registry->getTSDAndLock(&UnlockRequired);
198d89ec533Spatrick EXPECT_NE(TSD, nullptr);
199d89ec533Spatrick Set.insert(reinterpret_cast<void *>(TSD));
200d89ec533Spatrick if (UnlockRequired)
201d89ec533Spatrick TSD->unlock();
202d89ec533Spatrick }
203d89ec533Spatrick {
204d89ec533Spatrick std::unique_lock<std::mutex> Lock(Mutex);
205d89ec533Spatrick Pointers.insert(Set.begin(), Set.end());
206d89ec533Spatrick }
207d89ec533Spatrick }
208d89ec533Spatrick
TEST(ScudoTSDTest,TSDRegistryTSDsCount)209d89ec533Spatrick TEST(ScudoTSDTest, TSDRegistryTSDsCount) {
210d89ec533Spatrick Ready = false;
211d89ec533Spatrick Pointers.clear();
212d89ec533Spatrick using AllocatorT = MockAllocator<SharedCaches>;
213d89ec533Spatrick auto Deleter = [](AllocatorT *A) {
214d89ec533Spatrick A->unmapTestOnly();
215d89ec533Spatrick delete A;
216d89ec533Spatrick };
217d89ec533Spatrick std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
218d89ec533Spatrick Deleter);
219d89ec533Spatrick // We attempt to use as many TSDs as the shared cache offers by creating a
220d89ec533Spatrick // decent amount of threads that will be run concurrently and attempt to get
221d89ec533Spatrick // and lock TSDs. We put them all in a set and count the number of entries
222d89ec533Spatrick // after we are done.
223d89ec533Spatrick std::thread Threads[32];
224d89ec533Spatrick for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
225d89ec533Spatrick Threads[I] = std::thread(stressSharedRegistry, Allocator.get());
226d89ec533Spatrick {
227d89ec533Spatrick std::unique_lock<std::mutex> Lock(Mutex);
228d89ec533Spatrick Ready = true;
229d89ec533Spatrick Cv.notify_all();
230d89ec533Spatrick }
231d89ec533Spatrick for (auto &T : Threads)
232d89ec533Spatrick T.join();
233d89ec533Spatrick // The initial number of TSDs we get will be the minimum of the default count
234d89ec533Spatrick // and the number of CPUs.
235d89ec533Spatrick EXPECT_LE(Pointers.size(), 8U);
236d89ec533Spatrick Pointers.clear();
237d89ec533Spatrick auto Registry = Allocator->getTSDRegistry();
238d89ec533Spatrick // Increase the number of TSDs to 16.
239d89ec533Spatrick Registry->setOption(scudo::Option::MaxTSDsCount, 16);
240d89ec533Spatrick Ready = false;
241d89ec533Spatrick for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
242d89ec533Spatrick Threads[I] = std::thread(stressSharedRegistry, Allocator.get());
243d89ec533Spatrick {
244d89ec533Spatrick std::unique_lock<std::mutex> Lock(Mutex);
245d89ec533Spatrick Ready = true;
246d89ec533Spatrick Cv.notify_all();
247d89ec533Spatrick }
248d89ec533Spatrick for (auto &T : Threads)
249d89ec533Spatrick T.join();
250d89ec533Spatrick // We should get 16 distinct TSDs back.
251d89ec533Spatrick EXPECT_EQ(Pointers.size(), 16U);
252d89ec533Spatrick }
253