ProcessPriorityManager.cpp (34845B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ProcessPriorityManager.h" 8 9 #include "StaticPtr.h" 10 #include "mozilla/ClearOnShutdown.h" 11 #include "mozilla/Hal.h" 12 #include "mozilla/IntegerPrintfMacros.h" 13 #include "mozilla/Logging.h" 14 #include "mozilla/Preferences.h" 15 #include "mozilla/ProfilerMarkers.h" 16 #include "mozilla/ProfilerState.h" 17 #include "mozilla/Services.h" 18 #include "mozilla/StaticPrefs_dom.h" 19 #include "mozilla/StaticPrefs_threads.h" 20 #include "mozilla/dom/BrowserHost.h" 21 #include "mozilla/dom/BrowserParent.h" 22 #include "mozilla/dom/CanonicalBrowsingContext.h" 23 #include "mozilla/dom/ContentParent.h" 24 #include "mozilla/dom/Element.h" 25 #include "mozilla/glean/DomMetrics.h" 26 #include "nsCRT.h" 27 #include "nsComponentManagerUtils.h" 28 #include "nsFrameLoader.h" 29 #include "nsINamed.h" 30 #include "nsIObserver.h" 31 #include "nsIObserverService.h" 32 #include "nsIPropertyBag2.h" 33 #include "nsITimer.h" 34 #include "nsPrintfCString.h" 35 #include "nsQueryObject.h" 36 #include "nsTHashMap.h" 37 #include "nsTHashSet.h" 38 #include "nsXULAppAPI.h" 39 40 using namespace mozilla; 41 using namespace mozilla::dom; 42 using namespace mozilla::hal; 43 44 #ifdef XP_WIN 45 # include <process.h> 46 # define getpid _getpid 47 #else 48 # include <unistd.h> 49 #endif 50 51 #ifdef LOG 52 # undef LOG 53 #endif 54 55 // Use LOGP inside a ParticularProcessPriorityManager method; use LOG 56 // everywhere else. LOGP prints out information about the particular process 57 // priority manager. 58 // 59 // (Wow, our logging story is a huge mess.) 60 61 // #define ENABLE_LOGGING 1 62 63 #if defined(ANDROID) && defined(ENABLE_LOGGING) 64 # include <android/log.h> 65 # define LOG(fmt, ...) \ 66 __android_log_print(ANDROID_LOG_INFO, "Gecko:ProcessPriorityManager", fmt, \ 67 ##__VA_ARGS__) 68 # define LOGP(fmt, ...) \ 69 __android_log_print( \ 70 ANDROID_LOG_INFO, "Gecko:ProcessPriorityManager", \ 71 "[%schild-id=%" PRIu64 ", pid=%d] " fmt, NameWithComma().get(), \ 72 static_cast<uint64_t>(ChildID()), Pid(), ##__VA_ARGS__) 73 74 #elif defined(ENABLE_LOGGING) 75 # define LOG(fmt, ...) \ 76 printf("ProcessPriorityManager - " fmt "\n", ##__VA_ARGS__) 77 # define LOGP(fmt, ...) \ 78 printf("ProcessPriorityManager[%schild-id=%" PRIu64 ", pid=%d] - " fmt \ 79 "\n", \ 80 NameWithComma().get(), static_cast<uint64_t>(ChildID()), Pid(), \ 81 ##__VA_ARGS__) 82 #else 83 static LogModule* GetPPMLog() { 84 static LazyLogModule sLog("ProcessPriorityManager"); 85 return sLog; 86 } 87 # define LOG(fmt, ...) \ 88 MOZ_LOG(GetPPMLog(), LogLevel::Debug, \ 89 ("ProcessPriorityManager - " fmt, ##__VA_ARGS__)) 90 # define LOGP(fmt, ...) \ 91 MOZ_LOG(GetPPMLog(), LogLevel::Debug, \ 92 ("ProcessPriorityManager[%schild-id=%" PRIu64 ", pid=%d] - " fmt, \ 93 NameWithComma().get(), static_cast<uint64_t>(ChildID()), Pid(), \ 94 ##__VA_ARGS__)) 95 #endif 96 97 namespace geckoprofiler::markers { 98 struct SubProcessPriorityChange { 99 static constexpr Span<const char> MarkerTypeName() { 100 return MakeStringSpan("subprocessprioritychange"); 101 } 102 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, 103 int32_t aPid, 104 const ProfilerString8View& aPreviousPriority, 105 const ProfilerString8View& aNewPriority) { 106 aWriter.IntProperty("pid", aPid); 107 aWriter.StringProperty("Before", aPreviousPriority); 108 aWriter.StringProperty("After", aNewPriority); 109 } 110 static MarkerSchema MarkerTypeDisplay() { 111 using MS = MarkerSchema; 112 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; 113 schema.AddKeyFormat("pid", MS::Format::Integer); 114 schema.AddKeyFormat("Before", MS::Format::String); 115 schema.AddKeyFormat("After", MS::Format::String); 116 schema.SetAllLabels( 117 "priority of child {marker.data.pid}:" 118 " {marker.data.Before} -> {marker.data.After}"); 119 return schema; 120 } 121 }; 122 123 struct SubProcessPriority { 124 static constexpr Span<const char> MarkerTypeName() { 125 return MakeStringSpan("subprocesspriority"); 126 } 127 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, 128 int32_t aPid, 129 const ProfilerString8View& aPriority, 130 const ProfilingState& aProfilingState) { 131 aWriter.IntProperty("pid", aPid); 132 aWriter.StringProperty("Priority", aPriority); 133 aWriter.StringProperty("Marker cause", 134 ProfilerString8View::WrapNullTerminatedString( 135 ProfilingStateToString(aProfilingState))); 136 } 137 static MarkerSchema MarkerTypeDisplay() { 138 using MS = MarkerSchema; 139 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; 140 schema.AddKeyFormat("pid", MS::Format::Integer); 141 schema.AddKeyFormat("Priority", MS::Format::String); 142 schema.AddKeyFormat("Marker cause", MS::Format::String); 143 schema.SetAllLabels( 144 "priority of child {marker.data.pid}: {marker.data.Priority}"); 145 return schema; 146 } 147 }; 148 } // namespace geckoprofiler::markers 149 150 namespace { 151 152 class ParticularProcessPriorityManager; 153 154 /** 155 * This singleton class does the work to implement the process priority manager 156 * in the main process. This class may not be used in child processes. (You 157 * can call StaticInit, but it won't do anything, and GetSingleton() will 158 * return null.) 159 * 160 * ProcessPriorityManager::CurrentProcessIsForeground() and 161 * ProcessPriorityManager::AnyProcessHasHighPriority() which can be called in 162 * any process, are handled separately, by the ProcessPriorityManagerChild 163 * class. 164 */ 165 class ProcessPriorityManagerImpl final : public nsIObserver, 166 public nsSupportsWeakReference { 167 public: 168 /** 169 * If we're in the main process, get the ProcessPriorityManagerImpl 170 * singleton. If we're in a child process, return null. 171 */ 172 static ProcessPriorityManagerImpl* GetSingleton(); 173 174 static void StaticInit(); 175 static bool PrefsEnabled(); 176 static void SetProcessPriorityIfEnabled(int aPid, ProcessPriority aPriority); 177 static bool TestMode(); 178 179 NS_DECL_ISUPPORTS 180 NS_DECL_NSIOBSERVER 181 182 /** 183 * This function implements ProcessPriorityManager::SetProcessPriority. 184 */ 185 void SetProcessPriority(ContentParent* aContentParent, 186 ProcessPriority aPriority); 187 188 /** 189 * If a magic testing-only pref is set, notify the observer service on the 190 * given topic with the given data. This is used for testing 191 */ 192 void FireTestOnlyObserverNotification(const char* aTopic, 193 const nsACString& aData); 194 195 /** 196 * This must be called by a ParticularProcessPriorityManager when it changes 197 * its priority. 198 */ 199 void NotifyProcessPriorityChanged( 200 ParticularProcessPriorityManager* aParticularManager, 201 hal::ProcessPriority aOldPriority); 202 203 void BrowserPriorityChanged(CanonicalBrowsingContext* aBC, bool aPriority); 204 void BrowserPriorityChanged(BrowserParent* aBrowserParent, bool aPriority); 205 206 private: 207 static bool sPrefListenersRegistered; 208 static bool sInitialized; 209 static StaticRefPtr<ProcessPriorityManagerImpl> sSingleton; 210 211 static void PrefChangedCallback(const char* aPref, void* aClosure); 212 213 ProcessPriorityManagerImpl(); 214 ~ProcessPriorityManagerImpl(); 215 ProcessPriorityManagerImpl(const ProcessPriorityManagerImpl&) = delete; 216 217 const ProcessPriorityManagerImpl& operator=( 218 const ProcessPriorityManagerImpl&) = delete; 219 220 void Init(); 221 222 already_AddRefed<ParticularProcessPriorityManager> 223 GetParticularProcessPriorityManager(ContentParent* aContentParent); 224 225 void ObserveContentParentDestroyed(nsISupports* aSubject); 226 227 nsTHashMap<uint64_t, RefPtr<ParticularProcessPriorityManager> > 228 mParticularManagers; 229 230 /** Contains the PIDs of child processes holding high-priority wakelocks */ 231 nsTHashSet<uint64_t> mHighPriorityChildIDs; 232 }; 233 234 /** 235 * This singleton class implements the parts of the process priority manager 236 * that are available from all processes. 237 */ 238 class ProcessPriorityManagerChild final : public nsIObserver { 239 public: 240 static void StaticInit(); 241 static ProcessPriorityManagerChild* Singleton(); 242 243 NS_DECL_ISUPPORTS 244 NS_DECL_NSIOBSERVER 245 246 bool CurrentProcessIsForeground(); 247 248 private: 249 static StaticRefPtr<ProcessPriorityManagerChild> sSingleton; 250 251 ProcessPriorityManagerChild(); 252 ~ProcessPriorityManagerChild() = default; 253 ProcessPriorityManagerChild(const ProcessPriorityManagerChild&) = delete; 254 255 const ProcessPriorityManagerChild& operator=( 256 const ProcessPriorityManagerChild&) = delete; 257 258 void Init(); 259 260 hal::ProcessPriority mCachedPriority; 261 }; 262 263 /** 264 * This class manages the priority of one particular process. It is 265 * main-process only. 266 */ 267 class ParticularProcessPriorityManager final : public WakeLockObserver, 268 public nsITimerCallback, 269 public nsINamed, 270 public nsSupportsWeakReference { 271 ~ParticularProcessPriorityManager(); 272 273 public: 274 explicit ParticularProcessPriorityManager(ContentParent* aContentParent); 275 276 NS_DECL_ISUPPORTS 277 NS_DECL_NSITIMERCALLBACK 278 279 virtual void Notify(const WakeLockInformation& aInfo) override; 280 void Init(); 281 282 int32_t Pid() const; 283 uint64_t ChildID() const; 284 285 /** 286 * Used in logging, this method returns the ContentParent's name followed by 287 * ", ". If we can't get the ContentParent's name for some reason, it 288 * returns an empty string. 289 * 290 * The reference returned here is guaranteed to be live until the next call 291 * to NameWithComma() or until the ParticularProcessPriorityManager is 292 * destroyed, whichever comes first. 293 */ 294 const nsAutoCString& NameWithComma(); 295 296 ProcessPriority CurrentPriority(); 297 ProcessPriority ComputePriority(); 298 299 enum TimeoutPref { 300 BACKGROUND_PERCEIVABLE_GRACE_PERIOD, 301 BACKGROUND_GRACE_PERIOD, 302 }; 303 304 void ScheduleResetPriority(TimeoutPref aTimeoutPref); 305 void ResetPriority(); 306 void ResetPriorityNow(); 307 void SetPriorityNow(ProcessPriority aPriority); 308 309 void BrowserPriorityChanged(BrowserParent* aBrowserParent, bool aPriority); 310 311 void ShutDown(); 312 313 NS_IMETHOD GetName(nsACString& aName) override { 314 aName.AssignLiteral("ParticularProcessPriorityManager"); 315 return NS_OK; 316 } 317 318 private: 319 void FireTestOnlyObserverNotification(const char* aTopic, const char* aData); 320 321 bool IsHoldingWakeLock(const nsAString& aTopic); 322 323 ContentParent* mContentParent; 324 uint64_t mChildID; 325 ProcessPriority mPriority; 326 bool mHoldsCPUWakeLock; 327 bool mHoldsHighPriorityWakeLock; 328 bool mHoldsPlayingAudioWakeLock; 329 bool mHoldsPlayingVideoWakeLock; 330 331 /** 332 * Used to implement NameWithComma(). 333 */ 334 nsAutoCString mNameWithComma; 335 336 nsCOMPtr<nsITimer> mResetPriorityTimer; 337 338 // This hashtable contains the list of high priority TabIds for this process. 339 nsTHashSet<uint64_t> mHighPriorityBrowserParents; 340 }; 341 342 /* static */ 343 bool ProcessPriorityManagerImpl::sInitialized = false; 344 /* static */ 345 bool ProcessPriorityManagerImpl::sPrefListenersRegistered = false; 346 /* static */ 347 StaticRefPtr<ProcessPriorityManagerImpl> ProcessPriorityManagerImpl::sSingleton; 348 349 NS_IMPL_ISUPPORTS(ProcessPriorityManagerImpl, nsIObserver, 350 nsISupportsWeakReference); 351 352 /* static */ 353 void ProcessPriorityManagerImpl::PrefChangedCallback(const char* aPref, 354 void* aClosure) { 355 StaticInit(); 356 if (!PrefsEnabled() && sSingleton) { 357 sSingleton = nullptr; 358 sInitialized = false; 359 } 360 } 361 362 /* static */ 363 bool ProcessPriorityManagerImpl::PrefsEnabled() { 364 return StaticPrefs::dom_ipc_processPriorityManager_enabled(); 365 } 366 367 /* static */ 368 void ProcessPriorityManagerImpl::SetProcessPriorityIfEnabled( 369 int aPid, ProcessPriority aPriority) { 370 // The preference doesn't disable the process priority manager, but only its 371 // effect. This way the IPCs still happen and can be used to collect telemetry 372 // about CPU use. 373 if (PrefsEnabled()) { 374 hal::SetProcessPriority(aPid, aPriority); 375 } 376 } 377 378 /* static */ 379 bool ProcessPriorityManagerImpl::TestMode() { 380 return StaticPrefs::dom_ipc_processPriorityManager_testMode(); 381 } 382 383 /* static */ 384 void ProcessPriorityManagerImpl::StaticInit() { 385 if (sInitialized) { 386 return; 387 } 388 389 // The process priority manager is main-process only. 390 if (!XRE_IsParentProcess()) { 391 sInitialized = true; 392 return; 393 } 394 395 // Run StaticInit() again if the pref changes. We don't expect this to 396 // happen in normal operation, but it happens during testing. 397 if (!sPrefListenersRegistered) { 398 sPrefListenersRegistered = true; 399 Preferences::RegisterCallback(PrefChangedCallback, 400 "dom.ipc.processPriorityManager.enabled"); 401 } 402 403 sInitialized = true; 404 405 sSingleton = new ProcessPriorityManagerImpl(); 406 sSingleton->Init(); 407 ClearOnShutdown(&sSingleton); 408 } 409 410 /* static */ 411 ProcessPriorityManagerImpl* ProcessPriorityManagerImpl::GetSingleton() { 412 if (!sSingleton) { 413 StaticInit(); 414 } 415 416 return sSingleton; 417 } 418 419 ProcessPriorityManagerImpl::ProcessPriorityManagerImpl() { 420 MOZ_ASSERT(XRE_IsParentProcess()); 421 } 422 423 ProcessPriorityManagerImpl::~ProcessPriorityManagerImpl() = default; 424 425 void ProcessPriorityManagerImpl::Init() { 426 LOG("Starting up. This is the parent process."); 427 428 // The parent process's priority never changes; set it here and then forget 429 // about it. We'll manage only subprocesses' priorities using the process 430 // priority manager. 431 SetProcessPriorityIfEnabled(getpid(), PROCESS_PRIORITY_PARENT_PROCESS); 432 433 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 434 if (os) { 435 os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ true); 436 } 437 } 438 439 NS_IMETHODIMP 440 ProcessPriorityManagerImpl::Observe(nsISupports* aSubject, const char* aTopic, 441 const char16_t* aData) { 442 nsDependentCString topic(aTopic); 443 if (topic.EqualsLiteral("ipc:content-shutdown")) { 444 ObserveContentParentDestroyed(aSubject); 445 } else { 446 MOZ_ASSERT(false); 447 } 448 449 return NS_OK; 450 } 451 452 already_AddRefed<ParticularProcessPriorityManager> 453 ProcessPriorityManagerImpl::GetParticularProcessPriorityManager( 454 ContentParent* aContentParent) { 455 // If this content parent is already being shut down, there's no 456 // need to adjust its priority. 457 if (aContentParent->IsDead()) { 458 return nullptr; 459 } 460 461 const uint64_t cpId = aContentParent->ChildID(); 462 return mParticularManagers.WithEntryHandle(cpId, [&](auto&& entry) { 463 if (!entry) { 464 entry.Insert(new ParticularProcessPriorityManager(aContentParent)); 465 entry.Data()->Init(); 466 } 467 return do_AddRef(entry.Data()); 468 }); 469 } 470 471 void ProcessPriorityManagerImpl::SetProcessPriority( 472 ContentParent* aContentParent, ProcessPriority aPriority) { 473 MOZ_ASSERT(aContentParent); 474 if (RefPtr pppm = GetParticularProcessPriorityManager(aContentParent)) { 475 pppm->SetPriorityNow(aPriority); 476 } 477 } 478 479 void ProcessPriorityManagerImpl::ObserveContentParentDestroyed( 480 nsISupports* aSubject) { 481 nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); 482 NS_ENSURE_TRUE_VOID(props); 483 484 uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; 485 props->GetPropertyAsUint64(u"childID"_ns, &childID); 486 NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN); 487 488 if (auto entry = mParticularManagers.Lookup(childID)) { 489 entry.Data()->ShutDown(); 490 mHighPriorityChildIDs.Remove(childID); 491 entry.Remove(); 492 } 493 } 494 495 void ProcessPriorityManagerImpl::NotifyProcessPriorityChanged( 496 ParticularProcessPriorityManager* aParticularManager, 497 ProcessPriority aOldPriority) { 498 ProcessPriority newPriority = aParticularManager->CurrentPriority(); 499 500 if (newPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH && 501 aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH) { 502 mHighPriorityChildIDs.Insert(aParticularManager->ChildID()); 503 } else if (newPriority < PROCESS_PRIORITY_FOREGROUND_HIGH && 504 aOldPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) { 505 mHighPriorityChildIDs.Remove(aParticularManager->ChildID()); 506 } 507 } 508 509 static nsCString BCToString(dom::CanonicalBrowsingContext* aBC) { 510 nsCOMPtr<nsIURI> uri = aBC->GetCurrentURI(); 511 return nsPrintfCString("id=%" PRIu64 " uri=%s active=%d pactive=%d", 512 aBC->Id(), 513 uri ? uri->GetSpecOrDefault().get() : "(no uri)", 514 aBC->IsActive(), aBC->IsPriorityActive()); 515 } 516 517 void ProcessPriorityManagerImpl::BrowserPriorityChanged( 518 dom::CanonicalBrowsingContext* aBC, bool aPriority) { 519 MOZ_ASSERT(aBC->IsTop()); 520 521 LOG("BrowserPriorityChanged(%s, %d)\n", BCToString(aBC).get(), aPriority); 522 523 bool alreadyActive = aBC->IsPriorityActive(); 524 if (alreadyActive == aPriority) { 525 return; 526 } 527 528 glean::dom_contentprocess::os_priority_change_considered.Add(1); 529 530 aBC->SetPriorityActive(aPriority); 531 532 aBC->PreOrderWalk([&](BrowsingContext* aContext) { 533 CanonicalBrowsingContext* canonical = aContext->Canonical(); 534 LOG("PreOrderWalk for %p: %p -> %p, %p\n", aBC, canonical, 535 canonical->GetContentParent(), canonical->GetBrowserParent()); 536 if (ContentParent* cp = canonical->GetContentParent()) { 537 if (RefPtr pppm = GetParticularProcessPriorityManager(cp)) { 538 if (auto* bp = canonical->GetBrowserParent()) { 539 pppm->BrowserPriorityChanged(bp, aPriority); 540 } 541 } 542 } 543 }); 544 } 545 546 void ProcessPriorityManagerImpl::BrowserPriorityChanged( 547 BrowserParent* aBrowserParent, bool aPriority) { 548 LOG("BrowserPriorityChanged(bp=%p, %d)\n", aBrowserParent, aPriority); 549 550 if (RefPtr pppm = 551 GetParticularProcessPriorityManager(aBrowserParent->Manager())) { 552 glean::dom_contentprocess::os_priority_change_considered.Add(1); 553 pppm->BrowserPriorityChanged(aBrowserParent, aPriority); 554 } 555 } 556 557 NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, nsITimerCallback, 558 nsISupportsWeakReference, nsINamed); 559 560 ParticularProcessPriorityManager::ParticularProcessPriorityManager( 561 ContentParent* aContentParent) 562 : mContentParent(aContentParent), 563 mChildID(aContentParent->ChildID()), 564 mPriority(PROCESS_PRIORITY_UNKNOWN), 565 mHoldsCPUWakeLock(false), 566 mHoldsHighPriorityWakeLock(false), 567 mHoldsPlayingAudioWakeLock(false), 568 mHoldsPlayingVideoWakeLock(false) { 569 MOZ_ASSERT(XRE_IsParentProcess()); 570 MOZ_RELEASE_ASSERT(!aContentParent->IsDead()); 571 LOGP("Creating ParticularProcessPriorityManager."); 572 // Our static analysis doesn't allow capturing ref-counted pointers in 573 // lambdas, so we need to hide it in a uintptr_t. This is safe because this 574 // lambda will be destroyed in ~ParticularProcessPriorityManager(). 575 uintptr_t self = reinterpret_cast<uintptr_t>(this); 576 profiler_add_state_change_callback( 577 AllProfilingStates(), 578 [self](ProfilingState aProfilingState) { 579 const ParticularProcessPriorityManager* selfPtr = 580 reinterpret_cast<const ParticularProcessPriorityManager*>(self); 581 PROFILER_MARKER("Subprocess Priority", OTHER, 582 MarkerThreadId::MainThread(), SubProcessPriority, 583 selfPtr->Pid(), 584 ProfilerString8View::WrapNullTerminatedString( 585 ProcessPriorityToString(selfPtr->mPriority)), 586 aProfilingState); 587 }, 588 self); 589 } 590 591 void ParticularProcessPriorityManager::Init() { 592 RegisterWakeLockObserver(this); 593 594 // This process may already hold the CPU lock; for example, our parent may 595 // have acquired it on our behalf. 596 mHoldsCPUWakeLock = IsHoldingWakeLock(u"cpu"_ns); 597 mHoldsHighPriorityWakeLock = IsHoldingWakeLock(u"high-priority"_ns); 598 mHoldsPlayingAudioWakeLock = IsHoldingWakeLock(u"audio-playing"_ns); 599 mHoldsPlayingVideoWakeLock = IsHoldingWakeLock(u"video-playing"_ns); 600 601 LOGP( 602 "Done starting up. mHoldsCPUWakeLock=%d, " 603 "mHoldsHighPriorityWakeLock=%d, mHoldsPlayingAudioWakeLock=%d, " 604 "mHoldsPlayingVideoWakeLock=%d", 605 mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock, mHoldsPlayingAudioWakeLock, 606 mHoldsPlayingVideoWakeLock); 607 } 608 609 bool ParticularProcessPriorityManager::IsHoldingWakeLock( 610 const nsAString& aTopic) { 611 WakeLockInformation info; 612 GetWakeLockInfo(aTopic, &info); 613 return info.lockingProcesses().Contains(ChildID()); 614 } 615 616 ParticularProcessPriorityManager::~ParticularProcessPriorityManager() { 617 LOGP("Destroying ParticularProcessPriorityManager."); 618 619 profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this)); 620 621 ShutDown(); 622 } 623 624 /* virtual */ 625 void ParticularProcessPriorityManager::Notify( 626 const WakeLockInformation& aInfo) { 627 if (!mContentParent) { 628 // We've been shut down. 629 return; 630 } 631 632 bool* dest = nullptr; 633 if (aInfo.topic().EqualsLiteral("cpu")) { 634 dest = &mHoldsCPUWakeLock; 635 } else if (aInfo.topic().EqualsLiteral("high-priority")) { 636 dest = &mHoldsHighPriorityWakeLock; 637 } else if (aInfo.topic().EqualsLiteral("audio-playing")) { 638 dest = &mHoldsPlayingAudioWakeLock; 639 } else if (aInfo.topic().EqualsLiteral("video-playing")) { 640 dest = &mHoldsPlayingVideoWakeLock; 641 } 642 643 if (dest) { 644 bool thisProcessLocks = aInfo.lockingProcesses().Contains(ChildID()); 645 if (thisProcessLocks != *dest) { 646 *dest = thisProcessLocks; 647 LOGP( 648 "Got wake lock changed event. " 649 "Now mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d, " 650 "mHoldsPlayingAudioWakeLock=%d, mHoldsPlayingVideoWakeLock=%d", 651 mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock, 652 mHoldsPlayingAudioWakeLock, mHoldsPlayingVideoWakeLock); 653 ResetPriority(); 654 } 655 } 656 } 657 658 uint64_t ParticularProcessPriorityManager::ChildID() const { 659 // We have to cache mContentParent->ChildID() instead of getting it from the 660 // ContentParent each time because after ShutDown() is called, mContentParent 661 // is null. If we didn't cache ChildID(), then we wouldn't be able to run 662 // LOGP() after ShutDown(). 663 return mChildID; 664 } 665 666 int32_t ParticularProcessPriorityManager::Pid() const { 667 return mContentParent ? mContentParent->Pid() : -1; 668 } 669 670 const nsAutoCString& ParticularProcessPriorityManager::NameWithComma() { 671 mNameWithComma.Truncate(); 672 if (!mContentParent) { 673 return mNameWithComma; // empty string 674 } 675 676 nsAutoString name; 677 mContentParent->FriendlyName(name); 678 if (name.IsEmpty()) { 679 return mNameWithComma; // empty string 680 } 681 682 CopyUTF16toUTF8(name, mNameWithComma); 683 mNameWithComma.AppendLiteral(", "); 684 return mNameWithComma; 685 } 686 687 void ParticularProcessPriorityManager::ResetPriority() { 688 ProcessPriority processPriority = ComputePriority(); 689 if (mPriority == PROCESS_PRIORITY_UNKNOWN || mPriority > processPriority) { 690 // Apps set at a perceivable background priority are often playing media. 691 // Most media will have short gaps while changing tracks between songs, 692 // switching videos, etc. Give these apps a longer grace period so they 693 // can get their next track started, if there is one, before getting 694 // downgraded. 695 if (mPriority == PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) { 696 ScheduleResetPriority(BACKGROUND_PERCEIVABLE_GRACE_PERIOD); 697 } else { 698 ScheduleResetPriority(BACKGROUND_GRACE_PERIOD); 699 } 700 return; 701 } 702 703 SetPriorityNow(processPriority); 704 } 705 706 void ParticularProcessPriorityManager::ResetPriorityNow() { 707 SetPriorityNow(ComputePriority()); 708 } 709 710 void ParticularProcessPriorityManager::ScheduleResetPriority( 711 TimeoutPref aTimeoutPref) { 712 if (mResetPriorityTimer) { 713 LOGP("ScheduleResetPriority bailing; the timer is already running."); 714 return; 715 } 716 717 uint32_t timeout = 0; 718 switch (aTimeoutPref) { 719 case BACKGROUND_PERCEIVABLE_GRACE_PERIOD: 720 timeout = StaticPrefs:: 721 dom_ipc_processPriorityManager_backgroundPerceivableGracePeriodMS(); 722 break; 723 case BACKGROUND_GRACE_PERIOD: 724 timeout = 725 StaticPrefs::dom_ipc_processPriorityManager_backgroundGracePeriodMS(); 726 break; 727 default: 728 MOZ_ASSERT(false, "Unrecognized timeout pref"); 729 break; 730 } 731 732 LOGP("Scheduling reset timer to fire in %dms.", timeout); 733 NS_NewTimerWithCallback(getter_AddRefs(mResetPriorityTimer), this, timeout, 734 nsITimer::TYPE_ONE_SHOT); 735 } 736 737 NS_IMETHODIMP 738 ParticularProcessPriorityManager::Notify(nsITimer* aTimer) { 739 LOGP("Reset priority timer callback; about to ResetPriorityNow."); 740 ResetPriorityNow(); 741 mResetPriorityTimer = nullptr; 742 return NS_OK; 743 } 744 745 ProcessPriority ParticularProcessPriorityManager::CurrentPriority() { 746 return mPriority; 747 } 748 749 ProcessPriority ParticularProcessPriorityManager::ComputePriority() { 750 if (!mHighPriorityBrowserParents.IsEmpty() || 751 mContentParent->GetRemoteType() == EXTENSION_REMOTE_TYPE || 752 mHoldsPlayingAudioWakeLock) { 753 return PROCESS_PRIORITY_FOREGROUND; 754 } 755 756 if (mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock || 757 mHoldsPlayingVideoWakeLock) { 758 return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE; 759 } 760 761 return PROCESS_PRIORITY_BACKGROUND; 762 } 763 764 #ifdef XP_MACOSX 765 // Method used for setting QoS levels on background main threads. 766 static bool PriorityUsesLowPowerMainThread( 767 const hal::ProcessPriority& aPriority) { 768 return aPriority == hal::PROCESS_PRIORITY_BACKGROUND || 769 aPriority == hal::PROCESS_PRIORITY_PREALLOC; 770 } 771 // Method reduces redundancy in pref check while addressing the edge case 772 // where a pref is flipped to false during active browser use. 773 static bool PrefsUseLowPriorityThreads() { 774 return StaticPrefs::threads_use_low_power_enabled() && 775 StaticPrefs::threads_lower_mainthread_priority_in_background_enabled(); 776 } 777 #endif 778 779 void ParticularProcessPriorityManager::SetPriorityNow( 780 ProcessPriority aPriority) { 781 if (aPriority == PROCESS_PRIORITY_UNKNOWN) { 782 MOZ_ASSERT(false); 783 return; 784 } 785 786 LOGP("Changing priority from %s to %s (cp=%p).", 787 ProcessPriorityToString(mPriority), ProcessPriorityToString(aPriority), 788 mContentParent); 789 790 if (!mContentParent || mPriority == aPriority) { 791 return; 792 } 793 794 PROFILER_MARKER( 795 "Subprocess Priority", OTHER, 796 MarkerOptions(MarkerThreadId::MainThread(), MarkerStack::Capture()), 797 SubProcessPriorityChange, this->Pid(), 798 ProfilerString8View::WrapNullTerminatedString( 799 ProcessPriorityToString(mPriority)), 800 ProfilerString8View::WrapNullTerminatedString( 801 ProcessPriorityToString(aPriority))); 802 803 ProcessPriority oldPriority = mPriority; 804 805 mPriority = aPriority; 806 807 // We skip incrementing the DOM_CONTENTPROCESS_OS_PRIORITY_RAISED if we're 808 // transitioning from the PROCESS_PRIORITY_UNKNOWN level, which is where 809 // we initialize at. 810 if (oldPriority < mPriority && oldPriority != PROCESS_PRIORITY_UNKNOWN) { 811 glean::dom_contentprocess::os_priority_raised.Add(1); 812 } else if (oldPriority > mPriority) { 813 glean::dom_contentprocess::os_priority_lowered.Add(1); 814 } 815 816 ProcessPriorityManagerImpl::SetProcessPriorityIfEnabled(Pid(), mPriority); 817 818 if (oldPriority != mPriority) { 819 ProcessPriorityManagerImpl::GetSingleton()->NotifyProcessPriorityChanged( 820 this, oldPriority); 821 822 #ifdef XP_MACOSX 823 // In cases where we have low-power threads enabled (such as on MacOS) we 824 // can go ahead and put the main thread in the background here. If the new 825 // priority is the background priority, we can tell the OS to put the main 826 // thread on low-power cores. Alternately, if we are changing from the 827 // background to a higher priority, we change the main thread back to its 828 // normal state. 829 // During shutdown, we will manually set the priority to the highest 830 // possible and disallow any additional priority changes. 831 // 832 // The messages for this will be relayed using the ProcessHangMonitor such 833 // that the priority can be raised even if the main thread is unresponsive. 834 if (!mContentParent->IsShuttingDown() && 835 PriorityUsesLowPowerMainThread(mPriority) != 836 PriorityUsesLowPowerMainThread(oldPriority)) { 837 if (PriorityUsesLowPowerMainThread(mPriority) && 838 PrefsUseLowPriorityThreads()) { 839 mContentParent->SetMainThreadQoSPriority(nsIThread::QOS_PRIORITY_LOW); 840 } else if (PriorityUsesLowPowerMainThread(oldPriority)) { 841 // In the event that the user changes prefs while tabs are in the 842 // background, we still want to have the ability to put the main thread 843 // back in the foreground to keep tabs from being stuck in the 844 // background priority. 845 mContentParent->SetMainThreadQoSPriority( 846 nsIThread::QOS_PRIORITY_NORMAL); 847 } 848 } 849 #endif 850 851 (void)mContentParent->SendNotifyProcessPriorityChanged(mPriority); 852 } 853 854 FireTestOnlyObserverNotification("process-priority-set", 855 ProcessPriorityToString(mPriority)); 856 } 857 858 void ParticularProcessPriorityManager::BrowserPriorityChanged( 859 BrowserParent* aBrowserParent, bool aPriority) { 860 MOZ_ASSERT(aBrowserParent); 861 862 if (!aPriority) { 863 mHighPriorityBrowserParents.Remove(aBrowserParent->GetTabId()); 864 } else { 865 mHighPriorityBrowserParents.Insert(aBrowserParent->GetTabId()); 866 } 867 868 ResetPriority(); 869 } 870 871 void ParticularProcessPriorityManager::ShutDown() { 872 LOGP("shutdown for %p (mContentParent %p)", this, mContentParent); 873 874 // Unregister our wake lock observer if ShutDown hasn't been called. (The 875 // wake lock observer takes raw refs, so we don't want to take chances here!) 876 // We don't call UnregisterWakeLockObserver unconditionally because the code 877 // will print a warning if it's called unnecessarily. 878 if (mContentParent) { 879 UnregisterWakeLockObserver(this); 880 } 881 882 if (mResetPriorityTimer) { 883 mResetPriorityTimer->Cancel(); 884 mResetPriorityTimer = nullptr; 885 } 886 887 mContentParent = nullptr; 888 } 889 890 void ProcessPriorityManagerImpl::FireTestOnlyObserverNotification( 891 const char* aTopic, const nsACString& aData) { 892 if (!TestMode()) { 893 return; 894 } 895 896 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 897 NS_ENSURE_TRUE_VOID(os); 898 899 nsPrintfCString topic("process-priority-manager:TEST-ONLY:%s", aTopic); 900 901 LOG("Notifying observer %s, data %s", topic.get(), 902 PromiseFlatCString(aData).get()); 903 os->NotifyObservers(nullptr, topic.get(), NS_ConvertUTF8toUTF16(aData).get()); 904 } 905 906 void ParticularProcessPriorityManager::FireTestOnlyObserverNotification( 907 const char* aTopic, const char* aData) { 908 MOZ_ASSERT(aData, "Pass in data"); 909 910 if (!ProcessPriorityManagerImpl::TestMode()) { 911 return; 912 } 913 914 nsAutoCString data(nsPrintfCString("%" PRIu64, ChildID())); 915 data.Append(':'); 916 data.AppendASCII(aData); 917 918 // ProcessPriorityManagerImpl::GetSingleton() is guaranteed not to return 919 // null, since ProcessPriorityManagerImpl is the only class which creates 920 // ParticularProcessPriorityManagers. 921 922 ProcessPriorityManagerImpl::GetSingleton()->FireTestOnlyObserverNotification( 923 aTopic, data); 924 } 925 926 StaticRefPtr<ProcessPriorityManagerChild> 927 ProcessPriorityManagerChild::sSingleton; 928 929 /* static */ 930 void ProcessPriorityManagerChild::StaticInit() { 931 if (!sSingleton) { 932 sSingleton = new ProcessPriorityManagerChild(); 933 sSingleton->Init(); 934 ClearOnShutdown(&sSingleton); 935 } 936 } 937 938 /* static */ 939 ProcessPriorityManagerChild* ProcessPriorityManagerChild::Singleton() { 940 StaticInit(); 941 return sSingleton; 942 } 943 944 NS_IMPL_ISUPPORTS(ProcessPriorityManagerChild, nsIObserver) 945 946 ProcessPriorityManagerChild::ProcessPriorityManagerChild() { 947 if (XRE_IsParentProcess()) { 948 mCachedPriority = PROCESS_PRIORITY_PARENT_PROCESS; 949 } else { 950 mCachedPriority = PROCESS_PRIORITY_UNKNOWN; 951 } 952 } 953 954 void ProcessPriorityManagerChild::Init() { 955 // The process priority should only be changed in child processes; don't even 956 // bother listening for changes if we're in the main process. 957 if (!XRE_IsParentProcess()) { 958 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 959 NS_ENSURE_TRUE_VOID(os); 960 os->AddObserver(this, "ipc:process-priority-changed", /* weak = */ false); 961 } 962 } 963 964 NS_IMETHODIMP 965 ProcessPriorityManagerChild::Observe(nsISupports* aSubject, const char* aTopic, 966 const char16_t* aData) { 967 MOZ_ASSERT(!strcmp(aTopic, "ipc:process-priority-changed")); 968 969 nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); 970 NS_ENSURE_TRUE(props, NS_OK); 971 972 int32_t priority = static_cast<int32_t>(PROCESS_PRIORITY_UNKNOWN); 973 props->GetPropertyAsInt32(u"priority"_ns, &priority); 974 NS_ENSURE_TRUE(ProcessPriority(priority) != PROCESS_PRIORITY_UNKNOWN, NS_OK); 975 976 mCachedPriority = static_cast<ProcessPriority>(priority); 977 978 return NS_OK; 979 } 980 981 bool ProcessPriorityManagerChild::CurrentProcessIsForeground() { 982 return mCachedPriority == PROCESS_PRIORITY_UNKNOWN || 983 mCachedPriority >= PROCESS_PRIORITY_FOREGROUND; 984 } 985 986 } // namespace 987 988 namespace mozilla { 989 990 /* static */ 991 void ProcessPriorityManager::Init() { 992 ProcessPriorityManagerImpl::StaticInit(); 993 ProcessPriorityManagerChild::StaticInit(); 994 } 995 996 /* static */ 997 void ProcessPriorityManager::SetProcessPriority(ContentParent* aContentParent, 998 ProcessPriority aPriority) { 999 MOZ_ASSERT(aContentParent); 1000 MOZ_ASSERT(aContentParent->Pid() != -1); 1001 1002 ProcessPriorityManagerImpl* singleton = 1003 ProcessPriorityManagerImpl::GetSingleton(); 1004 if (singleton) { 1005 singleton->SetProcessPriority(aContentParent, aPriority); 1006 } 1007 } 1008 1009 /* static */ 1010 bool ProcessPriorityManager::CurrentProcessIsForeground() { 1011 return ProcessPriorityManagerChild::Singleton()->CurrentProcessIsForeground(); 1012 } 1013 1014 /* static */ 1015 void ProcessPriorityManager::BrowserPriorityChanged( 1016 CanonicalBrowsingContext* aBC, bool aPriority) { 1017 if (auto* singleton = ProcessPriorityManagerImpl::GetSingleton()) { 1018 singleton->BrowserPriorityChanged(aBC, aPriority); 1019 } 1020 } 1021 1022 /* static */ 1023 void ProcessPriorityManager::BrowserPriorityChanged( 1024 BrowserParent* aBrowserParent, bool aPriority) { 1025 MOZ_ASSERT(aBrowserParent); 1026 1027 ProcessPriorityManagerImpl* singleton = 1028 ProcessPriorityManagerImpl::GetSingleton(); 1029 if (!singleton) { 1030 return; 1031 } 1032 singleton->BrowserPriorityChanged(aBrowserParent, aPriority); 1033 } 1034 1035 } // namespace mozilla