sl@0: // Copyright (c) 2004-2009 Nokia Corporation and/or its subsidiary(-ies).
sl@0: // All rights reserved.
sl@0: // This component and the accompanying materials are made available
sl@0: // under the terms of "Eclipse Public License v1.0"
sl@0: // which accompanies this distribution, and is available
sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html".
sl@0: //
sl@0: // Initial Contributors:
sl@0: // Nokia Corporation - initial contribution.
sl@0: //
sl@0: // Contributors:
sl@0: //
sl@0: // Description:
sl@0: //
sl@0: 
sl@0: #include "srvrepos_noc.h"
sl@0: #include "cachemgr.h"
sl@0: #include <bsul/bsul.h>
sl@0: 
sl@0: #define UNUSED_VAR(a) a = a
sl@0: 
sl@0: _LIT(KCacheLit, "CoarseGrainedCache");
sl@0: _LIT(KDefaultCacheSizeLit, "size");
sl@0: _LIT(KDefaultEvictionTimeoutLit, "timeout");
sl@0:     
sl@0: CRepositoryCacheManager* CRepositoryCacheManager::NewLC(RFs& aFs)
sl@0: 	{
sl@0: 	CRepositoryCacheManager* self = new(ELeave) CRepositoryCacheManager;
sl@0: 	CleanupStack::PushL(self);
sl@0: 	self->ConstructL(aFs);
sl@0: 	return self;
sl@0: 	}
sl@0: 
sl@0: CRepositoryCacheManager::~CRepositoryCacheManager()
sl@0: 	{
sl@0: 	DisableCache(ETrue);
sl@0: 	}
sl@0: 
sl@0: void CRepositoryCacheManager::ConstructL(RFs& aFs)
sl@0: 	{
sl@0: 	CTimer::ConstructL();
sl@0: 	
sl@0: 	BSUL::CIniFile16* iniFile = NULL;
sl@0: 	TInt res = KErrNone;
sl@0: 	TBuf<KMaxFileName> iniFileName;
sl@0: 	
sl@0: 	iniFileName.Copy( *TServerResources::iInstallDirectory );
sl@0: 	iniFileName.Append( KCacheMgrIniFile );	
sl@0: 	TRAP(res, iniFile = BSUL::CIniFile16::NewL(aFs, iniFileName, ETrue));
sl@0: 	if(res==KErrNotFound)
sl@0: 		{
sl@0: 		// if RomDirectory exists
sl@0: 		if (TServerResources::iRomDirectory)
sl@0: 			{
sl@0: 			iniFileName.Copy( *TServerResources::iRomDirectory );
sl@0: 			iniFileName.Append( KCacheMgrIniFile );	
sl@0: 			TRAP(res, iniFile = BSUL::CIniFile16::NewL(aFs, iniFileName, ETrue));
sl@0: 			}
sl@0: 		if(res==KErrNotFound)
sl@0: 			{
sl@0: 			__CENTREP_TRACE1("CENTREP: Central Repository Cache Parameters ini file %S not found. Default values will be used.", &KCacheMgrIniFile);
sl@0: 			return;
sl@0: 			}
sl@0: 		}
sl@0: 	if (res != KErrNone)
sl@0: 		{
sl@0: 		User::Leave(res);
sl@0: 		}
sl@0: 		
sl@0: 	CleanupStack::PushL(iniFile);
sl@0: 	
sl@0: 	TBuf<20> buffer;
sl@0: 	TPtrC ptr(buffer);
sl@0: 	
sl@0: 	// find the value
sl@0: 	res = iniFile->FindVar(KCacheLit(), KDefaultCacheSizeLit(), ptr);
sl@0: 	TLex lex(ptr);
sl@0: 
sl@0: 	TBool valueFound = EFalse;
sl@0: 	
sl@0: 	// if the value can't be found or can't be converted into a positive integer, use the default
sl@0: 	if (res != KErrNone || lex.Val(iCacheSize) != KErrNone || iCacheSize < 0)	
sl@0: 		{
sl@0: 		iCacheSize = KDefaultCacheSize;
sl@0: 		}
sl@0: 	else
sl@0: 		{
sl@0: 		valueFound = ETrue;			
sl@0: 		}
sl@0: 		
sl@0: 	// if the value can be found, convert it
sl@0: 	if (iniFile->FindVar(KCacheLit(), KDefaultEvictionTimeoutLit(), ptr) == KErrNone)
sl@0: 		{
sl@0: 		TInt tempTimeout;
sl@0: 		lex.Assign(ptr);
sl@0: 		// if the value can be converted into a positive integer, assign it to iDefaultTimeout.
sl@0: 		if (lex.Val(tempTimeout) == KErrNone && tempTimeout >= 0)
sl@0: 			{
sl@0: 			iDefaultTimeout = tempTimeout;
sl@0: 			valueFound = ETrue;
sl@0: 			}
sl@0: 		}
sl@0: 	
sl@0: #ifdef _DEBUG
sl@0: 	// in Debug mode, if the Cache ini file exists either in install directory or 
sl@0: 	// rom directory but does not contains the correct section "CoarseGrainedCache"
sl@0: 	// nor any key of "size" and "timeout", the server panics.
sl@0: 	if(! valueFound)
sl@0: 	{
sl@0: 	Panic(ECacheIniFileCorrupted);
sl@0: 	}
sl@0: #else
sl@0: 	UNUSED_VAR(valueFound);
sl@0: #endif		
sl@0: 
sl@0: 	CleanupStack::PopAndDestroy(iniFile);
sl@0: 	}
sl@0: 
sl@0: void CRepositoryCacheManager::EnableCache(TInt aDefaultTimeout, TInt aCacheSize)
sl@0: 	{
sl@0: 	if (aDefaultTimeout>0)
sl@0: 		{
sl@0: 		iDefaultTimeout = aDefaultTimeout;
sl@0: 		}
sl@0: 	if (aCacheSize>0)
sl@0: 		{
sl@0: 		iCacheSize = aCacheSize;
sl@0: 		}
sl@0: 	
sl@0: 	EnableCache();
sl@0: 	}
sl@0: 
sl@0: void CRepositoryCacheManager::EnableCache()
sl@0: 	{
sl@0: 	// If disabled, enable
sl@0: 	if (!iEnabled)
sl@0: 		{
sl@0: 		iEnabled = ETrue;
sl@0: 		__CENTREP_TRACE2("CENTREP: Cache Enabled. Size:%d Default Timeout:%d", iCacheSize, iDefaultTimeout.Int());
sl@0: 		}
sl@0: 	}
sl@0: 		
sl@0: void CRepositoryCacheManager::DisableCache(TBool aFullFlush)
sl@0: 	{
sl@0: 	// If enabled, disable
sl@0: 	if (iEnabled)
sl@0: 		{
sl@0: 		// cancel any outstanding timer
sl@0: 		Cancel();
sl@0: 
sl@0: 		FlushCache(aFullFlush);
sl@0: 
sl@0: 		iEnabled = EFalse;
sl@0: 		__CENTREP_TRACE("CENTREP: Cache Disabled");
sl@0: 		}
sl@0: 	}
sl@0: 
sl@0: void CRepositoryCacheManager::RescheduleTimer(const TTime& aTimeInUTC)
sl@0: 	{
sl@0: 	
sl@0: 	TTime now;
sl@0: 	now.UniversalTime();
sl@0: 	
sl@0: 	//Get the 64bit time interval between now and the cache timeout
sl@0: 	TTimeIntervalMicroSeconds interval64 = aTimeInUTC.MicroSecondsFrom(now);
sl@0: 	TTimeIntervalMicroSeconds32 interval32(iDefaultTimeout);
sl@0: 	
sl@0: 	//If the interval is positive, i.e. the timeout is in the future, convert 
sl@0: 	//this interval to a 32 bit value, otherwise use the default timeout
sl@0: 	if(interval64 > 0)
sl@0: 		{
sl@0: 		//If the interval value is less than the maximum 32 bit value cast
sl@0: 		//interval to 32 bit value, otherwise the interval is too large for 
sl@0: 		//a 32 bit value so just set the interval to the max 32 bit value
sl@0: 		const TInt64 KMax32BitValue(KMaxTInt32);
sl@0: 		interval32 = (interval64 <= KMax32BitValue) ? 
sl@0: 				static_cast<TTimeIntervalMicroSeconds32>(interval64.Int64()): KMaxTInt32;
sl@0: 		}
sl@0: 
sl@0: 	//Reschedule the timer
sl@0: 	After(interval32);
sl@0: 
sl@0: 	}
sl@0: 
sl@0: void CRepositoryCacheManager::RemoveIdleRepository(CSharedRepository* aRepository)
sl@0: 	{
sl@0: 	if (iEnabled)
sl@0: 		{
sl@0: 		TInt i;
sl@0: 		TInt count=iIdleRepositories.Count();
sl@0: 		for(i=count-1; i>=0; i--)
sl@0: 			{
sl@0: 			if(iIdleRepositories[i].iSharedRepository==aRepository)
sl@0: 				{
sl@0: 				break;
sl@0: 				}
sl@0: 			}
sl@0: 		
sl@0: 		// Idle repository might not be found in the list if multiple clients try to open the same 
sl@0: 		// repository at the same time. First client will remove it and second one will not find it
sl@0: 		if(i>=0)
sl@0: 			{
sl@0: 			__CENTREP_TRACE1("CENTREP: Cache Hit when opening repository %x", aRepository->Uid().iUid);
sl@0: 
sl@0: 			iTotalCacheUsage -= iIdleRepositories[i].iSharedRepository->Size();		
sl@0: 			iIdleRepositories.Remove(i);
sl@0: 			
sl@0: 			// If this was the first repository on the list, it means its timer is still ticking. 
sl@0: 			// We have to stop it and ...
sl@0: 			if (i==0)
sl@0: 				{
sl@0: 				Cancel();
sl@0: 				// if there's still other repositories in the list, reschedule the timer with the
sl@0: 				// new top-of-the-list  
sl@0: 				if (iIdleRepositories.Count())
sl@0: 					{
sl@0: 					RescheduleTimer(iIdleRepositories[0].iCacheTime);
sl@0: 					}
sl@0: 				}
sl@0: 			}
sl@0: 		else
sl@0: 			{
sl@0: 			__CENTREP_TRACE1("CENTREP: Cache Miss when opening repository %x", aRepository->Uid().iUid);
sl@0: 			}
sl@0: 		}
sl@0: 	}
sl@0: 
sl@0: #ifdef CACHE_OOM_TESTABILITY
sl@0:   	// This code is only for tesing and doesn't go into MCL
sl@0: 	// Hence hide the leave in a macro instead of making StartEvictionL
sl@0: #define TEST_CODE_LEAVE(x) User::Leave(x)
sl@0: #endif	
sl@0: 
sl@0: TBool CRepositoryCacheManager::StartEviction(CSharedRepository*& aRepository)
sl@0: 	{
sl@0: 	// find the item in the cache and remove it if it exists to reset the timer
sl@0: 	RemoveIdleRepository(aRepository);
sl@0: 
sl@0: 	TInt64 lastTop = 0;
sl@0: 	
sl@0: 	if (iIdleRepositories.Count())
sl@0: 		{
sl@0: 		lastTop = iIdleRepositories[0].iCacheTime.Int64();
sl@0: 		}
sl@0: 
sl@0: 	// Execute the forced eviction algorithm only if it will make sense
sl@0: 	// The eviction makes sense if:
sl@0: 	// - there's anything in the cache to evict
sl@0: 	// - the repository we're trying to cache can fit in the cache after evictions
sl@0: 	if (iIdleRepositories.Count() && (aRepository->Size() < iCacheSize))
sl@0: 		{
sl@0: 		// Check to see if current cache size + the current repository size is overshooting the limit
sl@0: 		if (iTotalCacheUsage + aRepository->Size() > iCacheSize)
sl@0: 			{
sl@0: 			// Forced eviction
sl@0: 			__CENTREP_TRACE3("CENTREP: Cache Size Exceeded: Current(%d)+Size(%d)>Cache(%d)", iTotalCacheUsage, aRepository->Size(), iCacheSize);
sl@0: 			
sl@0: 			// Sort in the forced eviction order
sl@0: 			TLinearOrder<TRepositoryCacheInfo> forcedSortOrder(CRepositoryCacheManager::ForcedEvictionSortOrder);
sl@0: 			iIdleRepositories.Sort(forcedSortOrder);
sl@0: 			
sl@0: 			// Evict one by one until there's enough cache space or we run out of idle reps
sl@0: 			do
sl@0: 				{
sl@0: 				__CENTREP_TRACE1("CENTREP: Forced Eviction of repository %x", iIdleRepositories[0].iSharedRepository->Uid().iUid);			
sl@0: 				iTotalCacheUsage -= iIdleRepositories[0].iSharedRepository->Size();
sl@0: 				Evict(0);
sl@0: 				iIdleRepositories.Remove(0);		
sl@0: 				} while (iIdleRepositories.Count() && (iTotalCacheUsage + aRepository->Size() > iCacheSize));
sl@0: 			
sl@0: #ifdef CENTREP_TRACE			
sl@0: 			if (!iIdleRepositories.Count())
sl@0: 				{
sl@0: 				__CENTREP_TRACE("CENREP: Cache Emptied by Forced Eviction");
sl@0: 				}
sl@0: #endif				
sl@0: 			// Re-sort to timer order for normal operation
sl@0: 			TLinearOrder<TRepositoryCacheInfo> timerSortOrder(CRepositoryCacheManager::TimerEvictionSortOrder);
sl@0: 			iIdleRepositories.Sort(timerSortOrder);
sl@0: 			};
sl@0: 		}
sl@0: 	
sl@0: 	// See if there's enough space now
sl@0: 	if (iTotalCacheUsage + aRepository->Size() > iCacheSize)
sl@0: 		{
sl@0: 		return EFalse;
sl@0: 		}
sl@0: 
sl@0: 	// Create new item for the cache and insert it in the list
sl@0: 	TRepositoryCacheInfo repInfo;
sl@0: 	
sl@0: 	repInfo.iCacheTime.UniversalTime();
sl@0: 	repInfo.iCacheTime += TTimeIntervalMicroSeconds32(iDefaultTimeout);
sl@0: 	repInfo.iSharedRepository = aRepository;
sl@0: 	
sl@0: 	TLinearOrder<TRepositoryCacheInfo> timerSortOrder(CRepositoryCacheManager::TimerEvictionSortOrder);
sl@0: 	// With the same timeout value assigned to all repositories, no two repositories can have the same 
sl@0: 	// timeout theoretically, so InsertInOrder is sufficient. But in practice, because of the poor 
sl@0: 	// resolution of the NTickCount() function used by TTime::UniversalTime(), InsertInOrderAllowRepeats 
sl@0: 	// should be used instead of InsertInOrder to allow for duplicate timer values caused by two 
sl@0: 	// repositories cached in quick succession (<1ms)
sl@0: 	TInt err = iIdleRepositories.InsertInOrderAllowRepeats(repInfo, timerSortOrder);
sl@0: #ifdef CACHE_OOM_TESTABILITY
sl@0:   	// This code is only for tesing and doesn't go into MCL
sl@0:   	if (err == KErrNoMemory)	
sl@0:   		{
sl@0:   		TServerResources::iObserver->RemoveOpenRepository(aRepository);
sl@0:   		aRepository = NULL;
sl@0:   		// Should Leave here for the OOM tests to successfully complete. 
sl@0: 		TEST_CODE_LEAVE(err);
sl@0:   		}
sl@0: #endif	
sl@0: 	if (err!=KErrNone)
sl@0: 		{
sl@0: 		return EFalse;
sl@0: 		}
sl@0: 
sl@0: 	iTotalCacheUsage += repInfo.iSharedRepository->Size();
sl@0: 	
sl@0: 	// Only reset timer if necessary. This operation takes time and doing it every time reduces performance considerably
sl@0: 	if (lastTop != iIdleRepositories[0].iCacheTime.Int64())
sl@0: 		{
sl@0: 		// reset timer to the new top-of-the-list
sl@0: 		Cancel();
sl@0: 		RescheduleTimer(iIdleRepositories[0].iCacheTime);
sl@0: 		}
sl@0: 		
sl@0: 	return ETrue;
sl@0: 	}
sl@0: 
sl@0: void CRepositoryCacheManager::FlushCache(TBool aFullFlush)
sl@0: 	{
sl@0: 	// iterate through idle repositories (loaded in memory, scheduled to be evicted)
sl@0: 	TInt idleRepCount = iIdleRepositories.Count();
sl@0: 	for(TInt repCount = idleRepCount - 1; repCount >= 0 ; repCount--)	
sl@0: 		{
sl@0: 		// check if there are any observers listening (to see if any client is connected to this repository)
sl@0: 		if (aFullFlush || (TServerResources::iObserver->FindConnectedRepository(iIdleRepositories[repCount].iSharedRepository->Uid())==KErrNotFound))
sl@0: 			{
sl@0: 			// if the client has already been disconnected, unload from memory
sl@0: 			Evict(repCount);
sl@0: 			}
sl@0: 		}
sl@0: 	// this whole iteration and search above can be replaced by a simple reference counter variable check,
sl@0: 	// if the server is redesigned using a resource manager type pattern with CSharedRepository object as a resource
sl@0: 	
sl@0: 	// empty the list
sl@0: 	iIdleRepositories.Reset();
sl@0: 	
sl@0: 	iTotalCacheUsage = 0;
sl@0: 	__CENTREP_TRACE1("CENTREP: Cache Flush: %d repositories flushed", idleRepCount);
sl@0: 	}
sl@0: 	
sl@0: // Evict removes items from iOpenRepositories list, not from iIdleRepositories list
sl@0: void CRepositoryCacheManager::Evict(TInt aIdleRepIndex)
sl@0: 	{
sl@0: 	// find,remove and delete the idle repositories' pointers in the open repositories list 
sl@0: 	TServerResources::iObserver->RemoveOpenRepository(iIdleRepositories[aIdleRepIndex].iSharedRepository);
sl@0: 	}
sl@0: 		
sl@0: void CRepositoryCacheManager::RunL()
sl@0: 	{
sl@0: 	TTime now;
sl@0: 
sl@0: 	now.UniversalTime();
sl@0: 
sl@0: 	TInt count = iIdleRepositories.Count();
sl@0: 	
sl@0: 	// repositories that are involved in active transactions are not idle.
sl@0: 	// this checks to make sure that we're not trying to reclaim memory that
sl@0: 	// is actually still currently in use.
sl@0: 	
sl@0: 	for (TInt i = 0;i < count;i++)
sl@0: 		{
sl@0: 		if (iIdleRepositories[i].iCacheTime > now)
sl@0: 			{
sl@0: 			break;
sl@0: 			}
sl@0: 		
sl@0: 		if (iIdleRepositories[i].iSharedRepository->IsTransactionActive())
sl@0: 			{
sl@0: 			__CENTREP_TRACE1("CRepositoryCacheManager::RunL - rescheduling UID 0x%x, in active transaction",
sl@0: 					iIdleRepositories[i].iSharedRepository->Uid().iUid);
sl@0: 			StartEviction(iIdleRepositories[i].iSharedRepository);
sl@0: 			return;
sl@0: 			}
sl@0: 		}
sl@0: 	
sl@0: 
sl@0: 	// Try to evict all the repositories which have expired. There might be more than one repository
sl@0: 	// destined to expire at the same time, or there are more than one repository with expiry times
sl@0: 	// between the scheduled expiry time and now (which theoretically should have been the same, but maybe
sl@0: 	// because of other procesor activity, the timer Active Object just got late a bit)
sl@0: 	while((iIdleRepositories.Count()) && (iIdleRepositories[0].iCacheTime<=now))
sl@0: 		{
sl@0: 		__CENTREP_TRACE1("CENTREP: Normal Eviction of repository %x", iIdleRepositories[0].iSharedRepository->Uid().iUid);			
sl@0: 		// Always remove from the top of the sorted list
sl@0: 		iTotalCacheUsage -= iIdleRepositories[0].iSharedRepository->Size();		
sl@0: 		Evict(0);
sl@0: 		iIdleRepositories.Remove(0);		
sl@0: 		};
sl@0: 		
sl@0: 	// reschedule to run again at the expiry date of next repository on the list, if any
sl@0: 	if (iIdleRepositories.Count())
sl@0: 		{
sl@0: 		RescheduleTimer(iIdleRepositories[0].iCacheTime);
sl@0: 		}
sl@0: 	else
sl@0: 		{
sl@0: 		__CENTREP_TRACE("CENTREP: Cache Empty/Timer Disabled");			
sl@0: 		}
sl@0: 	}
sl@0: 
sl@0: TInt CRepositoryCacheManager::ForcedEvictionSortOrder(const TRepositoryCacheInfo &aRepository1, const TRepositoryCacheInfo &aRepository2)
sl@0: 	{
sl@0: /*
sl@0:    The code in the comments below is the original simple-to-read version of the algebraically
sl@0:    simplified production code. 
sl@0: 
sl@0: 	TTime now;
sl@0: 
sl@0: 	now.UniversalTime();
sl@0: 
sl@0: 	// we calculate the ages of the repositories by taking the difference between now and when
sl@0: 	// they were last became idle. When refactoring, the calculation of the absolute ages will be 
sl@0: 	// eleminated and the age difference between the repositories will be used in the formula instead
sl@0: 	
sl@0: 	TTimeIntervalMicroSeconds age1 = now.MicroSecondsFrom(aRepository1.iCacheTime);
sl@0: 	TTimeIntervalMicroSeconds age2 = now.MicroSecondsFrom(aRepository2.iCacheTime);
sl@0: 	
sl@0: 	// then divide the resulting numbers by conversion constant to get a number in a compatible unit
sl@0: 	// to the size. This operation reduces the microsecond-based values to having an approx. max
sl@0: 	// of 100K units
sl@0: 
sl@0: 	TInt t1 = age1.Int64()/KTimeoutToSizeConversion;
sl@0: 	TInt t2 = age2.Int64()/KTimeoutToSizeConversion;
sl@0: 	
sl@0: 	// the resulting normalized time difference values are added to the size of the repository
sl@0: 	// resulting in an implicit %50 weight in the overall importance value 
sl@0: 	// An approx. maximum size of a repository is assumed to be around 100K
sl@0: 	
sl@0: 	TInt importance1 = t1+aRepository1.iSharedRepository->Size();
sl@0: 	TInt importance2 = t2+aRepository2.iSharedRepository->Size();
sl@0: 	
sl@0: 	// the difference between the importances of the repositories determine the sorting order
sl@0: 
sl@0: 	return static_cast<TInt>(importance1-importance2);
sl@0: */	
sl@0: 	//	after refactoring, the resulting formula is this one
sl@0: 	return static_cast<TInt>(((aRepository1.iCacheTime.Int64()-aRepository2.iCacheTime.Int64())/KTimeoutToSizeConversion)+(aRepository1.iSharedRepository->Size()-aRepository2.iSharedRepository->Size()));	
sl@0: 	}
sl@0: 
sl@0: TInt CRepositoryCacheManager::TimerEvictionSortOrder(const TRepositoryCacheInfo &aRepository1, const TRepositoryCacheInfo &aRepository2)
sl@0: 	{
sl@0: 	return static_cast<TInt>(aRepository1.iCacheTime.Int64()-aRepository2.iCacheTime.Int64());
sl@0: 	}