michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et ft=cpp : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ProcessPriorityManager.h" michael@0: #include "mozilla/ClearOnShutdown.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/TabParent.h" michael@0: #include "mozilla/Hal.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/unused.h" michael@0: #include "AudioChannelService.h" michael@0: #include "prlog.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsIFrameLoader.h" michael@0: #include "nsIObserverService.h" michael@0: #include "StaticPtr.h" michael@0: #include "nsIMozBrowserFrame.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIPropertyBag2.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #define getpid _getpid michael@0: #else michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef LOG michael@0: #undef LOG michael@0: #endif michael@0: michael@0: // Use LOGP inside a ParticularProcessPriorityManager method; use LOG michael@0: // everywhere else. LOGP prints out information about the particular process michael@0: // priority manager. michael@0: // michael@0: // (Wow, our logging story is a huge mess.) michael@0: michael@0: // #define ENABLE_LOGGING 1 michael@0: michael@0: #if defined(ANDROID) && defined(ENABLE_LOGGING) michael@0: # include michael@0: # define LOG(fmt, ...) \ michael@0: __android_log_print(ANDROID_LOG_INFO, \ michael@0: "Gecko:ProcessPriorityManager", \ michael@0: fmt, ## __VA_ARGS__) michael@0: # define LOGP(fmt, ...) \ michael@0: __android_log_print(ANDROID_LOG_INFO, \ michael@0: "Gecko:ProcessPriorityManager", \ michael@0: "[%schild-id=%llu, pid=%d] " fmt, \ michael@0: NameWithComma().get(), \ michael@0: (long long unsigned) ChildID(), Pid(), ## __VA_ARGS__) michael@0: michael@0: #elif defined(ENABLE_LOGGING) michael@0: # define LOG(fmt, ...) \ michael@0: printf("ProcessPriorityManager - " fmt "\n", ##__VA_ARGS__) michael@0: # define LOGP(fmt, ...) \ michael@0: printf("ProcessPriorityManager[%schild-id=%llu, pid=%d] - " fmt "\n", \ michael@0: NameWithComma().get(), \ michael@0: (unsigned long long) ChildID(), Pid(), ##__VA_ARGS__) michael@0: michael@0: #elif defined(PR_LOGGING) michael@0: static PRLogModuleInfo* michael@0: GetPPMLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("ProcessPriorityManager"); michael@0: return sLog; michael@0: } michael@0: # define LOG(fmt, ...) \ michael@0: PR_LOG(GetPPMLog(), PR_LOG_DEBUG, \ michael@0: ("ProcessPriorityManager - " fmt, ##__VA_ARGS__)) michael@0: # define LOGP(fmt, ...) \ michael@0: PR_LOG(GetPPMLog(), PR_LOG_DEBUG, \ michael@0: ("ProcessPriorityManager[%schild-id=%llu, pid=%d] - " fmt, \ michael@0: NameWithComma().get(), \ michael@0: (unsigned long long) ChildID(), Pid(), ##__VA_ARGS__)) michael@0: #else michael@0: #define LOG(fmt, ...) michael@0: #define LOGP(fmt, ...) michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::hal; michael@0: michael@0: namespace { michael@0: michael@0: class ParticularProcessPriorityManager; michael@0: michael@0: /** michael@0: * This singleton class does the work to implement the process priority manager michael@0: * in the main process. This class may not be used in child processes. (You michael@0: * can call StaticInit, but it won't do anything, and GetSingleton() will michael@0: * return null.) michael@0: * michael@0: * ProcessPriorityManager::CurrentProcessIsForeground() and michael@0: * ProcessPriorityManager::AnyProcessHasHighPriority() which can be called in michael@0: * any process, are handled separately, by the ProcessPriorityManagerChild michael@0: * class. michael@0: */ michael@0: class ProcessPriorityManagerImpl MOZ_FINAL michael@0: : public nsIObserver michael@0: { michael@0: public: michael@0: /** michael@0: * If we're in the main process, get the ProcessPriorityManagerImpl michael@0: * singleton. If we're in a child process, return null. michael@0: */ michael@0: static ProcessPriorityManagerImpl* GetSingleton(); michael@0: michael@0: static void StaticInit(); michael@0: static bool PrefsEnabled(); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: /** michael@0: * This function implements ProcessPriorityManager::SetProcessPriority. michael@0: */ michael@0: void SetProcessPriority(ContentParent* aContentParent, michael@0: ProcessPriority aPriority, michael@0: uint32_t aBackgroundLRU = 0); michael@0: michael@0: /** michael@0: * If a magic testing-only pref is set, notify the observer service on the michael@0: * given topic with the given data. This is used for testing michael@0: */ michael@0: void FireTestOnlyObserverNotification(const char* aTopic, michael@0: const nsACString& aData = EmptyCString()); michael@0: michael@0: /** michael@0: * Does some process, other than the one handled by aParticularManager, have michael@0: * priority FOREGROUND_HIGH? michael@0: */ michael@0: bool OtherProcessHasHighPriority( michael@0: ParticularProcessPriorityManager* aParticularManager); michael@0: michael@0: /** michael@0: * Does one of the child processes have priority FOREGROUND_HIGH? michael@0: */ michael@0: bool ChildProcessHasHighPriority(); michael@0: michael@0: /** michael@0: * This must be called by a ParticularProcessPriorityManager when it changes michael@0: * its priority. michael@0: */ michael@0: void NotifyProcessPriorityChanged( michael@0: ParticularProcessPriorityManager* aParticularManager, michael@0: hal::ProcessPriority aOldPriority); michael@0: michael@0: private: michael@0: static bool sPrefListenersRegistered; michael@0: static bool sInitialized; michael@0: static StaticRefPtr sSingleton; michael@0: michael@0: static void PrefChangedCallback(const char* aPref, void* aClosure); michael@0: michael@0: ProcessPriorityManagerImpl(); michael@0: ~ProcessPriorityManagerImpl() {} michael@0: DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManagerImpl); michael@0: michael@0: void Init(); michael@0: michael@0: already_AddRefed michael@0: GetParticularProcessPriorityManager(ContentParent* aContentParent); michael@0: michael@0: void ObserveContentParentCreated(nsISupports* aContentParent); michael@0: void ObserveContentParentDestroyed(nsISupports* aSubject); michael@0: michael@0: nsDataHashtable > michael@0: mParticularManagers; michael@0: michael@0: nsTHashtable mHighPriorityChildIDs; michael@0: }; michael@0: michael@0: /** michael@0: * This singleton class implements the parts of the process priority manager michael@0: * that are available from all processes. michael@0: */ michael@0: class ProcessPriorityManagerChild MOZ_FINAL michael@0: : public nsIObserver michael@0: { michael@0: public: michael@0: static void StaticInit(); michael@0: static ProcessPriorityManagerChild* Singleton(); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: bool CurrentProcessIsForeground(); michael@0: bool CurrentProcessIsHighPriority(); michael@0: michael@0: private: michael@0: static StaticRefPtr sSingleton; michael@0: michael@0: ProcessPriorityManagerChild(); michael@0: ~ProcessPriorityManagerChild() {} michael@0: DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManagerChild); michael@0: michael@0: void Init(); michael@0: michael@0: hal::ProcessPriority mCachedPriority; michael@0: }; michael@0: michael@0: /** michael@0: * This class manages the priority of one particular process. It is michael@0: * main-process only. michael@0: */ michael@0: class ParticularProcessPriorityManager MOZ_FINAL michael@0: : public WakeLockObserver michael@0: , public nsIObserver michael@0: , public nsITimerCallback michael@0: , public nsSupportsWeakReference michael@0: { michael@0: public: michael@0: ParticularProcessPriorityManager(ContentParent* aContentParent); michael@0: ~ParticularProcessPriorityManager(); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: virtual void Notify(const WakeLockInformation& aInfo) MOZ_OVERRIDE; michael@0: void Init(); michael@0: michael@0: int32_t Pid() const; michael@0: uint64_t ChildID() const; michael@0: bool IsPreallocated() const; michael@0: michael@0: /** michael@0: * Used in logging, this method returns the ContentParent's name followed by michael@0: * ", ". If we can't get the ContentParent's name for some reason, it michael@0: * returns an empty string. michael@0: * michael@0: * The reference returned here is guaranteed to be live until the next call michael@0: * to NameWithComma() or until the ParticularProcessPriorityManager is michael@0: * destroyed, whichever comes first. michael@0: */ michael@0: const nsAutoCString& NameWithComma(); michael@0: michael@0: bool HasAppType(const char* aAppType); michael@0: bool IsExpectingSystemMessage(); michael@0: michael@0: void OnAudioChannelProcessChanged(nsISupports* aSubject); michael@0: void OnRemoteBrowserFrameShown(nsISupports* aSubject); michael@0: void OnTabParentDestroyed(nsISupports* aSubject); michael@0: void OnFrameloaderVisibleChanged(nsISupports* aSubject); michael@0: void OnChannelConnected(nsISupports* aSubject); michael@0: michael@0: ProcessPriority CurrentPriority(); michael@0: ProcessPriority ComputePriority(); michael@0: ProcessCPUPriority ComputeCPUPriority(ProcessPriority aPriority); michael@0: michael@0: void ScheduleResetPriority(const char* aTimeoutPref); michael@0: void ResetPriority(); michael@0: void ResetPriorityNow(); michael@0: void ResetCPUPriorityNow(); michael@0: michael@0: /** michael@0: * This overload is equivalent to SetPriorityNow(aPriority, michael@0: * ComputeCPUPriority()). michael@0: */ michael@0: void SetPriorityNow(ProcessPriority aPriority, michael@0: uint32_t aBackgroundLRU = 0); michael@0: michael@0: void SetPriorityNow(ProcessPriority aPriority, michael@0: ProcessCPUPriority aCPUPriority, michael@0: uint32_t aBackgroundLRU = 0); michael@0: michael@0: void ShutDown(); michael@0: michael@0: private: michael@0: void FireTestOnlyObserverNotification( michael@0: const char* aTopic, michael@0: const nsACString& aData = EmptyCString()); michael@0: michael@0: void FireTestOnlyObserverNotification( michael@0: const char* aTopic, michael@0: const char* aData = nullptr); michael@0: michael@0: ContentParent* mContentParent; michael@0: uint64_t mChildID; michael@0: ProcessPriority mPriority; michael@0: ProcessCPUPriority mCPUPriority; michael@0: bool mHoldsCPUWakeLock; michael@0: bool mHoldsHighPriorityWakeLock; michael@0: michael@0: /** michael@0: * Used to implement NameWithComma(). michael@0: */ michael@0: nsAutoCString mNameWithComma; michael@0: michael@0: nsCOMPtr mResetPriorityTimer; michael@0: }; michael@0: michael@0: class BackgroundProcessLRUPool MOZ_FINAL michael@0: { michael@0: public: michael@0: static BackgroundProcessLRUPool* Singleton(); michael@0: michael@0: /** michael@0: * Used to remove a ContentParent from background LRU pool when michael@0: * it is destroyed or its priority changed from BACKGROUND to others. michael@0: */ michael@0: void RemoveFromBackgroundLRUPool(ContentParent* aContentParent); michael@0: michael@0: /** michael@0: * Used to add a ContentParent into background LRU pool when michael@0: * its priority changed to BACKGROUND from others. michael@0: */ michael@0: void AddIntoBackgroundLRUPool(ContentParent* aContentParent); michael@0: michael@0: private: michael@0: static StaticAutoPtr sSingleton; michael@0: michael@0: int32_t mLRUPoolLevels; michael@0: int32_t mLRUPoolSize; michael@0: int32_t mLRUPoolAvailableIndex; michael@0: nsTArray mLRUPool; michael@0: michael@0: uint32_t CalculateLRULevel(uint32_t aBackgroundLRUPoolIndex); michael@0: michael@0: nsresult UpdateAvailableIndexInLRUPool( michael@0: ContentParent* aContentParent, michael@0: int32_t aTargetIndex = -1); michael@0: michael@0: void ShiftLRUPool(); michael@0: michael@0: void EnsureLRUPool(); michael@0: michael@0: BackgroundProcessLRUPool(); michael@0: DISALLOW_EVIL_CONSTRUCTORS(BackgroundProcessLRUPool); michael@0: michael@0: }; michael@0: michael@0: /* static */ bool ProcessPriorityManagerImpl::sInitialized = false; michael@0: /* static */ bool ProcessPriorityManagerImpl::sPrefListenersRegistered = false; michael@0: /* static */ StaticRefPtr michael@0: ProcessPriorityManagerImpl::sSingleton; michael@0: michael@0: NS_IMPL_ISUPPORTS(ProcessPriorityManagerImpl, michael@0: nsIObserver); michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManagerImpl::PrefChangedCallback(const char* aPref, michael@0: void* aClosure) michael@0: { michael@0: StaticInit(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: ProcessPriorityManagerImpl::PrefsEnabled() michael@0: { michael@0: return Preferences::GetBool("dom.ipc.processPriorityManager.enabled") && michael@0: !Preferences::GetBool("dom.ipc.tabs.disabled"); michael@0: } michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManagerImpl::StaticInit() michael@0: { michael@0: if (sInitialized) { michael@0: return; michael@0: } michael@0: michael@0: // The process priority manager is main-process only. michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: sInitialized = true; michael@0: return; michael@0: } michael@0: michael@0: // If IPC tabs aren't enabled at startup, don't bother with any of this. michael@0: if (!PrefsEnabled()) { michael@0: LOG("InitProcessPriorityManager bailing due to prefs."); michael@0: michael@0: // Run StaticInit() again if the prefs change. We don't expect this to michael@0: // happen in normal operation, but it happens during testing. michael@0: if (!sPrefListenersRegistered) { michael@0: sPrefListenersRegistered = true; michael@0: Preferences::RegisterCallback(PrefChangedCallback, michael@0: "dom.ipc.processPriorityManager.enabled"); michael@0: Preferences::RegisterCallback(PrefChangedCallback, michael@0: "dom.ipc.tabs.disabled"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: sInitialized = true; michael@0: michael@0: sSingleton = new ProcessPriorityManagerImpl(); michael@0: sSingleton->Init(); michael@0: ClearOnShutdown(&sSingleton); michael@0: } michael@0: michael@0: /* static */ ProcessPriorityManagerImpl* michael@0: ProcessPriorityManagerImpl::GetSingleton() michael@0: { michael@0: if (!sSingleton) { michael@0: StaticInit(); michael@0: } michael@0: michael@0: return sSingleton; michael@0: } michael@0: michael@0: ProcessPriorityManagerImpl::ProcessPriorityManagerImpl() michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerImpl::Init() michael@0: { michael@0: LOG("Starting up. This is the master process."); michael@0: michael@0: // The master process's priority never changes; set it here and then forget michael@0: // about it. We'll manage only subprocesses' priorities using the process michael@0: // priority manager. michael@0: hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER, michael@0: PROCESS_CPU_PRIORITY_NORMAL); michael@0: michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (os) { michael@0: os->AddObserver(this, "ipc:content-created", /* ownsWeak */ false); michael@0: os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ false); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ProcessPriorityManagerImpl::Observe( michael@0: nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: nsDependentCString topic(aTopic); michael@0: if (topic.EqualsLiteral("ipc:content-created")) { michael@0: ObserveContentParentCreated(aSubject); michael@0: } else if (topic.EqualsLiteral("ipc:content-shutdown")) { michael@0: ObserveContentParentDestroyed(aSubject); michael@0: } else { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: ProcessPriorityManagerImpl::GetParticularProcessPriorityManager( michael@0: ContentParent* aContentParent) michael@0: { michael@0: nsRefPtr pppm; michael@0: mParticularManagers.Get(aContentParent->ChildID(), &pppm); michael@0: if (!pppm) { michael@0: pppm = new ParticularProcessPriorityManager(aContentParent); michael@0: pppm->Init(); michael@0: mParticularManagers.Put(aContentParent->ChildID(), pppm); michael@0: michael@0: FireTestOnlyObserverNotification("process-created", michael@0: nsPrintfCString("%lld", aContentParent->ChildID())); michael@0: } michael@0: michael@0: return pppm.forget(); michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerImpl::SetProcessPriority(ContentParent* aContentParent, michael@0: ProcessPriority aPriority, michael@0: uint32_t aBackgroundLRU) michael@0: { michael@0: MOZ_ASSERT(aContentParent); michael@0: nsRefPtr pppm = michael@0: GetParticularProcessPriorityManager(aContentParent); michael@0: pppm->SetPriorityNow(aPriority, aBackgroundLRU); michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerImpl::ObserveContentParentCreated( michael@0: nsISupports* aContentParent) michael@0: { michael@0: // Do nothing; it's sufficient to get the PPPM. But assign to nsRefPtr so we michael@0: // don't leak the already_AddRefed object. michael@0: nsCOMPtr cp = do_QueryInterface(aContentParent); michael@0: nsRefPtr pppm = michael@0: GetParticularProcessPriorityManager(static_cast(cp.get())); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: EnumerateParticularProcessPriorityManagers( michael@0: const uint64_t& aKey, michael@0: nsRefPtr aValue, michael@0: void* aUserData) michael@0: { michael@0: nsTArray >* aArray = michael@0: static_cast >*>(aUserData); michael@0: aArray->AppendElement(aValue); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject) michael@0: { michael@0: nsCOMPtr props = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE_VOID(props); michael@0: michael@0: uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID); michael@0: NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN); michael@0: michael@0: nsRefPtr pppm; michael@0: mParticularManagers.Get(childID, &pppm); michael@0: MOZ_ASSERT(pppm); michael@0: if (pppm) { michael@0: pppm->ShutDown(); michael@0: } michael@0: michael@0: mParticularManagers.Remove(childID); michael@0: michael@0: if (mHighPriorityChildIDs.Contains(childID)) { michael@0: mHighPriorityChildIDs.RemoveEntry(childID); michael@0: michael@0: // We just lost a high-priority process; reset everyone's CPU priorities. michael@0: nsTArray > pppms; michael@0: mParticularManagers.EnumerateRead( michael@0: &EnumerateParticularProcessPriorityManagers, michael@0: &pppms); michael@0: michael@0: for (uint32_t i = 0; i < pppms.Length(); i++) { michael@0: pppms[i]->ResetCPUPriorityNow(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ProcessPriorityManagerImpl::OtherProcessHasHighPriority( michael@0: ParticularProcessPriorityManager* aParticularManager) michael@0: { michael@0: if (mHighPriorityChildIDs.Contains(aParticularManager->ChildID())) { michael@0: return mHighPriorityChildIDs.Count() > 1; michael@0: } michael@0: return mHighPriorityChildIDs.Count() > 0; michael@0: } michael@0: michael@0: bool michael@0: ProcessPriorityManagerImpl::ChildProcessHasHighPriority( void ) michael@0: { michael@0: return mHighPriorityChildIDs.Count() > 0; michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerImpl::NotifyProcessPriorityChanged( michael@0: ParticularProcessPriorityManager* aParticularManager, michael@0: ProcessPriority aOldPriority) michael@0: { michael@0: // This priority change can only affect other processes' priorities if we're michael@0: // changing to/from FOREGROUND_HIGH. michael@0: michael@0: if (aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH && michael@0: aParticularManager->CurrentPriority() < michael@0: PROCESS_PRIORITY_FOREGROUND_HIGH) { michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (aParticularManager->CurrentPriority() >= michael@0: PROCESS_PRIORITY_FOREGROUND_HIGH) { michael@0: mHighPriorityChildIDs.PutEntry(aParticularManager->ChildID()); michael@0: } else { michael@0: mHighPriorityChildIDs.RemoveEntry(aParticularManager->ChildID()); michael@0: } michael@0: michael@0: nsTArray > pppms; michael@0: mParticularManagers.EnumerateRead( michael@0: &EnumerateParticularProcessPriorityManagers, michael@0: &pppms); michael@0: michael@0: for (uint32_t i = 0; i < pppms.Length(); i++) { michael@0: if (pppms[i] != aParticularManager) { michael@0: pppms[i]->ResetCPUPriorityNow(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, michael@0: nsIObserver, michael@0: nsITimerCallback, michael@0: nsISupportsWeakReference); michael@0: michael@0: ParticularProcessPriorityManager::ParticularProcessPriorityManager( michael@0: ContentParent* aContentParent) michael@0: : mContentParent(aContentParent) michael@0: , mChildID(aContentParent->ChildID()) michael@0: , mPriority(PROCESS_PRIORITY_UNKNOWN) michael@0: , mCPUPriority(PROCESS_CPU_PRIORITY_NORMAL) michael@0: , mHoldsCPUWakeLock(false) michael@0: , mHoldsHighPriorityWakeLock(false) michael@0: { michael@0: MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); michael@0: LOGP("Creating ParticularProcessPriorityManager."); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::Init() michael@0: { michael@0: RegisterWakeLockObserver(this); michael@0: michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (os) { michael@0: os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ true); michael@0: os->AddObserver(this, "remote-browser-shown", /* ownsWeak */ true); michael@0: os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true); michael@0: os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ true); michael@0: } michael@0: michael@0: // This process may already hold the CPU lock; for example, our parent may michael@0: // have acquired it on our behalf. michael@0: WakeLockInformation info1, info2; michael@0: GetWakeLockInfo(NS_LITERAL_STRING("cpu"), &info1); michael@0: mHoldsCPUWakeLock = info1.lockingProcesses().Contains(ChildID()); michael@0: michael@0: GetWakeLockInfo(NS_LITERAL_STRING("high-priority"), &info2); michael@0: mHoldsHighPriorityWakeLock = info2.lockingProcesses().Contains(ChildID()); michael@0: LOGP("Done starting up. mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d", michael@0: mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock); michael@0: } michael@0: michael@0: ParticularProcessPriorityManager::~ParticularProcessPriorityManager() michael@0: { michael@0: LOGP("Destroying ParticularProcessPriorityManager."); michael@0: michael@0: // Unregister our wake lock observer if ShutDown hasn't been called. (The michael@0: // wake lock observer takes raw refs, so we don't want to take chances here!) michael@0: // We don't call UnregisterWakeLockObserver unconditionally because the code michael@0: // will print a warning if it's called unnecessarily. michael@0: michael@0: if (mContentParent) { michael@0: UnregisterWakeLockObserver(this); michael@0: } michael@0: } michael@0: michael@0: /* virtual */ void michael@0: ParticularProcessPriorityManager::Notify(const WakeLockInformation& aInfo) michael@0: { michael@0: if (!mContentParent) { michael@0: // We've been shut down. michael@0: return; michael@0: } michael@0: michael@0: bool* dest = nullptr; michael@0: if (aInfo.topic().EqualsLiteral("cpu")) { michael@0: dest = &mHoldsCPUWakeLock; michael@0: } else if (aInfo.topic().EqualsLiteral("high-priority")) { michael@0: dest = &mHoldsHighPriorityWakeLock; michael@0: } michael@0: michael@0: if (dest) { michael@0: bool thisProcessLocks = aInfo.lockingProcesses().Contains(ChildID()); michael@0: if (thisProcessLocks != *dest) { michael@0: *dest = thisProcessLocks; michael@0: LOGP("Got wake lock changed event. " michael@0: "Now mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d", michael@0: mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock); michael@0: ResetPriority(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ParticularProcessPriorityManager::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!mContentParent) { michael@0: // We've been shut down. michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsDependentCString topic(aTopic); michael@0: michael@0: if (topic.EqualsLiteral("audio-channel-process-changed")) { michael@0: OnAudioChannelProcessChanged(aSubject); michael@0: } else if (topic.EqualsLiteral("remote-browser-shown")) { michael@0: OnRemoteBrowserFrameShown(aSubject); michael@0: } else if (topic.EqualsLiteral("ipc:browser-destroyed")) { michael@0: OnTabParentDestroyed(aSubject); michael@0: } else if (topic.EqualsLiteral("frameloader-visible-changed")) { michael@0: OnFrameloaderVisibleChanged(aSubject); michael@0: } else { michael@0: MOZ_ASSERT(false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint64_t michael@0: ParticularProcessPriorityManager::ChildID() const michael@0: { michael@0: // We have to cache mContentParent->ChildID() instead of getting it from the michael@0: // ContentParent each time because after ShutDown() is called, mContentParent michael@0: // is null. If we didn't cache ChildID(), then we wouldn't be able to run michael@0: // LOGP() after ShutDown(). michael@0: return mChildID; michael@0: } michael@0: michael@0: int32_t michael@0: ParticularProcessPriorityManager::Pid() const michael@0: { michael@0: return mContentParent ? mContentParent->Pid() : -1; michael@0: } michael@0: michael@0: bool michael@0: ParticularProcessPriorityManager::IsPreallocated() const michael@0: { michael@0: return mContentParent ? mContentParent->IsPreallocated() : false; michael@0: } michael@0: michael@0: const nsAutoCString& michael@0: ParticularProcessPriorityManager::NameWithComma() michael@0: { michael@0: mNameWithComma.Truncate(); michael@0: if (!mContentParent) { michael@0: return mNameWithComma; // empty string michael@0: } michael@0: michael@0: nsAutoString name; michael@0: mContentParent->FriendlyName(name); michael@0: if (name.IsEmpty()) { michael@0: return mNameWithComma; // empty string michael@0: } michael@0: michael@0: mNameWithComma = NS_ConvertUTF16toUTF8(name); michael@0: mNameWithComma.AppendLiteral(", "); michael@0: return mNameWithComma; michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::OnAudioChannelProcessChanged(nsISupports* aSubject) michael@0: { michael@0: nsCOMPtr props = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE_VOID(props); michael@0: michael@0: uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID); michael@0: if (childID == ChildID()) { michael@0: ResetPriority(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::OnRemoteBrowserFrameShown(nsISupports* aSubject) michael@0: { michael@0: nsCOMPtr fl = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE_VOID(fl); michael@0: michael@0: // Ignore notifications that aren't from a BrowserOrApp michael@0: bool isBrowserOrApp; michael@0: fl->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp); michael@0: if (!isBrowserOrApp) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr tp; michael@0: fl->GetTabParent(getter_AddRefs(tp)); michael@0: NS_ENSURE_TRUE_VOID(tp); michael@0: michael@0: if (static_cast(tp.get())->Manager() != mContentParent) { michael@0: return; michael@0: } michael@0: michael@0: ResetPriority(); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::OnTabParentDestroyed(nsISupports* aSubject) michael@0: { michael@0: nsCOMPtr tp = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE_VOID(tp); michael@0: michael@0: if (static_cast(tp.get())->Manager() != mContentParent) { michael@0: return; michael@0: } michael@0: michael@0: ResetPriority(); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::OnFrameloaderVisibleChanged(nsISupports* aSubject) michael@0: { michael@0: nsCOMPtr fl = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE_VOID(fl); michael@0: michael@0: nsCOMPtr tp; michael@0: fl->GetTabParent(getter_AddRefs(tp)); michael@0: if (!tp) { michael@0: return; michael@0: } michael@0: michael@0: if (static_cast(tp.get())->Manager() != mContentParent) { michael@0: return; michael@0: } michael@0: michael@0: // Most of the time when something changes in a process we call michael@0: // ResetPriority(), giving a grace period before downgrading its priority. michael@0: // But notice that here don't give a grace period: We call ResetPriorityNow() michael@0: // instead. michael@0: // michael@0: // We do this because we're reacting here to a setVisibility() call, which is michael@0: // an explicit signal from the process embedder that we should re-prioritize michael@0: // a process. If we gave a grace period in response to setVisibility() michael@0: // calls, it would be impossible for the embedder to explicitly prioritize michael@0: // processes and prevent e.g. the case where we switch which process is in michael@0: // the foreground and, during the old fg processs's grace period, it OOMs the michael@0: // new fg process. michael@0: michael@0: ResetPriorityNow(); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::ResetPriority() michael@0: { michael@0: ProcessPriority processPriority = ComputePriority(); michael@0: if (mPriority == PROCESS_PRIORITY_UNKNOWN || michael@0: mPriority > processPriority) { michael@0: // Apps set at a perceivable background priority are often playing media. michael@0: // Most media will have short gaps while changing tracks between songs, michael@0: // switching videos, etc. Give these apps a longer grace period so they michael@0: // can get their next track started, if there is one, before getting michael@0: // downgraded. michael@0: if (mPriority == PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) { michael@0: ScheduleResetPriority("backgroundPerceivableGracePeriodMS"); michael@0: } else { michael@0: ScheduleResetPriority("backgroundGracePeriodMS"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: SetPriorityNow(processPriority); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::ResetPriorityNow() michael@0: { michael@0: SetPriorityNow(ComputePriority()); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::ScheduleResetPriority(const char* aTimeoutPref) michael@0: { michael@0: if (mResetPriorityTimer) { michael@0: LOGP("ScheduleResetPriority bailing; the timer is already running."); michael@0: return; michael@0: } michael@0: michael@0: uint32_t timeout = Preferences::GetUint( michael@0: nsPrintfCString("dom.ipc.processPriorityManager.%s", aTimeoutPref).get()); michael@0: LOGP("Scheduling reset timer to fire in %dms.", timeout); michael@0: mResetPriorityTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mResetPriorityTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: ParticularProcessPriorityManager::Notify(nsITimer* aTimer) michael@0: { michael@0: LOGP("Reset priority timer callback; about to ResetPriorityNow."); michael@0: ResetPriorityNow(); michael@0: mResetPriorityTimer = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: ParticularProcessPriorityManager::HasAppType(const char* aAppType) michael@0: { michael@0: const InfallibleTArray& browsers = michael@0: mContentParent->ManagedPBrowserParent(); michael@0: for (uint32_t i = 0; i < browsers.Length(); i++) { michael@0: nsAutoString appType; michael@0: static_cast(browsers[i])->GetAppType(appType); michael@0: if (appType.EqualsASCII(aAppType)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: ParticularProcessPriorityManager::IsExpectingSystemMessage() michael@0: { michael@0: const InfallibleTArray& browsers = michael@0: mContentParent->ManagedPBrowserParent(); michael@0: for (uint32_t i = 0; i < browsers.Length(); i++) { michael@0: TabParent* tp = static_cast(browsers[i]); michael@0: nsCOMPtr bf = do_QueryInterface(tp->GetOwnerElement()); michael@0: if (!bf) { michael@0: continue; michael@0: } michael@0: michael@0: if (bf->GetIsExpectingSystemMessage()) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: ProcessPriority michael@0: ParticularProcessPriorityManager::CurrentPriority() michael@0: { michael@0: return mPriority; michael@0: } michael@0: michael@0: ProcessPriority michael@0: ParticularProcessPriorityManager::ComputePriority() michael@0: { michael@0: if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) && michael@0: HasAppType("critical")) { michael@0: return PROCESS_PRIORITY_FOREGROUND_HIGH; michael@0: } michael@0: michael@0: bool isVisible = false; michael@0: const InfallibleTArray& browsers = michael@0: mContentParent->ManagedPBrowserParent(); michael@0: for (uint32_t i = 0; i < browsers.Length(); i++) { michael@0: if (static_cast(browsers[i])->IsVisible()) { michael@0: isVisible = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (isVisible) { michael@0: return HasAppType("keyboard") ? michael@0: PROCESS_PRIORITY_FOREGROUND_KEYBOARD : michael@0: PROCESS_PRIORITY_FOREGROUND; michael@0: } michael@0: michael@0: if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) && michael@0: IsExpectingSystemMessage()) { michael@0: return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE; michael@0: } michael@0: michael@0: AudioChannelService* service = AudioChannelService::GetAudioChannelService(); michael@0: if (service->ProcessContentOrNormalChannelIsActive(ChildID())) { michael@0: return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE; michael@0: } michael@0: michael@0: return HasAppType("homescreen") ? michael@0: PROCESS_PRIORITY_BACKGROUND_HOMESCREEN : michael@0: PROCESS_PRIORITY_BACKGROUND; michael@0: } michael@0: michael@0: ProcessCPUPriority michael@0: ParticularProcessPriorityManager::ComputeCPUPriority(ProcessPriority aPriority) michael@0: { michael@0: if (aPriority == PROCESS_PRIORITY_PREALLOC) { michael@0: return PROCESS_CPU_PRIORITY_LOW; michael@0: } michael@0: michael@0: if (aPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) { michael@0: return PROCESS_CPU_PRIORITY_NORMAL; michael@0: } michael@0: michael@0: return ProcessPriorityManagerImpl::GetSingleton()-> michael@0: OtherProcessHasHighPriority(this) ? michael@0: PROCESS_CPU_PRIORITY_LOW : michael@0: PROCESS_CPU_PRIORITY_NORMAL; michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::ResetCPUPriorityNow() michael@0: { michael@0: SetPriorityNow(mPriority); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority, michael@0: uint32_t aBackgroundLRU) michael@0: { michael@0: SetPriorityNow(aPriority, ComputeCPUPriority(aPriority), aBackgroundLRU); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority, michael@0: ProcessCPUPriority aCPUPriority, michael@0: uint32_t aBackgroundLRU) michael@0: { michael@0: if (aPriority == PROCESS_PRIORITY_UNKNOWN) { michael@0: MOZ_ASSERT(false); michael@0: return; michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: // Do not attempt to change the priority of the Nuwa process michael@0: if (mContentParent->IsNuwaProcess()) { michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: if (aBackgroundLRU > 0 && michael@0: aPriority == PROCESS_PRIORITY_BACKGROUND && michael@0: mPriority == PROCESS_PRIORITY_BACKGROUND) { michael@0: hal::SetProcessPriority(Pid(), mPriority, mCPUPriority, aBackgroundLRU); michael@0: michael@0: nsPrintfCString ProcessPriorityWithBackgroundLRU("%s:%d", michael@0: ProcessPriorityToString(mPriority, mCPUPriority), michael@0: aBackgroundLRU); michael@0: michael@0: FireTestOnlyObserverNotification("process-priority-with-background-LRU-set", michael@0: ProcessPriorityWithBackgroundLRU.get()); michael@0: } michael@0: michael@0: if (!mContentParent || michael@0: !ProcessPriorityManagerImpl::PrefsEnabled() || michael@0: (mPriority == aPriority && mCPUPriority == aCPUPriority)) { michael@0: return; michael@0: } michael@0: michael@0: // If the prefs were disabled after this ParticularProcessPriorityManager was michael@0: // created, we can at least avoid any further calls to michael@0: // hal::SetProcessPriority. Supporting dynamic enabling/disabling of the michael@0: // ProcessPriorityManager is mostly for testing. michael@0: if (!ProcessPriorityManagerImpl::PrefsEnabled()) { michael@0: return; michael@0: } michael@0: michael@0: if (aPriority == PROCESS_PRIORITY_BACKGROUND && michael@0: mPriority != PROCESS_PRIORITY_BACKGROUND && michael@0: !IsPreallocated()) { michael@0: ProcessPriorityManager::AddIntoBackgroundLRUPool(mContentParent); michael@0: } michael@0: michael@0: if (aPriority != PROCESS_PRIORITY_BACKGROUND && michael@0: mPriority == PROCESS_PRIORITY_BACKGROUND && michael@0: !IsPreallocated()) { michael@0: ProcessPriorityManager::RemoveFromBackgroundLRUPool(mContentParent); michael@0: } michael@0: michael@0: LOGP("Changing priority from %s to %s.", michael@0: ProcessPriorityToString(mPriority, mCPUPriority), michael@0: ProcessPriorityToString(aPriority, aCPUPriority)); michael@0: michael@0: ProcessPriority oldPriority = mPriority; michael@0: michael@0: mPriority = aPriority; michael@0: mCPUPriority = aCPUPriority; michael@0: hal::SetProcessPriority(Pid(), mPriority, mCPUPriority); michael@0: michael@0: if (oldPriority != mPriority) { michael@0: unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority); michael@0: } michael@0: michael@0: if (aPriority < PROCESS_PRIORITY_FOREGROUND) { michael@0: unused << mContentParent->SendFlushMemory(NS_LITERAL_STRING("low-memory")); michael@0: } michael@0: michael@0: FireTestOnlyObserverNotification("process-priority-set", michael@0: ProcessPriorityToString(mPriority, mCPUPriority)); michael@0: michael@0: if (oldPriority != mPriority) { michael@0: ProcessPriorityManagerImpl::GetSingleton()-> michael@0: NotifyProcessPriorityChanged(this, oldPriority); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::ShutDown() michael@0: { michael@0: MOZ_ASSERT(mContentParent); michael@0: michael@0: UnregisterWakeLockObserver(this); michael@0: michael@0: if (mResetPriorityTimer) { michael@0: mResetPriorityTimer->Cancel(); michael@0: mResetPriorityTimer = nullptr; michael@0: } michael@0: michael@0: if (mPriority == PROCESS_PRIORITY_BACKGROUND && !IsPreallocated()) { michael@0: ProcessPriorityManager::RemoveFromBackgroundLRUPool(mContentParent); michael@0: } michael@0: michael@0: mContentParent = nullptr; michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerImpl::FireTestOnlyObserverNotification( michael@0: const char* aTopic, michael@0: const nsACString& aData /* = EmptyCString() */) michael@0: { michael@0: if (!Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(os); michael@0: michael@0: nsPrintfCString topic("process-priority-manager:TEST-ONLY:%s", aTopic); michael@0: michael@0: LOG("Notifying observer %s, data %s", michael@0: topic.get(), PromiseFlatCString(aData).get()); michael@0: os->NotifyObservers(nullptr, topic.get(), NS_ConvertUTF8toUTF16(aData).get()); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::FireTestOnlyObserverNotification( michael@0: const char* aTopic, michael@0: const char* aData /* = nullptr */ ) michael@0: { michael@0: if (!Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString data; michael@0: if (aData) { michael@0: data.AppendASCII(aData); michael@0: } michael@0: michael@0: FireTestOnlyObserverNotification(aTopic, data); michael@0: } michael@0: michael@0: void michael@0: ParticularProcessPriorityManager::FireTestOnlyObserverNotification( michael@0: const char* aTopic, michael@0: const nsACString& aData /* = EmptyCString() */) michael@0: { michael@0: if (!Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoCString data(nsPrintfCString("%lld", ChildID())); michael@0: if (!aData.IsEmpty()) { michael@0: data.AppendLiteral(":"); michael@0: data.Append(aData); michael@0: } michael@0: michael@0: // ProcessPriorityManagerImpl::GetSingleton() is guaranteed not to return michael@0: // null, since ProcessPriorityManagerImpl is the only class which creates michael@0: // ParticularProcessPriorityManagers. michael@0: michael@0: ProcessPriorityManagerImpl::GetSingleton()-> michael@0: FireTestOnlyObserverNotification(aTopic, data); michael@0: } michael@0: michael@0: StaticRefPtr michael@0: ProcessPriorityManagerChild::sSingleton; michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManagerChild::StaticInit() michael@0: { michael@0: if (!sSingleton) { michael@0: sSingleton = new ProcessPriorityManagerChild(); michael@0: sSingleton->Init(); michael@0: ClearOnShutdown(&sSingleton); michael@0: } michael@0: } michael@0: michael@0: /* static */ ProcessPriorityManagerChild* michael@0: ProcessPriorityManagerChild::Singleton() michael@0: { michael@0: StaticInit(); michael@0: return sSingleton; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(ProcessPriorityManagerChild, michael@0: nsIObserver) michael@0: michael@0: ProcessPriorityManagerChild::ProcessPriorityManagerChild() michael@0: { michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: mCachedPriority = PROCESS_PRIORITY_MASTER; michael@0: } else { michael@0: mCachedPriority = PROCESS_PRIORITY_UNKNOWN; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ProcessPriorityManagerChild::Init() michael@0: { michael@0: // The process priority should only be changed in child processes; don't even michael@0: // bother listening for changes if we're in the main process. michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: NS_ENSURE_TRUE_VOID(os); michael@0: os->AddObserver(this, "ipc:process-priority-changed", /* weak = */ false); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ProcessPriorityManagerChild::Observe( michael@0: nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: MOZ_ASSERT(!strcmp(aTopic, "ipc:process-priority-changed")); michael@0: michael@0: nsCOMPtr props = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE(props, NS_OK); michael@0: michael@0: int32_t priority = static_cast(PROCESS_PRIORITY_UNKNOWN); michael@0: props->GetPropertyAsInt32(NS_LITERAL_STRING("priority"), &priority); michael@0: NS_ENSURE_TRUE(ProcessPriority(priority) != PROCESS_PRIORITY_UNKNOWN, NS_OK); michael@0: michael@0: mCachedPriority = static_cast(priority); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: ProcessPriorityManagerChild::CurrentProcessIsForeground() michael@0: { michael@0: return mCachedPriority == PROCESS_PRIORITY_UNKNOWN || michael@0: mCachedPriority >= PROCESS_PRIORITY_FOREGROUND; michael@0: } michael@0: michael@0: bool michael@0: ProcessPriorityManagerChild::CurrentProcessIsHighPriority() michael@0: { michael@0: return mCachedPriority == PROCESS_PRIORITY_UNKNOWN || michael@0: mCachedPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH; michael@0: } michael@0: michael@0: /* static */ StaticAutoPtr michael@0: BackgroundProcessLRUPool::sSingleton; michael@0: michael@0: /* static */ BackgroundProcessLRUPool* michael@0: BackgroundProcessLRUPool::Singleton() michael@0: { michael@0: if (!sSingleton) { michael@0: sSingleton = new BackgroundProcessLRUPool(); michael@0: ClearOnShutdown(&sSingleton); michael@0: } michael@0: return sSingleton; michael@0: } michael@0: michael@0: BackgroundProcessLRUPool::BackgroundProcessLRUPool() michael@0: { michael@0: EnsureLRUPool(); michael@0: } michael@0: michael@0: uint32_t michael@0: BackgroundProcessLRUPool::CalculateLRULevel(uint32_t aBackgroundLRUPoolIndex) michael@0: { michael@0: // Set LRU level of each background process and maintain LRU buffer as below: michael@0: michael@0: // Priority background : LRU0 michael@0: // Priority background+1: LRU1, LRU2 michael@0: // Priority background+2: LRU3, LRU4, LRU5, LRU6 michael@0: // Priority background+3: LRU7, LRU8, LRU9, LRU10, LRU11, LRU12, LRU13, LRU14 michael@0: // ... michael@0: // Priority background+L-1: 2^(number of background LRU pool levels - 1) michael@0: // (End of buffer) michael@0: michael@0: return (uint32_t)(log((float)aBackgroundLRUPoolIndex) / log(2.0)); michael@0: } michael@0: michael@0: void michael@0: BackgroundProcessLRUPool::EnsureLRUPool() michael@0: { michael@0: // We set mBackgroundLRUPoolLevels according to our pref. michael@0: // This value is used to set background process LRU pool michael@0: if (!NS_SUCCEEDED(Preferences::GetInt( michael@0: "dom.ipc.processPriorityManager.backgroundLRUPoolLevels", michael@0: &mLRUPoolLevels))) { michael@0: mLRUPoolLevels = 1; michael@0: } michael@0: michael@0: if (mLRUPoolLevels <= 0) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: // GonkHal defines OOM_ADJUST_MAX is 15 and b2g.js defines michael@0: // PROCESS_PRIORITY_BACKGROUND's oom_score_adj is 667 and oom_adj is 10. michael@0: // This means we can only have at most (15 -10 + 1) = 6 background LRU levels. michael@0: // See bug 822325 comment 49 michael@0: MOZ_ASSERT(mLRUPoolLevels <= 6); michael@0: michael@0: // LRU pool size = 2 ^ (number of background LRU pool levels) - 1 michael@0: mLRUPoolSize = (1 << mLRUPoolLevels) - 1; michael@0: michael@0: mLRUPoolAvailableIndex = 0; michael@0: michael@0: LOG("Making background LRU pool with size(%d)", mLRUPoolSize); michael@0: michael@0: mLRUPool.InsertElementsAt(0, mLRUPoolSize, (ContentParent*)nullptr); michael@0: } michael@0: michael@0: void michael@0: BackgroundProcessLRUPool::RemoveFromBackgroundLRUPool( michael@0: ContentParent* aContentParent) michael@0: { michael@0: for (int32_t i = 0; i < mLRUPoolSize; i++) { michael@0: if (mLRUPool[i]) { michael@0: if (mLRUPool[i]->ChildID() == aContentParent->ChildID()) { michael@0: michael@0: mLRUPool[i] = nullptr; michael@0: LOG("Remove ChildID(%llu) from LRU pool", aContentParent->ChildID()); michael@0: michael@0: // After we remove this ContentParent from LRU pool, we still need to michael@0: // update the available index if the index of removed one is less than michael@0: // the available index we already have. michael@0: UpdateAvailableIndexInLRUPool(aContentParent, i); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: BackgroundProcessLRUPool::UpdateAvailableIndexInLRUPool( michael@0: ContentParent* aContentParent, michael@0: int32_t aTargetIndex) michael@0: { michael@0: // If we specify which index we want to assign to mLRUPoolAvailableIndex, michael@0: // We have to make sure the index in LRUPool doesn't point to any michael@0: // ContentParent. michael@0: if (aTargetIndex >= 0 && aTargetIndex < mLRUPoolSize && michael@0: aTargetIndex < mLRUPoolAvailableIndex && michael@0: !mLRUPool[aTargetIndex]) { michael@0: mLRUPoolAvailableIndex = aTargetIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // When we didn't specify any legal aTargetIndex, then we just check michael@0: // whether current mLRUPoolAvailableIndex points to any ContentParent or not. michael@0: if (mLRUPoolAvailableIndex >= 0 && mLRUPoolAvailableIndex < mLRUPoolSize && michael@0: !(mLRUPool[mLRUPoolAvailableIndex])) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Both above way failed. So now we have to find proper value michael@0: // for mLRUPoolAvailableIndex. michael@0: // We are looking for an available index. We only shift process with michael@0: // LRU less than the available index should have, so we stop update michael@0: // mLRUPoolAvailableIndex from the for loop once we got a candidate. michael@0: mLRUPoolAvailableIndex = -1; michael@0: michael@0: for (int32_t i = 0; i < mLRUPoolSize; i++) { michael@0: if (mLRUPool[i]) { michael@0: if (mLRUPool[i]->ChildID() == aContentParent->ChildID()) { michael@0: LOG("ChildID(%llu) already in LRU pool", aContentParent->ChildID()); michael@0: MOZ_ASSERT(false); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: continue; michael@0: } else { michael@0: if (mLRUPoolAvailableIndex == -1) { michael@0: mLRUPoolAvailableIndex = i; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If the LRUPool is already full, mLRUPoolAvailableIndex is still -1 after michael@0: // above loop finished. We should set mLRUPoolAvailableIndex michael@0: // to mLRUPoolSize - 1 in this case. Here uses the mod operator to do it: michael@0: // New mLRUPoolAvailableIndex either equals old mLRUPoolAvailableIndex, or michael@0: // mLRUPoolSize - 1 if old mLRUPoolAvailableIndex is -1. michael@0: mLRUPoolAvailableIndex = michael@0: (mLRUPoolAvailableIndex + mLRUPoolSize) % mLRUPoolSize; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: BackgroundProcessLRUPool::ShiftLRUPool() michael@0: { michael@0: for (int32_t i = mLRUPoolAvailableIndex; i > 0; i--) { michael@0: mLRUPool[i] = mLRUPool[i - 1]; michael@0: // Check whether i+1 is power of Two. michael@0: // If so, then it crossed a LRU group boundary and michael@0: // we need to assign its new process priority LRU. michael@0: if (!((i + 1) & i)) { michael@0: ProcessPriorityManagerImpl::GetSingleton()->SetProcessPriority( michael@0: mLRUPool[i], PROCESS_PRIORITY_BACKGROUND, CalculateLRULevel(i + 1)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: BackgroundProcessLRUPool::AddIntoBackgroundLRUPool( michael@0: ContentParent* aContentParent) michael@0: { michael@0: // We have to make sure that we have correct available index in LRU pool michael@0: if (!NS_SUCCEEDED( michael@0: UpdateAvailableIndexInLRUPool(aContentParent))) { michael@0: return; michael@0: } michael@0: michael@0: // Shift the list in the pool, so we have room at index 0 for the newly added michael@0: // ContentParent michael@0: ShiftLRUPool(); michael@0: michael@0: mLRUPool[0] = aContentParent; michael@0: michael@0: LOG("Add ChildID(%llu) into LRU pool", aContentParent->ChildID()); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManager::Init() michael@0: { michael@0: ProcessPriorityManagerImpl::StaticInit(); michael@0: ProcessPriorityManagerChild::StaticInit(); michael@0: } michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManager::SetProcessPriority(ContentParent* aContentParent, michael@0: ProcessPriority aPriority) michael@0: { michael@0: MOZ_ASSERT(aContentParent); michael@0: michael@0: ProcessPriorityManagerImpl* singleton = michael@0: ProcessPriorityManagerImpl::GetSingleton(); michael@0: if (singleton) { michael@0: singleton->SetProcessPriority(aContentParent, aPriority); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManager::RemoveFromBackgroundLRUPool( michael@0: ContentParent* aContentParent) michael@0: { michael@0: MOZ_ASSERT(aContentParent); michael@0: michael@0: BackgroundProcessLRUPool* singleton = michael@0: BackgroundProcessLRUPool::Singleton(); michael@0: if (singleton) { michael@0: singleton->RemoveFromBackgroundLRUPool(aContentParent); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: ProcessPriorityManager::AddIntoBackgroundLRUPool(ContentParent* aContentParent) michael@0: { michael@0: MOZ_ASSERT(aContentParent); michael@0: michael@0: BackgroundProcessLRUPool* singleton = michael@0: BackgroundProcessLRUPool::Singleton(); michael@0: if (singleton) { michael@0: singleton->AddIntoBackgroundLRUPool(aContentParent); michael@0: } michael@0: } michael@0: michael@0: /* static */ bool michael@0: ProcessPriorityManager::CurrentProcessIsForeground() michael@0: { michael@0: return ProcessPriorityManagerChild::Singleton()-> michael@0: CurrentProcessIsForeground(); michael@0: } michael@0: michael@0: /* static */ bool michael@0: ProcessPriorityManager::AnyProcessHasHighPriority() michael@0: { michael@0: ProcessPriorityManagerImpl* singleton = michael@0: ProcessPriorityManagerImpl::GetSingleton(); michael@0: michael@0: if (singleton) { michael@0: return singleton->ChildProcessHasHighPriority(); michael@0: } else { michael@0: return ProcessPriorityManagerChild::Singleton()-> michael@0: CurrentProcessIsHighPriority(); michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla