os/persistentdata/persistentstorage/centralrepository/cenrepsrv/cachemgr.cpp
changeset 0 bde4ae8d615e
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/os/persistentdata/persistentstorage/centralrepository/cenrepsrv/cachemgr.cpp	Fri Jun 15 03:10:57 2012 +0200
     1.3 @@ -0,0 +1,456 @@
     1.4 +// Copyright (c) 2004-2009 Nokia Corporation and/or its subsidiary(-ies).
     1.5 +// All rights reserved.
     1.6 +// This component and the accompanying materials are made available
     1.7 +// under the terms of "Eclipse Public License v1.0"
     1.8 +// which accompanies this distribution, and is available
     1.9 +// at the URL "http://www.eclipse.org/legal/epl-v10.html".
    1.10 +//
    1.11 +// Initial Contributors:
    1.12 +// Nokia Corporation - initial contribution.
    1.13 +//
    1.14 +// Contributors:
    1.15 +//
    1.16 +// Description:
    1.17 +//
    1.18 +
    1.19 +#include "srvrepos_noc.h"
    1.20 +#include "cachemgr.h"
    1.21 +#include <bsul/bsul.h>
    1.22 +
    1.23 +#define UNUSED_VAR(a) a = a
    1.24 +
    1.25 +_LIT(KCacheLit, "CoarseGrainedCache");
    1.26 +_LIT(KDefaultCacheSizeLit, "size");
    1.27 +_LIT(KDefaultEvictionTimeoutLit, "timeout");
    1.28 +    
    1.29 +CRepositoryCacheManager* CRepositoryCacheManager::NewLC(RFs& aFs)
    1.30 +	{
    1.31 +	CRepositoryCacheManager* self = new(ELeave) CRepositoryCacheManager;
    1.32 +	CleanupStack::PushL(self);
    1.33 +	self->ConstructL(aFs);
    1.34 +	return self;
    1.35 +	}
    1.36 +
    1.37 +CRepositoryCacheManager::~CRepositoryCacheManager()
    1.38 +	{
    1.39 +	DisableCache(ETrue);
    1.40 +	}
    1.41 +
    1.42 +void CRepositoryCacheManager::ConstructL(RFs& aFs)
    1.43 +	{
    1.44 +	CTimer::ConstructL();
    1.45 +	
    1.46 +	BSUL::CIniFile16* iniFile = NULL;
    1.47 +	TInt res = KErrNone;
    1.48 +	TBuf<KMaxFileName> iniFileName;
    1.49 +	
    1.50 +	iniFileName.Copy( *TServerResources::iInstallDirectory );
    1.51 +	iniFileName.Append( KCacheMgrIniFile );	
    1.52 +	TRAP(res, iniFile = BSUL::CIniFile16::NewL(aFs, iniFileName, ETrue));
    1.53 +	if(res==KErrNotFound)
    1.54 +		{
    1.55 +		// if RomDirectory exists
    1.56 +		if (TServerResources::iRomDirectory)
    1.57 +			{
    1.58 +			iniFileName.Copy( *TServerResources::iRomDirectory );
    1.59 +			iniFileName.Append( KCacheMgrIniFile );	
    1.60 +			TRAP(res, iniFile = BSUL::CIniFile16::NewL(aFs, iniFileName, ETrue));
    1.61 +			}
    1.62 +		if(res==KErrNotFound)
    1.63 +			{
    1.64 +			__CENTREP_TRACE1("CENTREP: Central Repository Cache Parameters ini file %S not found. Default values will be used.", &KCacheMgrIniFile);
    1.65 +			return;
    1.66 +			}
    1.67 +		}
    1.68 +	if (res != KErrNone)
    1.69 +		{
    1.70 +		User::Leave(res);
    1.71 +		}
    1.72 +		
    1.73 +	CleanupStack::PushL(iniFile);
    1.74 +	
    1.75 +	TBuf<20> buffer;
    1.76 +	TPtrC ptr(buffer);
    1.77 +	
    1.78 +	// find the value
    1.79 +	res = iniFile->FindVar(KCacheLit(), KDefaultCacheSizeLit(), ptr);
    1.80 +	TLex lex(ptr);
    1.81 +
    1.82 +	TBool valueFound = EFalse;
    1.83 +	
    1.84 +	// if the value can't be found or can't be converted into a positive integer, use the default
    1.85 +	if (res != KErrNone || lex.Val(iCacheSize) != KErrNone || iCacheSize < 0)	
    1.86 +		{
    1.87 +		iCacheSize = KDefaultCacheSize;
    1.88 +		}
    1.89 +	else
    1.90 +		{
    1.91 +		valueFound = ETrue;			
    1.92 +		}
    1.93 +		
    1.94 +	// if the value can be found, convert it
    1.95 +	if (iniFile->FindVar(KCacheLit(), KDefaultEvictionTimeoutLit(), ptr) == KErrNone)
    1.96 +		{
    1.97 +		TInt tempTimeout;
    1.98 +		lex.Assign(ptr);
    1.99 +		// if the value can be converted into a positive integer, assign it to iDefaultTimeout.
   1.100 +		if (lex.Val(tempTimeout) == KErrNone && tempTimeout >= 0)
   1.101 +			{
   1.102 +			iDefaultTimeout = tempTimeout;
   1.103 +			valueFound = ETrue;
   1.104 +			}
   1.105 +		}
   1.106 +	
   1.107 +#ifdef _DEBUG
   1.108 +	// in Debug mode, if the Cache ini file exists either in install directory or 
   1.109 +	// rom directory but does not contains the correct section "CoarseGrainedCache"
   1.110 +	// nor any key of "size" and "timeout", the server panics.
   1.111 +	if(! valueFound)
   1.112 +	{
   1.113 +	Panic(ECacheIniFileCorrupted);
   1.114 +	}
   1.115 +#else
   1.116 +	UNUSED_VAR(valueFound);
   1.117 +#endif		
   1.118 +
   1.119 +	CleanupStack::PopAndDestroy(iniFile);
   1.120 +	}
   1.121 +
   1.122 +void CRepositoryCacheManager::EnableCache(TInt aDefaultTimeout, TInt aCacheSize)
   1.123 +	{
   1.124 +	if (aDefaultTimeout>0)
   1.125 +		{
   1.126 +		iDefaultTimeout = aDefaultTimeout;
   1.127 +		}
   1.128 +	if (aCacheSize>0)
   1.129 +		{
   1.130 +		iCacheSize = aCacheSize;
   1.131 +		}
   1.132 +	
   1.133 +	EnableCache();
   1.134 +	}
   1.135 +
   1.136 +void CRepositoryCacheManager::EnableCache()
   1.137 +	{
   1.138 +	// If disabled, enable
   1.139 +	if (!iEnabled)
   1.140 +		{
   1.141 +		iEnabled = ETrue;
   1.142 +		__CENTREP_TRACE2("CENTREP: Cache Enabled. Size:%d Default Timeout:%d", iCacheSize, iDefaultTimeout.Int());
   1.143 +		}
   1.144 +	}
   1.145 +		
   1.146 +void CRepositoryCacheManager::DisableCache(TBool aFullFlush)
   1.147 +	{
   1.148 +	// If enabled, disable
   1.149 +	if (iEnabled)
   1.150 +		{
   1.151 +		// cancel any outstanding timer
   1.152 +		Cancel();
   1.153 +
   1.154 +		FlushCache(aFullFlush);
   1.155 +
   1.156 +		iEnabled = EFalse;
   1.157 +		__CENTREP_TRACE("CENTREP: Cache Disabled");
   1.158 +		}
   1.159 +	}
   1.160 +
   1.161 +void CRepositoryCacheManager::RescheduleTimer(const TTime& aTimeInUTC)
   1.162 +	{
   1.163 +	
   1.164 +	TTime now;
   1.165 +	now.UniversalTime();
   1.166 +	
   1.167 +	//Get the 64bit time interval between now and the cache timeout
   1.168 +	TTimeIntervalMicroSeconds interval64 = aTimeInUTC.MicroSecondsFrom(now);
   1.169 +	TTimeIntervalMicroSeconds32 interval32(iDefaultTimeout);
   1.170 +	
   1.171 +	//If the interval is positive, i.e. the timeout is in the future, convert 
   1.172 +	//this interval to a 32 bit value, otherwise use the default timeout
   1.173 +	if(interval64 > 0)
   1.174 +		{
   1.175 +		//If the interval value is less than the maximum 32 bit value cast
   1.176 +		//interval to 32 bit value, otherwise the interval is too large for 
   1.177 +		//a 32 bit value so just set the interval to the max 32 bit value
   1.178 +		const TInt64 KMax32BitValue(KMaxTInt32);
   1.179 +		interval32 = (interval64 <= KMax32BitValue) ? 
   1.180 +				static_cast<TTimeIntervalMicroSeconds32>(interval64.Int64()): KMaxTInt32;
   1.181 +		}
   1.182 +
   1.183 +	//Reschedule the timer
   1.184 +	After(interval32);
   1.185 +
   1.186 +	}
   1.187 +
   1.188 +void CRepositoryCacheManager::RemoveIdleRepository(CSharedRepository* aRepository)
   1.189 +	{
   1.190 +	if (iEnabled)
   1.191 +		{
   1.192 +		TInt i;
   1.193 +		TInt count=iIdleRepositories.Count();
   1.194 +		for(i=count-1; i>=0; i--)
   1.195 +			{
   1.196 +			if(iIdleRepositories[i].iSharedRepository==aRepository)
   1.197 +				{
   1.198 +				break;
   1.199 +				}
   1.200 +			}
   1.201 +		
   1.202 +		// Idle repository might not be found in the list if multiple clients try to open the same 
   1.203 +		// repository at the same time. First client will remove it and second one will not find it
   1.204 +		if(i>=0)
   1.205 +			{
   1.206 +			__CENTREP_TRACE1("CENTREP: Cache Hit when opening repository %x", aRepository->Uid().iUid);
   1.207 +
   1.208 +			iTotalCacheUsage -= iIdleRepositories[i].iSharedRepository->Size();		
   1.209 +			iIdleRepositories.Remove(i);
   1.210 +			
   1.211 +			// If this was the first repository on the list, it means its timer is still ticking. 
   1.212 +			// We have to stop it and ...
   1.213 +			if (i==0)
   1.214 +				{
   1.215 +				Cancel();
   1.216 +				// if there's still other repositories in the list, reschedule the timer with the
   1.217 +				// new top-of-the-list  
   1.218 +				if (iIdleRepositories.Count())
   1.219 +					{
   1.220 +					RescheduleTimer(iIdleRepositories[0].iCacheTime);
   1.221 +					}
   1.222 +				}
   1.223 +			}
   1.224 +		else
   1.225 +			{
   1.226 +			__CENTREP_TRACE1("CENTREP: Cache Miss when opening repository %x", aRepository->Uid().iUid);
   1.227 +			}
   1.228 +		}
   1.229 +	}
   1.230 +
   1.231 +#ifdef CACHE_OOM_TESTABILITY
   1.232 +  	// This code is only for tesing and doesn't go into MCL
   1.233 +	// Hence hide the leave in a macro instead of making StartEvictionL
   1.234 +#define TEST_CODE_LEAVE(x) User::Leave(x)
   1.235 +#endif	
   1.236 +
   1.237 +TBool CRepositoryCacheManager::StartEviction(CSharedRepository*& aRepository)
   1.238 +	{
   1.239 +	// find the item in the cache and remove it if it exists to reset the timer
   1.240 +	RemoveIdleRepository(aRepository);
   1.241 +
   1.242 +	TInt64 lastTop = 0;
   1.243 +	
   1.244 +	if (iIdleRepositories.Count())
   1.245 +		{
   1.246 +		lastTop = iIdleRepositories[0].iCacheTime.Int64();
   1.247 +		}
   1.248 +
   1.249 +	// Execute the forced eviction algorithm only if it will make sense
   1.250 +	// The eviction makes sense if:
   1.251 +	// - there's anything in the cache to evict
   1.252 +	// - the repository we're trying to cache can fit in the cache after evictions
   1.253 +	if (iIdleRepositories.Count() && (aRepository->Size() < iCacheSize))
   1.254 +		{
   1.255 +		// Check to see if current cache size + the current repository size is overshooting the limit
   1.256 +		if (iTotalCacheUsage + aRepository->Size() > iCacheSize)
   1.257 +			{
   1.258 +			// Forced eviction
   1.259 +			__CENTREP_TRACE3("CENTREP: Cache Size Exceeded: Current(%d)+Size(%d)>Cache(%d)", iTotalCacheUsage, aRepository->Size(), iCacheSize);
   1.260 +			
   1.261 +			// Sort in the forced eviction order
   1.262 +			TLinearOrder<TRepositoryCacheInfo> forcedSortOrder(CRepositoryCacheManager::ForcedEvictionSortOrder);
   1.263 +			iIdleRepositories.Sort(forcedSortOrder);
   1.264 +			
   1.265 +			// Evict one by one until there's enough cache space or we run out of idle reps
   1.266 +			do
   1.267 +				{
   1.268 +				__CENTREP_TRACE1("CENTREP: Forced Eviction of repository %x", iIdleRepositories[0].iSharedRepository->Uid().iUid);			
   1.269 +				iTotalCacheUsage -= iIdleRepositories[0].iSharedRepository->Size();
   1.270 +				Evict(0);
   1.271 +				iIdleRepositories.Remove(0);		
   1.272 +				} while (iIdleRepositories.Count() && (iTotalCacheUsage + aRepository->Size() > iCacheSize));
   1.273 +			
   1.274 +#ifdef CENTREP_TRACE			
   1.275 +			if (!iIdleRepositories.Count())
   1.276 +				{
   1.277 +				__CENTREP_TRACE("CENREP: Cache Emptied by Forced Eviction");
   1.278 +				}
   1.279 +#endif				
   1.280 +			// Re-sort to timer order for normal operation
   1.281 +			TLinearOrder<TRepositoryCacheInfo> timerSortOrder(CRepositoryCacheManager::TimerEvictionSortOrder);
   1.282 +			iIdleRepositories.Sort(timerSortOrder);
   1.283 +			};
   1.284 +		}
   1.285 +	
   1.286 +	// See if there's enough space now
   1.287 +	if (iTotalCacheUsage + aRepository->Size() > iCacheSize)
   1.288 +		{
   1.289 +		return EFalse;
   1.290 +		}
   1.291 +
   1.292 +	// Create new item for the cache and insert it in the list
   1.293 +	TRepositoryCacheInfo repInfo;
   1.294 +	
   1.295 +	repInfo.iCacheTime.UniversalTime();
   1.296 +	repInfo.iCacheTime += TTimeIntervalMicroSeconds32(iDefaultTimeout);
   1.297 +	repInfo.iSharedRepository = aRepository;
   1.298 +	
   1.299 +	TLinearOrder<TRepositoryCacheInfo> timerSortOrder(CRepositoryCacheManager::TimerEvictionSortOrder);
   1.300 +	// With the same timeout value assigned to all repositories, no two repositories can have the same 
   1.301 +	// timeout theoretically, so InsertInOrder is sufficient. But in practice, because of the poor 
   1.302 +	// resolution of the NTickCount() function used by TTime::UniversalTime(), InsertInOrderAllowRepeats 
   1.303 +	// should be used instead of InsertInOrder to allow for duplicate timer values caused by two 
   1.304 +	// repositories cached in quick succession (<1ms)
   1.305 +	TInt err = iIdleRepositories.InsertInOrderAllowRepeats(repInfo, timerSortOrder);
   1.306 +#ifdef CACHE_OOM_TESTABILITY
   1.307 +  	// This code is only for tesing and doesn't go into MCL
   1.308 +  	if (err == KErrNoMemory)	
   1.309 +  		{
   1.310 +  		TServerResources::iObserver->RemoveOpenRepository(aRepository);
   1.311 +  		aRepository = NULL;
   1.312 +  		// Should Leave here for the OOM tests to successfully complete. 
   1.313 +		TEST_CODE_LEAVE(err);
   1.314 +  		}
   1.315 +#endif	
   1.316 +	if (err!=KErrNone)
   1.317 +		{
   1.318 +		return EFalse;
   1.319 +		}
   1.320 +
   1.321 +	iTotalCacheUsage += repInfo.iSharedRepository->Size();
   1.322 +	
   1.323 +	// Only reset timer if necessary. This operation takes time and doing it every time reduces performance considerably
   1.324 +	if (lastTop != iIdleRepositories[0].iCacheTime.Int64())
   1.325 +		{
   1.326 +		// reset timer to the new top-of-the-list
   1.327 +		Cancel();
   1.328 +		RescheduleTimer(iIdleRepositories[0].iCacheTime);
   1.329 +		}
   1.330 +		
   1.331 +	return ETrue;
   1.332 +	}
   1.333 +
   1.334 +void CRepositoryCacheManager::FlushCache(TBool aFullFlush)
   1.335 +	{
   1.336 +	// iterate through idle repositories (loaded in memory, scheduled to be evicted)
   1.337 +	TInt idleRepCount = iIdleRepositories.Count();
   1.338 +	for(TInt repCount = idleRepCount - 1; repCount >= 0 ; repCount--)	
   1.339 +		{
   1.340 +		// check if there are any observers listening (to see if any client is connected to this repository)
   1.341 +		if (aFullFlush || (TServerResources::iObserver->FindConnectedRepository(iIdleRepositories[repCount].iSharedRepository->Uid())==KErrNotFound))
   1.342 +			{
   1.343 +			// if the client has already been disconnected, unload from memory
   1.344 +			Evict(repCount);
   1.345 +			}
   1.346 +		}
   1.347 +	// this whole iteration and search above can be replaced by a simple reference counter variable check,
   1.348 +	// if the server is redesigned using a resource manager type pattern with CSharedRepository object as a resource
   1.349 +	
   1.350 +	// empty the list
   1.351 +	iIdleRepositories.Reset();
   1.352 +	
   1.353 +	iTotalCacheUsage = 0;
   1.354 +	__CENTREP_TRACE1("CENTREP: Cache Flush: %d repositories flushed", idleRepCount);
   1.355 +	}
   1.356 +	
   1.357 +// Evict removes items from iOpenRepositories list, not from iIdleRepositories list
   1.358 +void CRepositoryCacheManager::Evict(TInt aIdleRepIndex)
   1.359 +	{
   1.360 +	// find,remove and delete the idle repositories' pointers in the open repositories list 
   1.361 +	TServerResources::iObserver->RemoveOpenRepository(iIdleRepositories[aIdleRepIndex].iSharedRepository);
   1.362 +	}
   1.363 +		
   1.364 +void CRepositoryCacheManager::RunL()
   1.365 +	{
   1.366 +	TTime now;
   1.367 +
   1.368 +	now.UniversalTime();
   1.369 +
   1.370 +	TInt count = iIdleRepositories.Count();
   1.371 +	
   1.372 +	// repositories that are involved in active transactions are not idle.
   1.373 +	// this checks to make sure that we're not trying to reclaim memory that
   1.374 +	// is actually still currently in use.
   1.375 +	
   1.376 +	for (TInt i = 0;i < count;i++)
   1.377 +		{
   1.378 +		if (iIdleRepositories[i].iCacheTime > now)
   1.379 +			{
   1.380 +			break;
   1.381 +			}
   1.382 +		
   1.383 +		if (iIdleRepositories[i].iSharedRepository->IsTransactionActive())
   1.384 +			{
   1.385 +			__CENTREP_TRACE1("CRepositoryCacheManager::RunL - rescheduling UID 0x%x, in active transaction",
   1.386 +					iIdleRepositories[i].iSharedRepository->Uid().iUid);
   1.387 +			StartEviction(iIdleRepositories[i].iSharedRepository);
   1.388 +			return;
   1.389 +			}
   1.390 +		}
   1.391 +	
   1.392 +
   1.393 +	// Try to evict all the repositories which have expired. There might be more than one repository
   1.394 +	// destined to expire at the same time, or there are more than one repository with expiry times
   1.395 +	// between the scheduled expiry time and now (which theoretically should have been the same, but maybe
   1.396 +	// because of other procesor activity, the timer Active Object just got late a bit)
   1.397 +	while((iIdleRepositories.Count()) && (iIdleRepositories[0].iCacheTime<=now))
   1.398 +		{
   1.399 +		__CENTREP_TRACE1("CENTREP: Normal Eviction of repository %x", iIdleRepositories[0].iSharedRepository->Uid().iUid);			
   1.400 +		// Always remove from the top of the sorted list
   1.401 +		iTotalCacheUsage -= iIdleRepositories[0].iSharedRepository->Size();		
   1.402 +		Evict(0);
   1.403 +		iIdleRepositories.Remove(0);		
   1.404 +		};
   1.405 +		
   1.406 +	// reschedule to run again at the expiry date of next repository on the list, if any
   1.407 +	if (iIdleRepositories.Count())
   1.408 +		{
   1.409 +		RescheduleTimer(iIdleRepositories[0].iCacheTime);
   1.410 +		}
   1.411 +	else
   1.412 +		{
   1.413 +		__CENTREP_TRACE("CENTREP: Cache Empty/Timer Disabled");			
   1.414 +		}
   1.415 +	}
   1.416 +
   1.417 +TInt CRepositoryCacheManager::ForcedEvictionSortOrder(const TRepositoryCacheInfo &aRepository1, const TRepositoryCacheInfo &aRepository2)
   1.418 +	{
   1.419 +/*
   1.420 +   The code in the comments below is the original simple-to-read version of the algebraically
   1.421 +   simplified production code. 
   1.422 +
   1.423 +	TTime now;
   1.424 +
   1.425 +	now.UniversalTime();
   1.426 +
   1.427 +	// we calculate the ages of the repositories by taking the difference between now and when
   1.428 +	// they were last became idle. When refactoring, the calculation of the absolute ages will be 
   1.429 +	// eleminated and the age difference between the repositories will be used in the formula instead
   1.430 +	
   1.431 +	TTimeIntervalMicroSeconds age1 = now.MicroSecondsFrom(aRepository1.iCacheTime);
   1.432 +	TTimeIntervalMicroSeconds age2 = now.MicroSecondsFrom(aRepository2.iCacheTime);
   1.433 +	
   1.434 +	// then divide the resulting numbers by conversion constant to get a number in a compatible unit
   1.435 +	// to the size. This operation reduces the microsecond-based values to having an approx. max
   1.436 +	// of 100K units
   1.437 +
   1.438 +	TInt t1 = age1.Int64()/KTimeoutToSizeConversion;
   1.439 +	TInt t2 = age2.Int64()/KTimeoutToSizeConversion;
   1.440 +	
   1.441 +	// the resulting normalized time difference values are added to the size of the repository
   1.442 +	// resulting in an implicit %50 weight in the overall importance value 
   1.443 +	// An approx. maximum size of a repository is assumed to be around 100K
   1.444 +	
   1.445 +	TInt importance1 = t1+aRepository1.iSharedRepository->Size();
   1.446 +	TInt importance2 = t2+aRepository2.iSharedRepository->Size();
   1.447 +	
   1.448 +	// the difference between the importances of the repositories determine the sorting order
   1.449 +
   1.450 +	return static_cast<TInt>(importance1-importance2);
   1.451 +*/	
   1.452 +	//	after refactoring, the resulting formula is this one
   1.453 +	return static_cast<TInt>(((aRepository1.iCacheTime.Int64()-aRepository2.iCacheTime.Int64())/KTimeoutToSizeConversion)+(aRepository1.iSharedRepository->Size()-aRepository2.iSharedRepository->Size()));	
   1.454 +	}
   1.455 +
   1.456 +TInt CRepositoryCacheManager::TimerEvictionSortOrder(const TRepositoryCacheInfo &aRepository1, const TRepositoryCacheInfo &aRepository2)
   1.457 +	{
   1.458 +	return static_cast<TInt>(aRepository1.iCacheTime.Int64()-aRepository2.iCacheTime.Int64());
   1.459 +	}