kerneltest/e32test/dll/t_tls.cpp
author Mike Kinghan <mikek@symbian.org>
Tue, 16 Nov 2010 14:39:21 +0000
branchGCC_SURGE
changeset 303 9b85206a602c
parent 0 a41df078684a
child 293 0659d0e1a03c
permissions -rw-r--r--
We need a way to pass flags to rombuilds in Raptor via extension flm interfaces, so that the CPP pass of the rom input files can be informed what toolchain we are building with and conditionally include or exclude files depending on whether the toolchain could build them.

// Copyright (c) 1996-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\dll\t_tls.cpp
// Overview:
// Test and benchmark TLS (Thread Local Storage) functions
// API Information:
// UserSvr
// Details:
// - Use the UserSvr methods: DllTls, DllSetTls and DllFreeTls to test 
// the thread local storage. Verify the expected results. Verify that 
// the heap was not corrupted by any of the tests.
// - In a separate thread, use the UserSvr methods: DllTls, DllSetTls and 
// DllFreeTls to test the thread local storage. Verify the expected results.
// - Test the TLS cleanup handlers and verify results are as expected. Verify 
// that the heap was not corrupted by any of the tests.
// - Benchmark how many DllSetTls, DllTls and DllFreeTls operations can be
// performed in 1/2 second.
// Platforms/Drives/Compatibility:
// All.
// Assumptions/Requirement/Pre-requisites:
// Failures and causes:
// Base Port information:
// Tests and benchmarks Tls functions
// 14/10/96 Modified scheme on Arm release
// SetTls - 352	~1300
// Tls - 3510 ~1600
// 15/10/96 Array scheme on Arm release
// Set Tls - ~1900
// Tls - ~2550
// 
//

#define __E32TEST_EXTENSION__

#include <e32test.h>
#include <e32svr.h>
#include <f32dbg.h>
#include <e32def.h>
#include <e32def_private.h>
#include "u32std.h"

// Can't tell whether allocations will be on the kernel or user heap, so perform all heap check
// operations on both
#define HEAP_MARK			__UHEAP_MARK; __KHEAP_MARK
#define HEAP_MARKEND		__UHEAP_MARKEND; __KHEAP_MARKEND
#define HEAP_RESET			__UHEAP_RESET; __KHEAP_RESET
#define HEAP_CHECK(x)		__UHEAP_CHECK(x); __KHEAP_CHECK(x)
#define HEAP_FAILNEXT(x)	__UHEAP_FAILNEXT(x); __KHEAP_FAILNEXT(x)

TInt const KCheckHandle=67338721;
TUint8* const KCheckValue=(TUint8*)8525124;

LOCAL_D RTest test(_L("T_TLS"));

_LIT(KTestDllName, "t_tlsdll");
const TInt KTestDllOrdSet	= 1;
const TInt KTestDllOrdGet	= 2;
const TInt KTestDllOrdFree	= 3;

typedef TInt (*TTestDllSetFn)(TAny*);
typedef TAny* (*TTestDllGetFn)();
typedef void (*TTestDllFreeFn)();

TInt TestDllSetTls(RLibrary aLib, TAny* aValue)
	{
	TTestDllSetFn f = (TTestDllSetFn)aLib.Lookup(KTestDllOrdSet);
	return (*f)(aValue);
	}

TAny* TestDllGetTls(RLibrary aLib)
	{
	TTestDllGetFn f = (TTestDllGetFn)aLib.Lookup(KTestDllOrdGet);
	return (*f)();
	}

void TestDllFreeTls(RLibrary aLib)
	{
	TTestDllFreeFn f = (TTestDllFreeFn)aLib.Lookup(KTestDllOrdFree);
	(*f)();
	}

#define DISPLAY_PROGRESS	test.Printf(_L("Line %d\n"), __LINE__)

void Test()
	{
	test.Start(_L("Stuff without Setting"));

	HEAP_MARK;

	test(UserSvr::DllTls(KCheckHandle)==NULL);
	UserSvr::DllFreeTls(KCheckHandle);
	test(UserSvr::DllTls(KCheckHandle)==NULL);
	test(UserSvr::DllTls(0)==NULL);
	UserSvr::DllFreeTls(0);
	test(UserSvr::DllTls(0)==NULL);

	HEAP_CHECK(0);

	test.Next(_L("Set"));
	test(UserSvr::DllSetTls(KCheckHandle,KCheckValue)==KErrNone);
	test.Next(_L("Get"));
	test(UserSvr::DllTls(KCheckHandle)==KCheckValue);
	test.Next(_L("Free"));
	UserSvr::DllFreeTls(KCheckHandle);
	test(UserSvr::DllTls(KCheckHandle)==NULL);

	HEAP_CHECK(0);

	test.Next(_L("Set lots"));
	TInt i=0;
	for(;i<1000;i+=3)
		{
		test(UserSvr::DllSetTls(KCheckHandle+i,KCheckValue-i)==KErrNone);
		test(UserSvr::DllTls(KCheckHandle+i)==KCheckValue-i);
		}
	for(i=999;i>0;i-=3)
		{
		test(UserSvr::DllTls(KCheckHandle+i)==KCheckValue-i);
		UserSvr::DllFreeTls(KCheckHandle+i);
		test(UserSvr::DllTls(KCheckHandle+i)==NULL);
		}
	test(UserSvr::DllTls(KCheckHandle+i)==KCheckValue-i);
	UserSvr::DllFreeTls(KCheckHandle+i);
	test(UserSvr::DllTls(KCheckHandle+i)==NULL);

	HEAP_MARKEND;


	test.Next(_L("Test with DLL ID"));

	HEAP_MARK;

	test(UserSvr::DllTls(KCheckHandle, 1)==0);
	test(UserSvr::DllTls(KCheckHandle, 2)==0);
	DISPLAY_PROGRESS;
#ifdef _DEBUG
	HEAP_FAILNEXT(1);
	DISPLAY_PROGRESS;
	test(UserSvr::DllSetTls(KCheckHandle, 1, KCheckValue)==KErrNoMemory);
#endif
	test(UserSvr::DllSetTls(KCheckHandle, 1, KCheckValue)==KErrNone);
	test(UserSvr::DllTls(KCheckHandle, 1)==KCheckValue);
	test(UserSvr::DllTls(KCheckHandle, 2)==0);
	DISPLAY_PROGRESS;
	HEAP_FAILNEXT(1);
	DISPLAY_PROGRESS;
	test(UserSvr::DllSetTls(KCheckHandle, 3, KCheckValue)==KErrNone);

#ifdef _DEBUG
	RSemaphore s;
	test(s.CreateLocal(0)==KErrNoMemory);
#endif

	HEAP_RESET;

	test(UserSvr::DllTls(KCheckHandle, 1)==0);
	test(UserSvr::DllTls(KCheckHandle, 2)==0);
	test(UserSvr::DllTls(KCheckHandle, 3)==KCheckValue);
	UserSvr::DllFreeTls(KCheckHandle);

	DISPLAY_PROGRESS;
	HEAP_MARKEND;
	DISPLAY_PROGRESS;

	test.Next(_L("Test reloading DLL"));

	RLibrary l;
	TInt r = l.Load(KTestDllName);
	test(r==KErrNone);
	for (i=0; i<2; ++i)
		{
		test.Printf(_L("i=%d\n"),i);
		test(TestDllGetTls(l)==0);
		test(TestDllSetTls(l, KCheckValue)==KErrNone);
		test(TestDllGetTls(l)==KCheckValue);
		if (i==0)
			{
			TestDllFreeTls(l);
			test(TestDllGetTls(l)==0);
			}
		l.Close();
		r = l.Load(KTestDllName);
		test(r==KErrNone);
		test(TestDllGetTls(l)==0);
		TestDllFreeTls(l);
		}
	l.Close();

	test.End();
	}

TInt TestThread(TAny*)
	{
	RTest test(_L("T_TLS Thread"));
	test.Start(_L("Stuff without Setting"));
	test(UserSvr::DllTls(KCheckHandle)==NULL);
	UserSvr::DllFreeTls(KCheckHandle);
	test(UserSvr::DllTls(KCheckHandle)==NULL);
	test(UserSvr::DllTls(0)==NULL);
	UserSvr::DllFreeTls(0);
	test(UserSvr::DllTls(0)==NULL);
	test.Next(_L("Set"));
	test(UserSvr::DllSetTls(KCheckHandle,KCheckValue)==KErrNone);
	test.Next(_L("Get"));
	test(UserSvr::DllTls(KCheckHandle)==KCheckValue);
	test.Next(_L("Free"));
	UserSvr::DllFreeTls(KCheckHandle);
	test(UserSvr::DllTls(KCheckHandle)==NULL);
	test.Next(_L("Set lots"));
	TInt i=0;
	for(;i<1000;i+=3)
		{
		test(UserSvr::DllSetTls(KCheckHandle+i,KCheckValue-i)==KErrNone);
		test(UserSvr::DllTls(KCheckHandle+i)==KCheckValue-i);
		}
	for(i=999;i>=0;i-=3)
		{
		test(UserSvr::DllTls(KCheckHandle+i)==KCheckValue-i);
		UserSvr::DllFreeTls(KCheckHandle+i);
		test(UserSvr::DllTls(KCheckHandle+i)==NULL);
		}

	test.Close();
	return KErrNone;
	}

TInt HeapSize(RHeap* aHeap = NULL)
	{
	if (!aHeap)
		aHeap = &User::Heap();
	TInt size;
	aHeap->AllocSize(size);
	return size;
	}

TBool TLSStoredOnUserHeap()
	{
	TInt initCount = HeapSize();
	TInt i;
	const TInt KMax = 2;
	for(i = 0 ; i < KMax ; ++i)
		test(UserSvr::DllSetTls(KCheckHandle+i,KCheckValue-i) == KErrNone);
	TInt finalCount = HeapSize();
	for(i = 0 ; i < KMax ; ++i)
		UserSvr::DllFreeTls(KCheckHandle+i);
	return initCount != finalCount;
	}

class RDummyAllocator : public RAllocator
	{
public:
	TInt AccessCount() { return iAccessCount; }
	};

RHeap* MainHeap = NULL;

TInt HeapAccessCount(TAny* aHeap)
	{
	RDummyAllocator* heap = (RDummyAllocator*)aHeap;
	return heap->AccessCount();
	}

TBool HeapExists(RHeap* aHeap)
	{
	RThread thread;
	test_KErrNone(thread.Create(_L("HeapExistsThread"), HeapAccessCount, 0x1000, MainHeap, (TAny*)aHeap));
	TRequestStatus status;
	thread.Logon(status);
	thread.Resume();
	User::WaitForRequest(status);

	TInt r;
	if (thread.ExitType() == EExitKill)
		{
		r = thread.ExitReason();
		test_NotNegative(r);
		}
	else
		{
		test_Equal(EExitPanic, thread.ExitType());
		test_Equal(3, thread.ExitReason());
		r = 0;
		}
	thread.Close();
	
	return r != 0;
	}

TInt TestUserSideTls1Thread(TAny*)
	{
	// Ensure TLS uses initial heap
	if (UserSvr::DllSetTls(KCheckHandle,KCheckValue) != KErrNone)
		return __LINE__;
	UserSvr::DllFreeTls(KCheckHandle);

	RHeap* tlsHeap = &User::Heap();
	TInt tlsInitSize = HeapSize(tlsHeap);

	// Switch heap
	RHeap* newHeap = User::ChunkHeap(NULL, 0x1000, 0x1000);
	if (!newHeap)
		return __LINE__;
	TInt newInitSize = HeapSize(newHeap);
	User::SwitchHeap(newHeap);
	tlsHeap->Close();

	// Allocate more TLS data
	for(TInt i = 0 ; i < 100 ; ++i)
		{
		if (UserSvr::DllSetTls(KCheckHandle+i,KCheckValue-i) != KErrNone)
			return __LINE__;
		}

	// Test TLS data was allocated on original heap
	if (!(HeapSize(tlsHeap) > tlsInitSize))
		return __LINE__;
	if (HeapSize(newHeap) != newInitSize)
		return __LINE__;
	
	return KErrNone;
	}

void TestUserSideTls1()
	{
	test.Next(_L("Test user-side TLS behaviour when switching heaps"));

	RThread thread;
	test_KErrNone(thread.Create(_L("TestUserSideTls1Thread"), TestUserSideTls1Thread, 0x1000, 0x1000, 0x1000, 0));
	
	TRequestStatus status;
	thread.Logon(status);
	thread.Resume();
	User::WaitForRequest(status);
	
	test_Equal(EExitKill, thread.ExitType());
	test_Equal(KErrNone, thread.ExitReason());
	thread.Close();
	}

TInt TestUserSideTls2Thread(TAny*)
	{
	// Allocate some TLS data
	for(TInt i = 0 ; i < 100 ; ++i)
		{
		if (UserSvr::DllSetTls(KCheckHandle+i,KCheckValue-i) != KErrNone)
			return __LINE__;
		}

	// Exit normally
	return KErrNone;
	}

void TestUserSideTls2()
	{
	test.Next(_L("Test user-side TLS data cleanup on thread exit"));

	RHeap* tlsHeap = User::ChunkHeap(NULL, 0x1000, 0x1000);
	test_NotNull(tlsHeap);
	TInt initSize = HeapSize(tlsHeap);

	RThread thread;
	test_KErrNone(thread.Create(_L("TestUserSideTls2Thread"), TestUserSideTls2Thread, 0x1000, tlsHeap, 0));
	TThreadId id = thread.Id();
	
	TRequestStatus status;
	thread.Logon(status);
	thread.Resume();
	User::WaitForRequest(status);
	
	test_Equal(EExitKill, thread.ExitType());
	test_Equal(KErrNone, thread.ExitReason());
	thread.Close();

	// Check TLS data freed
	test_Equal(initSize, HeapSize(tlsHeap));	
	tlsHeap->Close();

	// Check heap no longer exists
	test(!HeapExists(tlsHeap));

	// Check thread no longer exists
	RThread thread2;
	test_Equal(KErrNotFound, thread2.Open(id));
	}

TInt TestUserSideTls3Thread(TAny*)
	{
	// Allocate some TLS data
	for(TInt i = 0 ; i < 100 ; ++i)
		{
		if (UserSvr::DllSetTls(KCheckHandle+i,KCheckValue-i) != KErrNone)
			return __LINE__;
		}

	// Panic
	User::Panic(_L("Test"), 999);
	return KErrNone;
	}

void TestUserSideTls3()
	{
	test.Next(_L("Test user-side TLS data cleanup on thread panic"));

	RHeap* tlsHeap = User::ChunkHeap(NULL, 0x1000, 0x1000);
	test_NotNull(tlsHeap);

	RThread thread;
	test_KErrNone(thread.Create(_L("TestUserSideTls3Thread"), TestUserSideTls3Thread, 0x1000, tlsHeap, 0));
	TThreadId id = thread.Id();
	
	TRequestStatus status;
	thread.Logon(status);
	thread.Resume();
	User::WaitForRequest(status);
	
	test_Equal(EExitPanic, thread.ExitType());
	test_Equal(999, thread.ExitReason());
	
	thread.Close();
	tlsHeap->Close();
	
	// Check heap no longer exists
	test(!HeapExists(tlsHeap));

	// Check thread no longer exists
	RThread thread2;
	test_Equal(KErrNotFound, thread2.Open(id));
	}

TInt TestUserSideTls4Thread(TAny*)
	{
	// Shouldn't ever run
	return KErrGeneral;
	}

void TestUserSideTls4()
	{
	test.Next(_L("Test user-side TLS data cleanup on early thread kill"));
	
	RHeap* tlsHeap = User::ChunkHeap(NULL, 0x1000, 0x1000);
	test_NotNull(tlsHeap);

	RThread thread;
	test_KErrNone(thread.Create(_L("TestUserSideTls4Thread"), TestUserSideTls4Thread, 0x1000, tlsHeap, 0));
	TThreadId id = thread.Id();
	
	thread.Kill(101);

	TRequestStatus status;
	thread.Logon(status);
	User::WaitForRequest(status);
	
	test_Equal(EExitKill, thread.ExitType());
	test_Equal(101, thread.ExitReason());
	
	thread.Close();
	tlsHeap->Close();
	
	// Check heap no longer exists
	test(!HeapExists(tlsHeap));

	// Check thread no longer exists
	RThread thread2;
	test_Equal(KErrNotFound, thread2.Open(id));
	}

#if 0
class CTest : public CBase
	{
public:
	static CTest* New(TInt aSize, TInt aTls, TInt aLinkedTls);
	virtual ~CTest();
public:
	TAny* iAlloc;
	TInt iTls;
	TInt iLinkedTls;
	};

void TlsCleanup2(TAny* a)
	{
	delete ((CTest*)a);
	}

CTest::~CTest()
	{
	RDebug::Print(_L("~CTest %d %d"), iTls, iLinkedTls);
	User::Free(iAlloc);
	UserSvr::DllFreeTls(iTls);
	if (iLinkedTls)
		{
		CTest* p = (CTest*)UserSvr::DllTls(iLinkedTls);
		if (p)
			delete p;
		}
	}

CTest* CTest::New(TInt aSize, TInt aTls, TInt aLinkedTls)
	{
	CTest* p = new CTest;
	test(p!=NULL);
	p->iTls = aTls;
	p->iAlloc = User::Alloc(aSize);
	test(p->iAlloc!=NULL);
	p->iLinkedTls = aLinkedTls;
	test(UserSvr::DllSetTls(aTls, p, TlsCleanup2)==KErrNone);
	return p;
	}


void TlsCleanupCheck(TAny*)
	{
	__UHEAP_MARKEND;
	}

void TlsCleanup1(TAny* a)
	{
	User::Free(a);
	}

TInt TlsCleanupThread1(TAny* a)
	{
	__UHEAP_MARK;
	TInt n = (TInt)a;
	TInt i;
	TInt r = UserSvr::DllSetTls(0, NULL, TlsCleanupCheck);
	if (r!=KErrNone)
		return r;
	for (i=0; i<n; ++i)
		{
		TAny* p = User::Alloc(64);
		if (!p)
			return KErrNoMemory;
		r = UserSvr::DllSetTls(i+1, p, TlsCleanup1);
		if (r!=KErrNone)
			return r;
		}
	return KErrNone;
	}

TInt TlsCleanupThread2(TAny*)
	{
	__UHEAP_MARK;
	TInt r = UserSvr::DllSetTls(0, NULL, TlsCleanupCheck);
	if (r!=KErrNone)
		return r;

	CTest::New(256, 1, 2);
	CTest::New(256, 2, 3);
	CTest::New(256, 3, 0);
	CTest::New(256, 6, 5);
	CTest::New(256, 5, 4);
	CTest::New(256, 4, 0);
	CTest::New(256, 9, 8);
	CTest::New(256, 8, 7);
	CTest::New(256, 7, 9);
	CTest::New(256, 10, 11);
	CTest* p = CTest::New(256, 11, 12);
	CTest::New(256, 12, 0);

	delete p;

	return KErrNone;
	}

void DisplayExitInfo(RThread t)
	{
	TInt exitType = t.ExitType();
	TInt exitReason = t.ExitReason();
	TBuf<32> exitCat = t.ExitCategory();
	test.Printf(_L("Exit Info: %d,%d,%S\n"), exitType, exitReason, &exitCat);
	}

void DoTestCleanupHandler(TThreadFunction aFunc, TAny* aArg)
	{
	RThread t;
	TInt r = t.Create(KNullDesC, aFunc, 0x1000, NULL, aArg);
	test(r==KErrNone);
	TRequestStatus s;
	t.Logon(s);
	t.Resume();
	User::WaitForRequest(s);
	DisplayExitInfo(t);
	test(t.ExitType()==EExitKill);
	test(t.ExitReason()==KErrNone);
	test(s==KErrNone);
	CLOSE_AND_WAIT(t);
	}

void TestCleanupHandler()
	{
	HEAP_MARK;

	test.Start(_L("Test TLS cleanup handlers"));
	DoTestCleanupHandler(TlsCleanupThread1, (TAny*)1 );
	DoTestCleanupHandler(TlsCleanupThread1, (TAny*)2 );
	DoTestCleanupHandler(TlsCleanupThread1, (TAny*)3 );
	DoTestCleanupHandler(TlsCleanupThread2, NULL );

	test.End();

	HEAP_MARKEND;
	}
#endif

void Benchmark()
	{
	const TInt KMaxEntries=(512*1024)/sizeof(STls); // limits TLS entries to 512K of storage
	const TInt KMaxTime=500000; // limits time to half a second
	
	HEAP_MARK;

	test.Start(_L("SetTls()"));//Note: much slower if done in reverse order
	TInt count=0;
	TTime start;
	TTime finish;
	TTime now;
	start.HomeTime();
	finish.HomeTime();
	finish+=TTimeIntervalMicroSeconds(KMaxTime);
	while (now.HomeTime(),now<finish && count<KMaxEntries)
		{
		test(UserSvr::DllSetTls(count,KCheckValue)==KErrNone);
		count++;
		}
	TTimeIntervalMicroSeconds elapsed=now.MicroSecondsFrom(start);
	TInt scount=(TInt)((count*500000.0)/elapsed.Int64()); // scale count up to 1/2 second
	test.Printf(_L("     Done %d in 1/2 sec\n"),scount);
	if (count==KMaxEntries)
		test.Printf(_L("     (%d in %f sec)\n"),count,elapsed.Int64()/1000000.0);

	TInt max=count;

	test.Next(_L("Tls()"));
	TInt i=0;
	count=0;
	finish.HomeTime();
	finish+=TTimeIntervalMicroSeconds(KMaxTime);
	while (now.HomeTime(),now<finish)
		{
		test(UserSvr::DllTls(i)==KCheckValue);
		count++;
		if (++i==max)
			i=0;
		}
	elapsed=now.MicroSecondsFrom(start);
	scount=(TInt)((count*500000.0)/elapsed.Int64()); // scale count up to 1/2 second
	test.Printf(_L("     Done %d in 1/2 sec\n"),scount);

	test.Next(_L("FreeTls()"));//Note: much faster if done in reverse order
	count=0;
	start.HomeTime();
	finish.HomeTime();
	finish+=TTimeIntervalMicroSeconds(KMaxTime);
	while (now.HomeTime(),now<finish)
		{
		UserSvr::DllFreeTls(count++);
		if (count==max)
			break;
		}
	elapsed=now.MicroSecondsFrom(start);
	scount=(TInt)((count*500000.0)/elapsed.Int64()); // scale count up to 1/2 second
	test.Printf(_L("     Done %d in 1/2 sec\n"),scount);
	if (count==max)
		test.Printf(_L("     (%d in %f sec)\n"),count,elapsed.Int64()/1000000.0);
	
	while (count<max)
		UserSvr::DllFreeTls(--max);

	HEAP_MARKEND;
	test.End();
	}

TInt E32Main()
	{

	test.Title();
	test.Start(_L("Test"));

	// Turn off lazy dll unloading
	RLoader l;
	test(l.Connect()==KErrNone);
	test(l.CancelLazyDllUnload()==KErrNone);
	l.Close();

	Test();
	test.Next(_L("Thread Test"));

	HEAP_MARK;

	RThread thread;
	TInt r=thread.Create(_L("TLS test thread"),TestThread,KDefaultStackSize,0x1000,0x8000,NULL);
	test(r==KErrNone);
	TRequestStatus stat;
	thread.Logon(stat);
	thread.Resume();
	User::WaitForRequest(stat);
	test(stat==KErrNone);
	test(thread.ExitCategory()==_L("Kill"));
	test(thread.ExitType()==EExitKill); 
	test(thread.ExitReason()==KErrNone); 
	CLOSE_AND_WAIT(thread);
	
	HEAP_MARKEND;

	if (TLSStoredOnUserHeap())
		{
		MainHeap = &User::Heap();
		TestUserSideTls1();
		TestUserSideTls2();
		TestUserSideTls3();
		TestUserSideTls4();		
		}
	
	User::After(100);
	
	// On serial screen systems, WSERV may not yet have completed the disconnect
	// from the second thread. This causes the kernel heap check to fail.
	// So we wait here until WSERV can run.
	test.Printf(_L("Wait for WSERV\n"));

//	TestCleanupHandler();

	test.Next(_L("Benchmark"));
	Benchmark();
	test.End();
	return KErrNone;
	}