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"
24 using namespace mozilla::hal
;
25 using namespace mozilla::dom
;
29 * This singleton class implements the static methods on
30 * PreallocatedProcessManager.
32 class PreallocatedProcessManagerImpl final
: public nsIObserver
{
33 friend class PreallocatedProcessManager
;
36 static PreallocatedProcessManagerImpl
* Singleton();
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
);
48 static const char* const kObserverTopics
[];
50 static StaticRefPtr
<PreallocatedProcessManagerImpl
> sSingleton
;
52 PreallocatedProcessManagerImpl();
53 ~PreallocatedProcessManagerImpl();
54 PreallocatedProcessManagerImpl(const PreallocatedProcessManagerImpl
&) =
57 const PreallocatedProcessManagerImpl
& operator=(
58 const PreallocatedProcessManagerImpl
&) = delete;
63 void AllocateAfterDelay(bool aStartup
= false);
64 void AllocateOnIdle();
68 void Enable(uint32_t aProcesses
);
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(); }
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
;
88 uint32_t PreallocatedProcessManagerImpl::sNumBlockers
= 0;
90 const char* const PreallocatedProcessManagerImpl::kObserverTopics
[] = {
92 "profile-change-teardown",
93 NS_XPCOM_SHUTDOWN_OBSERVER_ID
,
97 StaticRefPtr
<PreallocatedProcessManagerImpl
>
98 PreallocatedProcessManagerImpl::sSingleton
;
101 PreallocatedProcessManagerImpl
* PreallocatedProcessManagerImpl::Singleton() {
102 MOZ_ASSERT(NS_IsMainThread());
104 sSingleton
= new PreallocatedProcessManagerImpl
;
106 ClearOnShutdown(&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
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();
135 for (auto topic
: kObserverTopics
) {
136 os
->AddObserver(this, topic
, /* ownsWeak */ false);
142 PreallocatedProcessManagerImpl::Observe(nsISupports
* aSubject
,
144 const char16_t
* aData
) {
145 if (!strcmp("nsPref:changed", aTopic
)) {
146 // The only other observer we registered was for our prefs.
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();
157 for (auto topic
: kObserverTopics
) {
158 os
->RemoveObserver(this, topic
);
160 } else if (!strcmp("memory-pressure", aTopic
)) {
163 MOZ_ASSERT_UNREACHABLE("Unknown topic");
169 void PreallocatedProcessManagerImpl::RereadPrefs() {
170 if (mozilla::BrowserTabsRemoteAutostart() &&
171 Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) {
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();
179 StaticPrefs::dom_ipc_processPrelaunch_lowmem_mb() * 1024 * 1024) {
185 // We have one prealloc queue for all types except File now
186 if (static_cast<uint64_t>(number
) < mPreallocatedProcesses
.Length()) {
195 already_AddRefed
<ContentParent
> PreallocatedProcessManagerImpl::Take(
196 const nsACString
& aRemoteType
) {
200 RefPtr
<ContentParent
> process
;
202 process
= mPreallocatedProcesses
.ElementAt(0);
203 mPreallocatedProcesses
.RemoveElementAt(0);
205 // Don't set the priority to FOREGROUND here, since it may not have
208 // We took a preallocated process. Let's try to start up a new one
210 ContentParent
* last
= mPreallocatedProcesses
.SafeLastElement(nullptr);
211 // There could be a launching process that isn't the last, but that's
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()) {
242 AllocateAfterDelay(/* aStartup */ true);
245 void PreallocatedProcessManagerImpl::AddBlocker(ContentParent
* aParent
) {
246 if (sNumBlockers
== 0) {
247 mBlockingStartTime
= TimeStamp::Now();
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);
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");
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
) {
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
),
294 void PreallocatedProcessManagerImpl::AllocateOnIdle() {
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();
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
)
327 GetCurrentSerialEventTarget(), __func__
,
328 [self
, this, process
](const RefPtr
<ContentParent
>&) {
329 if (process
->IsDead()) {
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.
339 // Continue prestarting processes if needed
341 if (mPreallocatedProcesses
.Length() < mNumberPreallocs
) {
344 } else if (!IsEnabled()) {
345 // if this has a remote type set, it's been allocated for use
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() {
367 void PreallocatedProcessManagerImpl::CloseProcesses() {
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
378 if (RefPtr
<ContentParent
> recycled
=
379 ContentParent::sRecycledE10SProcess
.forget()) {
380 recycled
->MaybeBeginShutDown();
384 inline PreallocatedProcessManagerImpl
*
385 PreallocatedProcessManager::GetPPMImpl() {
386 if (PreallocatedProcessManagerImpl::IsShutdown()) {
389 return PreallocatedProcessManagerImpl::Singleton();
393 bool PreallocatedProcessManager::Enabled() {
394 if (auto impl
= GetPPMImpl()) {
395 return impl
->IsEnabled();
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
);
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
);
425 already_AddRefed
<ContentParent
> PreallocatedProcessManager::Take(
426 const nsACString
& aRemoteType
) {
427 if (auto impl
= GetPPMImpl()) {
428 return impl
->Take(aRemoteType
);
434 void PreallocatedProcessManager::Erase(ContentParent
* aParent
) {
435 if (auto impl
= GetPPMImpl()) {
436 impl
->Erase(aParent
);
440 } // namespace mozilla