tor-browser

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

PreallocatedProcessManager.cpp (14413B)


      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 "mozilla/PreallocatedProcessManager.h"
      8 
      9 #include "ProcessPriorityManager.h"
     10 #include "mozilla/AppShutdown.h"
     11 #include "mozilla/ClearOnShutdown.h"
     12 #include "mozilla/Preferences.h"
     13 #include "mozilla/ProfilerMarkers.h"
     14 #include "mozilla/StaticPrefs_dom.h"
     15 #include "mozilla/dom/ContentParent.h"
     16 #include "mozilla/dom/ScriptSettings.h"
     17 #include "nsIPropertyBag2.h"
     18 #include "nsIXULRuntime.h"
     19 #include "nsServiceManagerUtils.h"
     20 #include "nsTArray.h"
     21 #include "prsystem.h"
     22 
     23 using namespace mozilla::hal;
     24 using namespace mozilla::dom;
     25 
     26 namespace mozilla {
     27 /**
     28 * This singleton class implements the static methods on
     29 * PreallocatedProcessManager.
     30 */
     31 class PreallocatedProcessManagerImpl final : public nsIObserver {
     32  friend class PreallocatedProcessManager;
     33 
     34 public:
     35  static PreallocatedProcessManagerImpl* Singleton();
     36 
     37  NS_DECL_ISUPPORTS
     38  NS_DECL_NSIOBSERVER
     39 
     40  // See comments on PreallocatedProcessManager for these methods.
     41  void AddBlocker(ContentParent* aParent);
     42  void RemoveBlocker(ContentParent* aParent);
     43  UniqueContentParentKeepAlive Take(const nsACString& aRemoteType);
     44  void Erase(ContentParent* aParent);
     45 
     46 private:
     47  static const char* const kObserverTopics[];
     48 
     49  static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
     50 
     51  PreallocatedProcessManagerImpl();
     52  ~PreallocatedProcessManagerImpl();
     53  PreallocatedProcessManagerImpl(const PreallocatedProcessManagerImpl&) =
     54      delete;
     55 
     56  const PreallocatedProcessManagerImpl& operator=(
     57      const PreallocatedProcessManagerImpl&) = delete;
     58 
     59  void Init();
     60 
     61  bool CanAllocate();
     62  void AllocateAfterDelay();
     63  void AllocateOnIdle();
     64  void AllocateNow();
     65 
     66  void RereadPrefs();
     67  void Enable(uint32_t aProcesses);
     68  void Disable();
     69  void CloseProcesses();
     70 
     71  bool IsEmpty() const { return mPreallocatedProcesses.IsEmpty(); }
     72  static bool IsShutdown() {
     73    return AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
     74  }
     75  bool IsEnabled() { return mEnabled && !IsShutdown(); }
     76 
     77  bool mEnabled;
     78  uint32_t mNumberPreallocs;
     79  AutoTArray<UniqueContentParentKeepAlive, 3> mPreallocatedProcesses;
     80  // Even if we have multiple PreallocatedProcessManagerImpls, we'll have
     81  // one blocker counter
     82  static uint32_t sNumBlockers;
     83  TimeStamp mBlockingStartTime;
     84 };
     85 
     86 /* static */
     87 uint32_t PreallocatedProcessManagerImpl::sNumBlockers = 0;
     88 
     89 const char* const PreallocatedProcessManagerImpl::kObserverTopics[] = {
     90    "memory-pressure",
     91    "profile-change-teardown",
     92    NS_XPCOM_SHUTDOWN_OBSERVER_ID,
     93 };
     94 
     95 /* static */
     96 StaticRefPtr<PreallocatedProcessManagerImpl>
     97    PreallocatedProcessManagerImpl::sSingleton;
     98 
     99 /* static */
    100 PreallocatedProcessManagerImpl* PreallocatedProcessManagerImpl::Singleton() {
    101  MOZ_ASSERT(NS_IsMainThread());
    102  if (!sSingleton) {
    103    sSingleton = new PreallocatedProcessManagerImpl;
    104    sSingleton->Init();
    105    ClearOnShutdown(&sSingleton);
    106  }
    107  return sSingleton;
    108  //  PreallocatedProcessManagers live until shutdown
    109 }
    110 
    111 NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
    112 
    113 PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
    114    : mEnabled(false), mNumberPreallocs(1) {}
    115 
    116 PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() {
    117  // Note: mPreallocatedProcesses may not be null, but all processes should
    118  // be dead (IsDead==true).  We block Erase() when our observer sees
    119  // shutdown starting.
    120 }
    121 
    122 void PreallocatedProcessManagerImpl::Init() {
    123  Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
    124  // We have to respect processCount at all time. This is especially important
    125  // for testing.
    126  Preferences::AddStrongObserver(this, "dom.ipc.processCount");
    127  // A StaticPref, but we need to adjust the number of preallocated processes
    128  // if the value goes up or down, so we need to run code on change.
    129  Preferences::AddStrongObserver(this,
    130                                 "dom.ipc.processPrelaunch.fission.number");
    131 
    132  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
    133  MOZ_ASSERT(os);
    134  for (auto topic : kObserverTopics) {
    135    os->AddObserver(this, topic, /* ownsWeak */ false);
    136  }
    137  RereadPrefs();
    138 }
    139 
    140 NS_IMETHODIMP
    141 PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject,
    142                                        const char* aTopic,
    143                                        const char16_t* aData) {
    144  if (!strcmp("nsPref:changed", aTopic)) {
    145    // The only other observer we registered was for our prefs.
    146    RereadPrefs();
    147  } else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) ||
    148             !strcmp("profile-change-teardown", aTopic)) {
    149    Preferences::RemoveObserver(this, "dom.ipc.processPrelaunch.enabled");
    150    Preferences::RemoveObserver(this, "dom.ipc.processCount");
    151    Preferences::RemoveObserver(this,
    152                                "dom.ipc.processPrelaunch.fission.number");
    153 
    154    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
    155    MOZ_ASSERT(os);
    156    for (auto topic : kObserverTopics) {
    157      os->RemoveObserver(this, topic);
    158    }
    159  } else if (!strcmp("memory-pressure", aTopic)) {
    160    CloseProcesses();
    161  } else {
    162    MOZ_ASSERT_UNREACHABLE("Unknown topic");
    163  }
    164 
    165  return NS_OK;
    166 }
    167 
    168 void PreallocatedProcessManagerImpl::RereadPrefs() {
    169  if (mozilla::BrowserTabsRemoteAutostart() &&
    170      Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) {
    171    int32_t number = 1;
    172    if (mozilla::FissionAutostart()) {
    173      number = StaticPrefs::dom_ipc_processPrelaunch_fission_number();
    174      // limit preallocated processes on low-mem machines
    175      PRUint64 bytes = PR_GetPhysicalMemorySize();
    176      if (bytes > 0 &&
    177          bytes <=
    178              StaticPrefs::dom_ipc_processPrelaunch_lowmem_mb() * 1024 * 1024) {
    179        number = 1;
    180      }
    181    }
    182    if (number >= 0) {
    183      Enable(number);
    184      // We have one prealloc queue for all types except File now
    185      if (static_cast<uint64_t>(number) < mPreallocatedProcesses.Length()) {
    186        CloseProcesses();
    187      }
    188    }
    189  } else {
    190    Disable();
    191  }
    192 }
    193 
    194 UniqueContentParentKeepAlive PreallocatedProcessManagerImpl::Take(
    195    const nsACString& aRemoteType) {
    196  if (!IsEnabled()) {
    197    return nullptr;
    198  }
    199  UniqueContentParentKeepAlive process;
    200  if (!IsEmpty()) {
    201    process = std::move(mPreallocatedProcesses.ElementAt(0));
    202    mPreallocatedProcesses.RemoveElementAt(0);
    203 
    204    // Don't set the priority to FOREGROUND here, since it may not have
    205    // finished starting
    206 
    207    // We took a preallocated process. Let's try to start up a new one
    208    // soon.
    209    ContentParent* last = mPreallocatedProcesses.SafeLastElement(nullptr).get();
    210    // There could be a launching process that isn't the last, but that's
    211    // ok (and unlikely)
    212    if (!last || !last->IsLaunching()) {
    213      AllocateAfterDelay();
    214    }
    215    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    216            ("Use prealloc process %p%s, %lu available", process.get(),
    217             process->IsLaunching() ? " (still launching)" : "",
    218             (unsigned long)mPreallocatedProcesses.Length()));
    219  }
    220  if (process && !process->IsLaunching()) {
    221    ProcessPriorityManager::SetProcessPriority(process.get(),
    222                                               PROCESS_PRIORITY_FOREGROUND);
    223  }  // else this will get set by the caller when they call InitInternal()
    224 
    225  return process;
    226 }
    227 
    228 void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) {
    229  (void)mPreallocatedProcesses.RemoveElement(aParent);
    230 }
    231 
    232 void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) {
    233  mNumberPreallocs = aProcesses;
    234  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    235          ("Enabling preallocation: %u", aProcesses));
    236  if (mEnabled || IsShutdown()) {
    237    return;
    238  }
    239 
    240  mEnabled = true;
    241  AllocateAfterDelay();
    242 }
    243 
    244 void PreallocatedProcessManagerImpl::AddBlocker(ContentParent* aParent) {
    245  if (sNumBlockers == 0) {
    246    mBlockingStartTime = TimeStamp::Now();
    247  }
    248  sNumBlockers++;
    249 }
    250 
    251 void PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent) {
    252  // This used to assert that the blocker existed, but preallocated
    253  // processes aren't blockers anymore because it's not useful and
    254  // interferes with async launch, and it's simpler if content
    255  // processes don't need to remember whether they were preallocated.
    256 
    257  MOZ_DIAGNOSTIC_ASSERT(sNumBlockers > 0);
    258  sNumBlockers--;
    259  if (sNumBlockers == 0) {
    260    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    261            ("Blocked preallocation for %fms",
    262             (TimeStamp::Now() - mBlockingStartTime).ToMilliseconds()));
    263    PROFILER_MARKER_TEXT("Process", DOM,
    264                         MarkerTiming::IntervalUntilNowFrom(mBlockingStartTime),
    265                         "Blocked preallocation");
    266    if (IsEmpty()) {
    267      AllocateAfterDelay();
    268    }
    269  }
    270 }
    271 
    272 bool PreallocatedProcessManagerImpl::CanAllocate() {
    273  return IsEnabled() && sNumBlockers == 0 &&
    274         mPreallocatedProcesses.Length() < mNumberPreallocs && !IsShutdown() &&
    275         (FissionAutostart() ||
    276          !ContentParent::IsMaxProcessCountReached(DEFAULT_REMOTE_TYPE));
    277 }
    278 
    279 void PreallocatedProcessManagerImpl::AllocateAfterDelay() {
    280  if (!IsEnabled()) {
    281    return;
    282  }
    283  long delay = StaticPrefs::dom_ipc_processPrelaunch_delayMs();
    284  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    285          ("Starting delayed process start, delay=%ld", delay));
    286  NS_DelayedDispatchToCurrentThread(
    287      NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this,
    288                        &PreallocatedProcessManagerImpl::AllocateOnIdle),
    289      delay);
    290 }
    291 
    292 void PreallocatedProcessManagerImpl::AllocateOnIdle() {
    293  if (!IsEnabled()) {
    294    return;
    295  }
    296 
    297  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    298          ("Starting process allocate on idle"));
    299  NS_DispatchToCurrentThreadQueue(
    300      NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this,
    301                        &PreallocatedProcessManagerImpl::AllocateNow),
    302      EventQueuePriority::Idle);
    303 }
    304 
    305 void PreallocatedProcessManagerImpl::AllocateNow() {
    306  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    307          ("Trying to start process now"));
    308  if (!CanAllocate()) {
    309    if (IsEnabled() && IsEmpty() && sNumBlockers > 0) {
    310      // If it's too early to allocate a process let's retry later.
    311      AllocateAfterDelay();
    312    }
    313    return;
    314  }
    315 
    316  UniqueContentParentKeepAlive process = ContentParent::MakePreallocProcess();
    317  if (!process) {
    318    // We fully failed to create a prealloc process. Don't try to kick off a new
    319    // preallocation here, to avoid possible looping.
    320    MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    321            ("Failed to launch prealloc process"));
    322    return;
    323  }
    324 
    325  process->WaitForLaunchAsync(PROCESS_PRIORITY_PREALLOC)
    326      ->Then(
    327          GetCurrentSerialEventTarget(), __func__,
    328          [self = RefPtr{this},
    329           process = RefPtr{process.get()}](UniqueContentParentKeepAlive&&) {
    330            if (process->IsDead()) {
    331              self->Erase(process);
    332              // Process died in startup (before we could add it).  If it
    333              // dies after this, MarkAsDead() will Erase() this entry.
    334              // Shouldn't be in the sBrowserContentParents, so we don't need
    335              // RemoveFromList().  We won't try to kick off a new
    336              // preallocation here, to avoid possible looping if something is
    337              // causing them to consistently fail; if everything is ok on the
    338              // next allocation request we'll kick off creation.
    339            } else if (self->CanAllocate()) {
    340              // Continue prestarting processes if needed
    341              if (self->mPreallocatedProcesses.Length() <
    342                  self->mNumberPreallocs) {
    343                self->AllocateOnIdle();
    344              }
    345            }
    346          },
    347          [self = RefPtr{this}, process = RefPtr{process.get()}]() {
    348            self->Erase(process);
    349          });
    350 
    351  mPreallocatedProcesses.AppendElement(std::move(process));
    352  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    353          ("Preallocated = %lu of %d processes",
    354           (unsigned long)mPreallocatedProcesses.Length(), mNumberPreallocs));
    355 }
    356 
    357 void PreallocatedProcessManagerImpl::Disable() {
    358  if (!mEnabled) {
    359    return;
    360  }
    361 
    362  mEnabled = false;
    363  CloseProcesses();
    364 }
    365 
    366 void PreallocatedProcessManagerImpl::CloseProcesses() {
    367  // Drop our KeepAlives on these processes. This will automatically lead to the
    368  // processes being shut down when no keepalives are left.
    369  mPreallocatedProcesses.Clear();
    370 }
    371 
    372 inline PreallocatedProcessManagerImpl*
    373 PreallocatedProcessManager::GetPPMImpl() {
    374  if (PreallocatedProcessManagerImpl::IsShutdown()) {
    375    return nullptr;
    376  }
    377  return PreallocatedProcessManagerImpl::Singleton();
    378 }
    379 
    380 /* static */
    381 bool PreallocatedProcessManager::Enabled() {
    382  if (auto impl = GetPPMImpl()) {
    383    return impl->IsEnabled();
    384  }
    385  return false;
    386 }
    387 
    388 /* static */
    389 void PreallocatedProcessManager::AddBlocker(const nsACString& aRemoteType,
    390                                            ContentParent* aParent) {
    391  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    392          ("AddBlocker: %s %p (sNumBlockers=%d)",
    393           PromiseFlatCString(aRemoteType).get(), aParent,
    394           PreallocatedProcessManagerImpl::sNumBlockers));
    395  if (auto impl = GetPPMImpl()) {
    396    impl->AddBlocker(aParent);
    397  }
    398 }
    399 
    400 /* static */
    401 void PreallocatedProcessManager::RemoveBlocker(const nsACString& aRemoteType,
    402                                               ContentParent* aParent) {
    403  MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
    404          ("RemoveBlocker: %s %p (sNumBlockers=%d)",
    405           PromiseFlatCString(aRemoteType).get(), aParent,
    406           PreallocatedProcessManagerImpl::sNumBlockers));
    407  if (auto impl = GetPPMImpl()) {
    408    impl->RemoveBlocker(aParent);
    409  }
    410 }
    411 
    412 /* static */
    413 UniqueContentParentKeepAlive PreallocatedProcessManager::Take(
    414    const nsACString& aRemoteType) {
    415  if (auto impl = GetPPMImpl()) {
    416    return impl->Take(aRemoteType);
    417  }
    418  return nullptr;
    419 }
    420 
    421 /* static */
    422 void PreallocatedProcessManager::Erase(ContentParent* aParent) {
    423  if (auto impl = GetPPMImpl()) {
    424    impl->Erase(aParent);
    425  }
    426 }
    427 
    428 }  // namespace mozilla