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 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 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(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 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 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 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(importance1-importance2); sl@0: */ sl@0: // after refactoring, the resulting formula is this one sl@0: return static_cast(((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(aRepository1.iCacheTime.Int64()-aRepository2.iCacheTime.Int64()); sl@0: }