tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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