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 */ 22BlockCacheConcurrencyTest::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 */ 38BlockCacheConcurrencyTest::~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 */ 49void 50BlockCacheConcurrencyTest::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 */ 63void 64BlockCacheConcurrencyTest::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 */ 76void * 77BlockCacheConcurrencyTest::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 */ 105void 106BlockCacheConcurrencyTest::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 */ 134void 135BlockCacheConcurrencyTest::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 */ 176void 177BlockCacheConcurrencyTest::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 */ 241void 242BlockCacheConcurrencyTest::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 */ 253void 254BlockCacheConcurrencyTest::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 */ 266CppUnit::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