1 /* 2 This file tests BBlockCache from multiple threads to ensure there are 3 no concurrency problems. 4 */ 5 6 7 #include "BlockCacheConcurrencyTest.h" 8 9 #include <stdlib.h> 10 11 #include <BlockCache.h> 12 #include <List.h> 13 14 #include "ThreadedTestCaller.h" 15 16 17 /* 18 * Method: BlockCacheConcurrencyTest::BlockCacheConcurrencyTest() 19 * Descr: This method is the only constructor for the BlockCacheConcurrencyTest 20 * class. 21 */ 22 BlockCacheConcurrencyTest::BlockCacheConcurrencyTest(std::string name) 23 : 24 BThreadedTestCase(name), 25 theObjCache(NULL), 26 theMallocCache(NULL), 27 numBlocksInCache(128), 28 sizeOfBlocksInCache(23), 29 sizeOfNonCacheBlocks(29) 30 { 31 } 32 33 34 /* 35 * Method: BlockCacheConcurrencyTest::~BlockCacheConcurrencyTest() 36 * Descr: This method is the destructor for the BlockCacheConcurrencyTest class. 37 */ 38 BlockCacheConcurrencyTest::~BlockCacheConcurrencyTest() 39 { 40 } 41 42 43 /* 44 * Method: BlockCacheConcurrencyTest::setUp() 45 * Descr: This method creates a couple of BBlockCache instances to perform 46 * tests on. One uses new/delete and the other uses malloc/free 47 * on its blocks. 48 */ 49 void 50 BlockCacheConcurrencyTest::setUp() 51 { 52 theObjCache = new BBlockCache(numBlocksInCache, sizeOfBlocksInCache, 53 B_OBJECT_CACHE); 54 theMallocCache = new BBlockCache(numBlocksInCache, sizeOfBlocksInCache, 55 B_MALLOC_CACHE); 56 } 57 58 59 /* 60 * Method: BlockCacheConcurrencyTest::tearDown() 61 * Descr: This method cleans up the BBlockCache instances which were tested. 62 */ 63 void 64 BlockCacheConcurrencyTest::tearDown() 65 { 66 delete theObjCache; 67 delete theMallocCache; 68 } 69 70 71 /* 72 * Method: BlockCacheConcurrencyTest::GetBlock() 73 * Descr: This method returns a pointer from the BBlockCache, checking 74 * the value before passing it to the caller. 75 */ 76 void * 77 BlockCacheConcurrencyTest::GetBlock(BBlockCache *theCache, size_t blockSize, 78 thread_id theThread, BList *cacheList, BList *nonCacheList) 79 { 80 void *thePtr = theCache->Get(blockSize); 81 82 // The new block should not already be used by this thread. 83 CPPUNIT_ASSERT(!cacheList->HasItem(thePtr)); 84 CPPUNIT_ASSERT(!nonCacheList->HasItem(thePtr)); 85 86 // Add the block to the list of blocks used by this thread. 87 if (blockSize == sizeOfBlocksInCache) { 88 CPPUNIT_ASSERT(cacheList->AddItem(thePtr)); 89 } else { 90 CPPUNIT_ASSERT(nonCacheList->AddItem(thePtr)); 91 } 92 93 // Store the thread id at the start of the block for future 94 // reference. 95 *((thread_id *)thePtr) = theThread; 96 return(thePtr); 97 } 98 99 100 /* 101 * Method: BlockCacheConcurrencyTest::SavedCacheBlock() 102 * Descr: This method passes the pointer back to the BBlockCache 103 * and checks the sanity of the lists. 104 */ 105 void 106 BlockCacheConcurrencyTest::SaveBlock(BBlockCache *theCache, void *thePtr, 107 size_t blockSize, thread_id theThread, BList *cacheList, 108 BList *nonCacheList) 109 { 110 // The block being returned to the cache should still have 111 // the thread id of this thread in it, or some other thread has 112 // perhaps manipulated this block which would indicate a 113 // concurrency problem. 114 CPPUNIT_ASSERT(*((thread_id *)thePtr) == theThread); 115 116 // Remove the item from the appropriate list and confirm it isn't 117 // on the other list for some reason. 118 if (blockSize == sizeOfBlocksInCache) { 119 CPPUNIT_ASSERT(cacheList->RemoveItem(thePtr)); 120 CPPUNIT_ASSERT(!nonCacheList->HasItem(thePtr)); 121 } else { 122 CPPUNIT_ASSERT(!cacheList->HasItem(thePtr)); 123 CPPUNIT_ASSERT(nonCacheList->RemoveItem(thePtr)); 124 } 125 theCache->Save(thePtr, blockSize); 126 } 127 128 129 /* 130 * Method: BlockCacheConcurrencyTest::FreeBlock() 131 * Descr: This method frees the block directly using delete[] or free(), 132 * checking the sanity of the lists as it does the operation. 133 */ 134 void 135 BlockCacheConcurrencyTest::FreeBlock(void *thePtr, size_t blockSize, 136 bool isMallocTest, thread_id theThread, BList *cacheList, 137 BList *nonCacheList) 138 { 139 // The block being returned to the cache should still have 140 // the thread id of this thread in it, or some other thread has 141 // perhaps manipulated this block which would indicate a 142 // concurrency problem. 143 CPPUNIT_ASSERT(*((thread_id *)thePtr) == theThread); 144 145 // Remove the item from the appropriate list and confirm it isn't 146 // on the other list for some reason. 147 if (blockSize == sizeOfBlocksInCache) { 148 CPPUNIT_ASSERT(cacheList->RemoveItem(thePtr)); 149 CPPUNIT_ASSERT(!nonCacheList->HasItem(thePtr)); 150 } else { 151 CPPUNIT_ASSERT(!cacheList->HasItem(thePtr)); 152 CPPUNIT_ASSERT(nonCacheList->RemoveItem(thePtr)); 153 } 154 if (isMallocTest) { 155 free(thePtr); 156 } else { 157 delete[] (uint8*)thePtr; 158 } 159 } 160 161 162 /* 163 * Method: BlockCacheConcurrencyTest::TestBlockCache() 164 * Descr: This method performs the tests on BBlockCache. It is 165 * called by 6 threads concurrently. Three of them are 166 * operating on the B_OBJECT_CACHE instance of BBlockCache 167 * and the other three are operating on the B_MALLOC_CACHE 168 * instance. 169 * 170 * The goal of this method is to perform a series of get, 171 * save and free operations on block from the cache using 172 * "cache size" and "non-cache size" blocks. Also, at the 173 * end of this method, all blocks unfreed by this method are 174 * freed to avoid a memory leak. 175 */ 176 void 177 BlockCacheConcurrencyTest::TestBlockCache(BBlockCache *theCache, 178 bool isMallocTest) 179 { 180 BList cacheList; 181 BList nonCacheList; 182 thread_id theThread = find_thread(NULL); 183 184 // Do everything eight times to ensure the test runs long 185 // enough to check for concurrency problems. 186 for (int j = 0; j < 8; j++) { 187 // Perform a series of gets, saves and frees 188 for (int i = 0; i < numBlocksInCache / 2; i++) { 189 GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList); 190 GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList); 191 GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList); 192 GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList); 193 194 SaveBlock(theCache, cacheList.ItemAt(cacheList.CountItems() / 2), 195 sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList); 196 SaveBlock(theCache, nonCacheList.ItemAt(nonCacheList.CountItems() / 2), 197 sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList); 198 199 GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList); 200 GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList); 201 GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList); 202 GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList); 203 204 FreeBlock(cacheList.ItemAt(cacheList.CountItems() / 2), 205 sizeOfBlocksInCache, isMallocTest, theThread, &cacheList, &nonCacheList); 206 FreeBlock(nonCacheList.ItemAt(nonCacheList.CountItems() / 2), 207 sizeOfNonCacheBlocks, isMallocTest, theThread, &cacheList, &nonCacheList); 208 } 209 bool performFree = false; 210 // Free or save (every other block) for all "cache sized" blocks. 211 while (!cacheList.IsEmpty()) { 212 if (performFree) { 213 FreeBlock(cacheList.LastItem(), sizeOfBlocksInCache, isMallocTest, theThread, &cacheList, 214 &nonCacheList); 215 } else { 216 SaveBlock(theCache, cacheList.LastItem(), sizeOfBlocksInCache, theThread, &cacheList, 217 &nonCacheList); 218 } 219 performFree = !performFree; 220 } 221 // Free or save (every other block) for all "non-cache sized" blocks. 222 while (!nonCacheList.IsEmpty()) { 223 if (performFree) { 224 FreeBlock(nonCacheList.LastItem(), sizeOfNonCacheBlocks, isMallocTest, theThread, &cacheList, 225 &nonCacheList); 226 } else { 227 SaveBlock(theCache, nonCacheList.LastItem(), sizeOfNonCacheBlocks, theThread, &cacheList, 228 &nonCacheList); 229 } 230 performFree = !performFree; 231 } 232 } 233 } 234 235 236 /* 237 * Method: BlockCacheConcurrencyTest::TestThreadMalloc() 238 * Descr: This method passes the BBlockCache instance to TestBlockCache() 239 * where the instance will be tested. 240 */ 241 void 242 BlockCacheConcurrencyTest::TestThreadMalloc() 243 { 244 TestBlockCache(theMallocCache, true); 245 } 246 247 248 /* 249 * Method: BlockCacheConcurrencyTest::TestThreadObj() 250 * Descr: This method passes the BBlockCache instance to TestBlockCache() 251 * where the instance will be tested. 252 */ 253 void 254 BlockCacheConcurrencyTest::TestThreadObj() 255 { 256 TestBlockCache(theObjCache, false); 257 } 258 259 260 /* 261 * Method: BlockCacheConcurrencyTest::suite() 262 * Descr: This static member function returns a test caller for performing 263 * the "BlockCacheConcurrencyTest" test. The test caller 264 * is created as a ThreadedTestCaller with six independent threads. 265 */ 266 CppUnit::Test *BlockCacheConcurrencyTest::suite() 267 { 268 typedef BThreadedTestCaller <BlockCacheConcurrencyTest > 269 BlockCacheConcurrencyTestCaller; 270 271 BlockCacheConcurrencyTest *theTest = new BlockCacheConcurrencyTest(""); 272 BlockCacheConcurrencyTestCaller *threadedTest = new BlockCacheConcurrencyTestCaller("BBlockCache::Concurrency Test", theTest); 273 threadedTest->addThread("A", &BlockCacheConcurrencyTest::TestThreadObj); 274 threadedTest->addThread("B", &BlockCacheConcurrencyTest::TestThreadObj); 275 threadedTest->addThread("C", &BlockCacheConcurrencyTest::TestThreadObj); 276 threadedTest->addThread("D", &BlockCacheConcurrencyTest::TestThreadMalloc); 277 threadedTest->addThread("E", &BlockCacheConcurrencyTest::TestThreadMalloc); 278 threadedTest->addThread("F", &BlockCacheConcurrencyTest::TestThreadMalloc); 279 return(threadedTest); 280 } 281