xref: /haiku/src/tests/kits/support/bblockcache/BlockCacheConcurrencyTest.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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