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