kerneltest/e32test/nkernsa/rwspinlock.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 0 a41df078684a
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// e32test\nkernsa\rwspinlock.cpp

//---------------------------------------------------------------------------------------------------------------------
//! @SYMTestCaseID				KBASE-rwspinlock-2442
//! @SYMTestType				UT
//! @SYMTestCaseDesc			Verifying the nkern SpinLock
//! @SYMPREQ					PREQ2094
//! @SYMTestPriority			High
//! @SYMTestActions				
//! 	1. 	RWParallelTest: run a number of reader and writer threads accessing a
//! 		common data block. Each writer completely rewrites the block over and
//! 		over, and the readers verify that the data is consistent.
//! 	2. 	RWOrderingTest: run a number of reader and writer threads which spin-
//! 		wait while holding the spinlock. Each works out the maximum time it
//!         had to wait to acquire the spinlock.
//! 		
//! 
//! @SYMTestExpectedResults
//! 	1.	Properties checked:
//! 		1) readers never see a partial write transaction
//! 		2) the number of writers active is never greater than 1
//! 		3) the number of readers active while a writer is active is 0
//! 		4) more than one reader ran concurrently
//! 	
//! 	2. Properties checked:
//! 		5) Threads acquire the spinlock in the order which they asked for it
//!     	   i.e. neither reader nor writer priority but FIFO
//---------------------------------------------------------------------------------------------------------------------

#include <nktest/nkutils.h>

#ifdef __SMP__

// cheap and cheerful, no side effects please
#define MIN(a, b) ((a)<(b)?(a):(b))

// The spinlock, used throughout
TRWSpinLock RW(TSpinLock::EOrderNone);


///////////////////////////////////////////////
// First test: RWParallelTest
//

// Number of words in the data block
#define BLOCK_SIZE 1024
// Number of write transactions to execute in total (across all writers)
#define WRITE_GOAL 100000

// The data block, the first entry is used as the seed value and is just
// incremented by one each time.
TUint32 Array[BLOCK_SIZE];
// The number of readers currently holding the lock
TUint32 Readers = 0;
// The number of writers currently holding the lock
TUint32 Writers = 0;
// The maximum number of readers that were seen holding the lock at once
TUint32 HighReaders = 0;

void RWParallelReadThread(TAny*)
	{
	// high_r is the maximum number of readers seen by this particular reader
	TUint32 c, r, high_r = 0;
	TBool failed;
	do
		{
		failed = EFalse;

		// Take read lock and update reader count
		RW.LockIrqR();
		__e32_atomic_add_ord32(&Readers, 1);

		// Check property 1
		c = Array[0];
		if (!verify_block_no_trace(Array, BLOCK_SIZE))
			failed = ETrue;

		// Update reader count and release read lock
		r = __e32_atomic_add_ord32(&Readers, (TUint32)-1);
		RW.UnlockIrqR();

		TEST_RESULT(!failed, "Array data inconsistent");

		// Update local high reader count
		if (r > high_r)
			high_r = r;
		}
	while (c < WRITE_GOAL);

	// Update HighReaders if our high reader count is greater
	TUint32 global_high = __e32_atomic_load_acq32(&HighReaders);
	do
		{
		if (global_high >= high_r)
			break;
		}
	while (!__e32_atomic_cas_ord32(&HighReaders, &global_high, high_r));
	}

void RWParallelWriteThread(TAny*)
	{
	TUint32 c, r, w;
	do
		{
		// Take write lock and update writer count
		RW.LockIrqW();
		w = __e32_atomic_add_ord32(&Writers, 1);

		// Get reader count
		r = __e32_atomic_load_acq32(&Readers);

		// Increment seed and recalculate array data
		c = ++Array[0];
		setup_block(Array, BLOCK_SIZE);

		// Update writer count and release write lock
		__e32_atomic_add_ord32(&Writers, (TUint32)-1);
		RW.UnlockIrqW();

		// Check properties 2 and 3
		TEST_RESULT(w == 0, "Multiple writers active");
		TEST_RESULT(r == 0, "Reader active while writing");
		}
	while (c < WRITE_GOAL);
	}

void RWParallelTest()
	{
	TEST_PRINT("Testing read consistency during parallel accesses");

	NFastSemaphore exitSem(0);

	// Set up the block for the initial seed of 0
	setup_block(Array, BLOCK_SIZE);

	// Spawn three readers and a writer for each processor, all equal priority
	TInt cpu;
	TInt threads = 0;
	for_each_cpu(cpu)
		{
		CreateThreadSignalOnExit("RWParallelTestR1", &RWParallelReadThread, 10, NULL, 0, KSmallTimeslice, &exitSem, cpu);
		CreateThreadSignalOnExit("RWParallelTestR2", &RWParallelReadThread, 10, NULL, 0, KSmallTimeslice, &exitSem, cpu);
		CreateThreadSignalOnExit("RWParallelTestR3", &RWParallelReadThread, 10, NULL, 0, KSmallTimeslice, &exitSem, cpu);
		CreateThreadSignalOnExit("RWParallelTestW", &RWParallelWriteThread, 10, NULL, 0, KSmallTimeslice, &exitSem, cpu);
		threads += 4;
		}

	// Wait for all threads to terminate
	while (threads--)
		NKern::FSWait(&exitSem);

	// Check property 4
	TUint r = __e32_atomic_load_acq32(&HighReaders);
	TEST_RESULT(r > 1, "Didn't see concurrent reads");

	TEST_PRINT1("Done, max concurrent readers was %d", r);
	}


///////////////////////////////////////////////
// Second test: RWOrderingTest
//

// Number of times for each thread to try the lock
#define ORDERING_REPEATS 5000
// Time base for spinning
#define SPIN_BASE 100
// Time for read threads to spin (prime)
#define READ_SPIN 7
// Time for write threads to spin (different prime)
#define WRITE_SPIN 11
// Maximum write-thread wait seen
TUint32 MaxWriteWait;
// Maximum read-thread wait seen
TUint32 MaxReadWait;

void RWOrderingThread(TAny* aWrite)
	{
	NThreadBase* us = NKern::CurrentThread();
	TUint32 seed[2] = {(TUint32)us, 0};
	TUint32 c, maxdelay = 0;
	for (c = 0; c < ORDERING_REPEATS; ++c)
		{
		// Disable interrupts to stop preemption disrupting timing
		TInt irq = NKern::DisableAllInterrupts();

		// Time taking lock
		TUint32 before = norm_fast_counter();
		if (aWrite)
			RW.LockOnlyW();
		else
			RW.LockOnlyR();
		TUint32 after = norm_fast_counter();
		TUint32 delay = after - before;
		if (delay > maxdelay)
			maxdelay = delay;

		// Spin for a fixed amount of time
		nfcfspin(SPIN_BASE * (aWrite ? WRITE_SPIN : READ_SPIN));

		// Release lock
		if (aWrite)
			RW.UnlockOnlyW();
		else
			RW.UnlockOnlyR();

		// Reenable interrupts
		NKern::RestoreInterrupts(irq);

		// Sleep for a tick ~50% of the time to shuffle ordering
		if (random(seed) & 0x4000)
			NKern::Sleep(1);
		}

	// Update Max{Read,Write}Wait if ours is higher
	TUint32 global_high = __e32_atomic_load_acq32(aWrite ? &MaxWriteWait : &MaxReadWait);
	do
		{
		if (global_high >= maxdelay)
			break;
		}
	while (!__e32_atomic_cas_ord32(aWrite ? &MaxWriteWait : &MaxReadWait, &global_high, maxdelay));
	
	if (aWrite)
		TEST_PRINT1("Write max delay: %d", maxdelay);
	else
		TEST_PRINT1("Read max delay: %d", maxdelay);
	}

void RWOrderingTest()
	{
	TEST_PRINT("Testing lock acquisition ordering");

	NFastSemaphore exitSem(0);

	TInt cpus = NKern::NumberOfCpus();
	TInt writers, cpu;
	for (writers = 0; writers <= cpus; ++writers)
		{
		TInt readers = cpus - writers;

		// reset maximums
		__e32_atomic_store_rel32(&MaxWriteWait, 0);
		__e32_atomic_store_rel32(&MaxReadWait, 0);

		// start one thread on each cpu, according to readers/writers
		for (cpu = 0; cpu < writers; ++cpu)
			CreateThreadSignalOnExit("RWOrderingTestW", &RWOrderingThread, 10, (TAny*)ETrue, 0, KSmallTimeslice, &exitSem, cpu);
		for (       ; cpu < cpus; ++cpu)
			CreateThreadSignalOnExit("RWOrderingTestR", &RWOrderingThread, 10, (TAny*)EFalse, 0, KSmallTimeslice, &exitSem, cpu);

		// Wait for all threads to terminate
		while (cpu--)
			NKern::FSWait(&exitSem);

		// Get, round, and print maximum delays
		TUint32 w = __e32_atomic_load_acq32(&MaxWriteWait);
		TUint32 r = __e32_atomic_load_acq32(&MaxReadWait);
		w += (SPIN_BASE/2) - 1;
		r += (SPIN_BASE/2) - 1;
		w /= SPIN_BASE;
		r /= SPIN_BASE;
		TEST_PRINT4("%d writers, %d readers, max delay: write %d read %d", writers, readers, w, r);

		// Work out expected delays
		// For writers, we might have every other writer ahead of us, with the readers interleaved
		TUint32 we = ((writers-1) * WRITE_SPIN) + (MIN(readers  , writers) * READ_SPIN);
		// For readers, we might have every writer ahead of us, with the other readers interleaved
		TUint32 re = ((writers  ) * WRITE_SPIN) + (MIN(readers-1, writers) * READ_SPIN);

		// Compare
		if (writers)
			{
			TEST_PRINT1("Expected write %d", we);
			TEST_RESULT(w==we, "Write delay not expected time");
			}
		if (readers)
			{
			TEST_PRINT1("Expected read %d", re);
			TEST_RESULT(r==re, "Read delay not expected time");
			}
		}

	TEST_PRINT("Done");
	}


/////////////////////
// Run all tests
void TestRWSpinLock()
	{
	TEST_PRINT("Testing R/W Spinlocks...");

	RWParallelTest();
	RWOrderingTest();
	}

#else

void TestRWSpinLock()
	{
	TEST_PRINT("Skipping R/W Spinlock tests on uniproc");
	}

#endif