Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / ipc / PreallocatedProcessManager.cpp
blobb96521492ffd946bbc7bb13237d687b63fc556cf
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/. */
7 #include "mozilla/PreallocatedProcessManager.h"
9 #include "mozilla/AppShutdown.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/Preferences.h"
12 #include "mozilla/ProfilerMarkers.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/dom/ContentParent.h"
15 #include "mozilla/dom/ScriptSettings.h"
16 #include "mozilla/StaticPrefs_dom.h"
17 #include "nsIPropertyBag2.h"
18 #include "ProcessPriorityManager.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsIXULRuntime.h"
21 #include "nsTArray.h"
22 #include "prsystem.h"
24 using namespace mozilla::hal;
25 using namespace mozilla::dom;
27 namespace mozilla {
28 /**
29 * This singleton class implements the static methods on
30 * PreallocatedProcessManager.
32 class PreallocatedProcessManagerImpl final : public nsIObserver {
33 friend class PreallocatedProcessManager;
35 public:
36 static PreallocatedProcessManagerImpl* Singleton();
38 NS_DECL_ISUPPORTS
39 NS_DECL_NSIOBSERVER
41 // See comments on PreallocatedProcessManager for these methods.
42 void AddBlocker(ContentParent* aParent);
43 void RemoveBlocker(ContentParent* aParent);
44 already_AddRefed<ContentParent> Take(const nsACString& aRemoteType);
45 void Erase(ContentParent* aParent);
47 private:
48 static const char* const kObserverTopics[];
50 static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
52 PreallocatedProcessManagerImpl();
53 ~PreallocatedProcessManagerImpl();
54 PreallocatedProcessManagerImpl(const PreallocatedProcessManagerImpl&) =
55 delete;
57 const PreallocatedProcessManagerImpl& operator=(
58 const PreallocatedProcessManagerImpl&) = delete;
60 void Init();
62 bool CanAllocate();
63 void AllocateAfterDelay(bool aStartup = false);
64 void AllocateOnIdle();
65 void AllocateNow();
67 void RereadPrefs();
68 void Enable(uint32_t aProcesses);
69 void Disable();
70 void CloseProcesses();
72 bool IsEmpty() const { return mPreallocatedProcesses.IsEmpty(); }
73 static bool IsShutdown() {
74 return AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
76 bool IsEnabled() { return mEnabled && !IsShutdown(); }
78 bool mEnabled;
79 uint32_t mNumberPreallocs;
80 AutoTArray<RefPtr<ContentParent>, 3> mPreallocatedProcesses;
81 // Even if we have multiple PreallocatedProcessManagerImpls, we'll have
82 // one blocker counter
83 static uint32_t sNumBlockers;
84 TimeStamp mBlockingStartTime;
87 /* static */
88 uint32_t PreallocatedProcessManagerImpl::sNumBlockers = 0;
90 const char* const PreallocatedProcessManagerImpl::kObserverTopics[] = {
91 "memory-pressure",
92 "profile-change-teardown",
93 NS_XPCOM_SHUTDOWN_OBSERVER_ID,
96 /* static */
97 StaticRefPtr<PreallocatedProcessManagerImpl>
98 PreallocatedProcessManagerImpl::sSingleton;
100 /* static */
101 PreallocatedProcessManagerImpl* PreallocatedProcessManagerImpl::Singleton() {
102 MOZ_ASSERT(NS_IsMainThread());
103 if (!sSingleton) {
104 sSingleton = new PreallocatedProcessManagerImpl;
105 sSingleton->Init();
106 ClearOnShutdown(&sSingleton);
108 return sSingleton;
109 // PreallocatedProcessManagers live until shutdown
112 NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
114 PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
115 : mEnabled(false), mNumberPreallocs(1) {}
117 PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() {
118 // Note: mPreallocatedProcesses may not be null, but all processes should
119 // be dead (IsDead==true). We block Erase() when our observer sees
120 // shutdown starting.
123 void PreallocatedProcessManagerImpl::Init() {
124 Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled");
125 // We have to respect processCount at all time. This is especially important
126 // for testing.
127 Preferences::AddStrongObserver(this, "dom.ipc.processCount");
128 // A StaticPref, but we need to adjust the number of preallocated processes
129 // if the value goes up or down, so we need to run code on change.
130 Preferences::AddStrongObserver(this,
131 "dom.ipc.processPrelaunch.fission.number");
133 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
134 MOZ_ASSERT(os);
135 for (auto topic : kObserverTopics) {
136 os->AddObserver(this, topic, /* ownsWeak */ false);
138 RereadPrefs();
141 NS_IMETHODIMP
142 PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject,
143 const char* aTopic,
144 const char16_t* aData) {
145 if (!strcmp("nsPref:changed", aTopic)) {
146 // The only other observer we registered was for our prefs.
147 RereadPrefs();
148 } else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) ||
149 !strcmp("profile-change-teardown", aTopic)) {
150 Preferences::RemoveObserver(this, "dom.ipc.processPrelaunch.enabled");
151 Preferences::RemoveObserver(this, "dom.ipc.processCount");
152 Preferences::RemoveObserver(this,
153 "dom.ipc.processPrelaunch.fission.number");
155 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
156 MOZ_ASSERT(os);
157 for (auto topic : kObserverTopics) {
158 os->RemoveObserver(this, topic);
160 } else if (!strcmp("memory-pressure", aTopic)) {
161 CloseProcesses();
162 } else {
163 MOZ_ASSERT_UNREACHABLE("Unknown topic");
166 return NS_OK;
169 void PreallocatedProcessManagerImpl::RereadPrefs() {
170 if (mozilla::BrowserTabsRemoteAutostart() &&
171 Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) {
172 int32_t number = 1;
173 if (mozilla::FissionAutostart()) {
174 number = StaticPrefs::dom_ipc_processPrelaunch_fission_number();
175 // limit preallocated processes on low-mem machines
176 PRUint64 bytes = PR_GetPhysicalMemorySize();
177 if (bytes > 0 &&
178 bytes <=
179 StaticPrefs::dom_ipc_processPrelaunch_lowmem_mb() * 1024 * 1024) {
180 number = 1;
183 if (number >= 0) {
184 Enable(number);
185 // We have one prealloc queue for all types except File now
186 if (static_cast<uint64_t>(number) < mPreallocatedProcesses.Length()) {
187 CloseProcesses();
190 } else {
191 Disable();
195 already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take(
196 const nsACString& aRemoteType) {
197 if (!IsEnabled()) {
198 return nullptr;
200 RefPtr<ContentParent> process;
201 if (!IsEmpty()) {
202 process = mPreallocatedProcesses.ElementAt(0);
203 mPreallocatedProcesses.RemoveElementAt(0);
205 // Don't set the priority to FOREGROUND here, since it may not have
206 // finished starting
208 // We took a preallocated process. Let's try to start up a new one
209 // soon.
210 ContentParent* last = mPreallocatedProcesses.SafeLastElement(nullptr);
211 // There could be a launching process that isn't the last, but that's
212 // ok (and unlikely)
213 if (!last || !last->IsLaunching()) {
214 AllocateAfterDelay();
216 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
217 ("Use prealloc process %p%s, %lu available", process.get(),
218 process->IsLaunching() ? " (still launching)" : "",
219 (unsigned long)mPreallocatedProcesses.Length()));
221 if (process && !process->IsLaunching()) {
222 ProcessPriorityManager::SetProcessPriority(process,
223 PROCESS_PRIORITY_FOREGROUND);
224 } // else this will get set by the caller when they call InitInternal()
226 return process.forget();
229 void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) {
230 (void)mPreallocatedProcesses.RemoveElement(aParent);
233 void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) {
234 mNumberPreallocs = aProcesses;
235 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
236 ("Enabling preallocation: %u", aProcesses));
237 if (mEnabled || IsShutdown()) {
238 return;
241 mEnabled = true;
242 AllocateAfterDelay(/* aStartup */ true);
245 void PreallocatedProcessManagerImpl::AddBlocker(ContentParent* aParent) {
246 if (sNumBlockers == 0) {
247 mBlockingStartTime = TimeStamp::Now();
249 sNumBlockers++;
252 void PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent) {
253 // This used to assert that the blocker existed, but preallocated
254 // processes aren't blockers anymore because it's not useful and
255 // interferes with async launch, and it's simpler if content
256 // processes don't need to remember whether they were preallocated.
258 MOZ_DIAGNOSTIC_ASSERT(sNumBlockers > 0);
259 sNumBlockers--;
260 if (sNumBlockers == 0) {
261 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
262 ("Blocked preallocation for %fms",
263 (TimeStamp::Now() - mBlockingStartTime).ToMilliseconds()));
264 PROFILER_MARKER_TEXT("Process", DOM,
265 MarkerTiming::IntervalUntilNowFrom(mBlockingStartTime),
266 "Blocked preallocation");
267 if (IsEmpty()) {
268 AllocateAfterDelay();
273 bool PreallocatedProcessManagerImpl::CanAllocate() {
274 return IsEnabled() && sNumBlockers == 0 &&
275 mPreallocatedProcesses.Length() < mNumberPreallocs && !IsShutdown() &&
276 (FissionAutostart() ||
277 !ContentParent::IsMaxProcessCountReached(DEFAULT_REMOTE_TYPE));
280 void PreallocatedProcessManagerImpl::AllocateAfterDelay(bool aStartup) {
281 if (!IsEnabled()) {
282 return;
284 long delay = aStartup ? StaticPrefs::dom_ipc_processPrelaunch_startupDelayMs()
285 : StaticPrefs::dom_ipc_processPrelaunch_delayMs();
286 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
287 ("Starting delayed process start, delay=%ld", delay));
288 NS_DelayedDispatchToCurrentThread(
289 NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this,
290 &PreallocatedProcessManagerImpl::AllocateOnIdle),
291 delay);
294 void PreallocatedProcessManagerImpl::AllocateOnIdle() {
295 if (!IsEnabled()) {
296 return;
299 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
300 ("Starting process allocate on idle"));
301 NS_DispatchToCurrentThreadQueue(
302 NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this,
303 &PreallocatedProcessManagerImpl::AllocateNow),
304 EventQueuePriority::Idle);
307 void PreallocatedProcessManagerImpl::AllocateNow() {
308 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
309 ("Trying to start process now"));
310 if (!CanAllocate()) {
311 if (IsEnabled() && IsEmpty() && sNumBlockers > 0) {
312 // If it's too early to allocate a process let's retry later.
313 AllocateAfterDelay();
315 return;
318 RefPtr<ContentParent> process = ContentParent::MakePreallocProcess();
319 mPreallocatedProcesses.AppendElement(process);
320 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
321 ("Preallocated = %lu of %d processes",
322 (unsigned long)mPreallocatedProcesses.Length(), mNumberPreallocs));
324 RefPtr<PreallocatedProcessManagerImpl> self(this);
325 process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC)
326 ->Then(
327 GetCurrentSerialEventTarget(), __func__,
328 [self, this, process](const RefPtr<ContentParent>&) {
329 if (process->IsDead()) {
330 Erase(process);
331 // Process died in startup (before we could add it). If it
332 // dies after this, MarkAsDead() will Erase() this entry.
333 // Shouldn't be in the sBrowserContentParents, so we don't need
334 // RemoveFromList(). We won't try to kick off a new
335 // preallocation here, to avoid possible looping if something is
336 // causing them to consistently fail; if everything is ok on the
337 // next allocation request we'll kick off creation.
338 } else {
339 // Continue prestarting processes if needed
340 if (CanAllocate()) {
341 if (mPreallocatedProcesses.Length() < mNumberPreallocs) {
342 AllocateOnIdle();
344 } else if (!IsEnabled()) {
345 // if this has a remote type set, it's been allocated for use
346 // already
347 if (process->mRemoteType == PREALLOC_REMOTE_TYPE) {
348 // This will Erase() it
349 process->ShutDownProcess(
350 ContentParent::SEND_SHUTDOWN_MESSAGE);
355 [self, this, process]() { Erase(process); });
358 void PreallocatedProcessManagerImpl::Disable() {
359 if (!mEnabled) {
360 return;
363 mEnabled = false;
364 CloseProcesses();
367 void PreallocatedProcessManagerImpl::CloseProcesses() {
368 while (!IsEmpty()) {
369 RefPtr<ContentParent> process(mPreallocatedProcesses.ElementAt(0));
370 mPreallocatedProcesses.RemoveElementAt(0);
371 process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
372 // drop ref and let it free
375 // Make sure to also clear out the recycled E10S process cache, as it's also
376 // controlled by the same preference, and can be cleaned up due to memory
377 // pressure.
378 if (RefPtr<ContentParent> recycled =
379 ContentParent::sRecycledE10SProcess.forget()) {
380 recycled->MaybeBeginShutDown();
384 inline PreallocatedProcessManagerImpl*
385 PreallocatedProcessManager::GetPPMImpl() {
386 if (PreallocatedProcessManagerImpl::IsShutdown()) {
387 return nullptr;
389 return PreallocatedProcessManagerImpl::Singleton();
392 /* static */
393 bool PreallocatedProcessManager::Enabled() {
394 if (auto impl = GetPPMImpl()) {
395 return impl->IsEnabled();
397 return false;
400 /* static */
401 void PreallocatedProcessManager::AddBlocker(const nsACString& aRemoteType,
402 ContentParent* aParent) {
403 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
404 ("AddBlocker: %s %p (sNumBlockers=%d)",
405 PromiseFlatCString(aRemoteType).get(), aParent,
406 PreallocatedProcessManagerImpl::sNumBlockers));
407 if (auto impl = GetPPMImpl()) {
408 impl->AddBlocker(aParent);
412 /* static */
413 void PreallocatedProcessManager::RemoveBlocker(const nsACString& aRemoteType,
414 ContentParent* aParent) {
415 MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug,
416 ("RemoveBlocker: %s %p (sNumBlockers=%d)",
417 PromiseFlatCString(aRemoteType).get(), aParent,
418 PreallocatedProcessManagerImpl::sNumBlockers));
419 if (auto impl = GetPPMImpl()) {
420 impl->RemoveBlocker(aParent);
424 /* static */
425 already_AddRefed<ContentParent> PreallocatedProcessManager::Take(
426 const nsACString& aRemoteType) {
427 if (auto impl = GetPPMImpl()) {
428 return impl->Take(aRemoteType);
430 return nullptr;
433 /* static */
434 void PreallocatedProcessManager::Erase(ContentParent* aParent) {
435 if (auto impl = GetPPMImpl()) {
436 impl->Erase(aParent);
440 } // namespace mozilla