From b83ba235822ef7e5a0299eb1a95d306defa6360a Mon Sep 17 00:00:00 2001 From: Blake Kaplan Date: Tue, 1 Nov 2016 16:02:43 -0700 Subject: [PATCH] Bug 1334716 - Make process selection a service and implementable in JS. r=krizsa MozReview-Commit-ID: CViRvZB8nKe --HG-- extra : rebase_source : 3e2c4d785ebe9af1b74ad4407d9fc9e2cf371bb4 --- browser/installer/package-manifest.in | 3 + dom/base/ProcessSelector.js | 62 +++++++++ dom/base/ProcessSelector.manifest | 2 + dom/base/moz.build | 2 + dom/interfaces/base/moz.build | 1 + dom/interfaces/base/nsIContentProcess.idl | 53 ++++++++ dom/ipc/ContentParent.cpp | 153 +++++++++++++++++++--- dom/ipc/ContentParent.h | 6 + 8 files changed, 265 insertions(+), 17 deletions(-) create mode 100644 dom/base/ProcessSelector.js create mode 100644 dom/base/ProcessSelector.manifest create mode 100644 dom/interfaces/base/nsIContentProcess.idl diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 71f558c777e1..948a74da466e 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -520,6 +520,9 @@ @RESPATH@/components/remotebrowserutils.manifest @RESPATH@/components/RemoteWebNavigation.js +@RESPATH@/components/ProcessSelector.js +@RESPATH@/components/ProcessSelector.manifest + @RESPATH@/components/SlowScriptDebug.manifest @RESPATH@/components/SlowScriptDebug.js diff --git a/dom/base/ProcessSelector.js b/dom/base/ProcessSelector.js new file mode 100644 index 000000000000..c1fabb336a66 --- /dev/null +++ b/dom/base/ProcessSelector.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import('resource://gre/modules/Services.jsm'); + +const BASE_PREF = "dom.ipc.processCount" +const PREF_BRANCH = BASE_PREF + "."; + +// Utilities: +function getMaxContentParents(processType) { + let maxContentParents = -1; + try { + maxContentParents = Services.prefs.getIntPref(PREF_BRANCH + aType); + } catch (e) { + // Pref probably didn't exist, get the default number of processes. + try { + maxContentParents = Services.prefs.getIntPref(BASE_PREF); + } catch (e) { + // No prefs? That's odd, use only one process. + maxContentParents = 1; + } + } + + return maxContentParents; +} + +// Fills up aProcesses until max and then selects randomly from the available +// ones. +function RandomSelector() { +} + +RandomSelector.prototype = { + classID: Components.ID("{c616fcfd-9737-41f1-aa74-cee72a38f91b}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentProcessProvider]), + + provideProcess(aType, aOpener, aProcesses, aCount) { + let maxContentParents = getMaxContentParents(); + if (aCount < maxContentParents) { + return Ci.nsIContentProcessProvider.NEW_PROCESS; + } + + let startIdx = Math.floor(Math.random() * maxContentParents); + let curIdx = startIdx; + + do { + if (aProcesses[curIdx].opener === aOpener) { + return curIdx; + } + + curIdx = (curIdx + 1) % maxContentParents; + } while (curIdx !== startIdx); + + return Ci.nsIContentProcessProvider.NEW_PROCESS; + }, +}; + +var components = [RandomSelector]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/dom/base/ProcessSelector.manifest b/dom/base/ProcessSelector.manifest new file mode 100644 index 000000000000..8b46c5ab16aa --- /dev/null +++ b/dom/base/ProcessSelector.manifest @@ -0,0 +1,2 @@ +component {c616fcfd-9737-41f1-aa74-cee72a38f91b} ProcessSelector.js +contract @mozilla.org/ipc/processselector;1 {c616fcfd-9737-41f1-aa74-cee72a38f91b} diff --git a/dom/base/moz.build b/dom/base/moz.build index 8e19020315ae..1df9015b61a1 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -391,6 +391,8 @@ EXTRA_COMPONENTS += [ 'contentAreaDropListener.manifest', 'messageWakeupService.js', 'messageWakeupService.manifest', + 'ProcessSelector.js', + 'ProcessSelector.manifest', 'SlowScriptDebug.js', 'SlowScriptDebug.manifest', ] diff --git a/dom/interfaces/base/moz.build b/dom/interfaces/base/moz.build index e96fc0158ee0..9d7f7acddcb7 100644 --- a/dom/interfaces/base/moz.build +++ b/dom/interfaces/base/moz.build @@ -11,6 +11,7 @@ XPIDL_SOURCES += [ 'nsIContentPermissionPrompt.idl', 'nsIContentPrefService.idl', 'nsIContentPrefService2.idl', + 'nsIContentProcess.idl', 'nsIContentURIGrouper.idl', 'nsIDOMChromeWindow.idl', 'nsIDOMClientRect.idl', diff --git a/dom/interfaces/base/nsIContentProcess.idl b/dom/interfaces/base/nsIContentProcess.idl new file mode 100644 index 000000000000..2aa21fa5a804 --- /dev/null +++ b/dom/interfaces/base/nsIContentProcess.idl @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMElement; +interface nsIMessageSender; +interface nsIURI; + +[scriptable, builtinclass, uuid(456f58be-29dd-4973-885b-95aece1c9a8a)] +interface nsIContentProcessInfo : nsISupports +{ + /** + * Is this content process alive? + */ + readonly attribute boolean isAlive; + + /** + * The content process's PID. + * Throws if the process is not alive. + */ + readonly attribute int32_t processId; + + /** + * This content process's opener. + */ + readonly attribute nsIContentProcessInfo opener; + + /** + * The process manager for this ContentParent (so a process message manager + * as opposed to a frame message manager. + */ + readonly attribute nsIMessageSender messageManager; +}; + +[scriptable, uuid(83ffb063-5f65-4c45-ae07-3f553e0809bb)] +interface nsIContentProcessProvider : nsISupports +{ + /** + * Return this from provideProcess to create a new process. + */ + const int32_t NEW_PROCESS = -1; + + /** + * Given aAliveProcesses (with an opener aOpener), choose which process of + * aType to use. Return nsIContentProcessProvider.NEW_PROCESS to ask the + * caller to create a new content process. + */ + int32_t provideProcess(in AString aType, in nsIContentProcessInfo aOpener, + [array, size_is(aCount)] in nsIContentProcessInfo aAliveProcesses, + in uint32_t aCount); +}; diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index fc293095918b..76ce3b5aed89 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -112,6 +112,7 @@ #include "nsIAlertsService.h" #include "nsIClipboard.h" #include "nsContentPermissionHelper.h" +#include "nsIContentProcess.h" #include "nsICycleCollectorListener.h" #include "nsIDocShellTreeOwner.h" #include "nsIDocument.h" @@ -435,6 +436,90 @@ ContentParentsMemoryReporter::CollectReports( } nsClassHashtable>* ContentParent::sBrowserContentParents; + +namespace { + +class ScriptableCPInfo final : public nsIContentProcessInfo +{ +public: + explicit ScriptableCPInfo(ContentParent* aParent) + : mContentParent(aParent) + { + MOZ_ASSERT(mContentParent); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPROCESSINFO + + void ProcessDied() + { + mContentParent = nullptr; + } + +private: + ~ScriptableCPInfo() + { + MOZ_ASSERT(!mContentParent, "must call ProcessDied"); + } + + ContentParent* mContentParent; +}; + +NS_IMPL_ISUPPORTS(ScriptableCPInfo, nsIContentProcessInfo) + +NS_IMETHODIMP +ScriptableCPInfo::GetIsAlive(bool* aIsAlive) +{ + *aIsAlive = mContentParent != nullptr; + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetProcessId(int32_t* aPID) +{ + if (!mContentParent) { + *aPID = -1; + return NS_ERROR_NOT_INITIALIZED; + } + + *aPID = mContentParent->Pid(); + if (*aPID == -1) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetOpener(nsIContentProcessInfo** aInfo) +{ + *aInfo = nullptr; + if (!mContentParent) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (ContentParent* opener = mContentParent->Opener()) { + nsCOMPtr info = opener->ScriptableHelper(); + info.forget(aInfo); + } + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetMessageManager(nsIMessageSender** aMessenger) +{ + *aMessenger = nullptr; + if (!mContentParent) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr manager = mContentParent->GetMessageManager(); + manager.forget(aMessenger); + return NS_OK; +} + +} // anonymous namespace + nsTArray* ContentParent::sPrivateContent; StaticAutoPtr > ContentParent::sContentParents; #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX) @@ -697,38 +782,65 @@ ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType, ContentParent* aOpener) { nsTArray& contentParents = GetOrCreatePool(aRemoteType); - uint32_t maxContentParents = GetMaxProcessCount(aRemoteType); - RefPtr p; - if (contentParents.Length() >= maxContentParents) { + if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) { // We never want to re-use Large-Allocation processes. - if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) { + if (contentParents.Length() >= maxContentParents) { return GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), aPriority, aOpener); } + } else { + nsTArray infos(contentParents.Length()); + for (auto* cp : contentParents) { + infos.AppendElement(cp->mScriptableHelper); + } - if ((p = RandomSelect(contentParents, aOpener, maxContentParents))) { + nsCOMPtr cpp = + do_GetService("@mozilla.org/ipc/processselector;1"); + nsIContentProcessInfo* openerInfo = aOpener ? aOpener->mScriptableHelper.get() : nullptr; + int32_t index; + if (cpp && + NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, openerInfo, + infos.Elements(), infos.Length(), + &index))) { + // If the provider returned an existing ContentParent, use that one. + if (0 <= index && static_cast(index) <= maxContentParents) { + RefPtr retval = contentParents[index]; + return retval.forget(); + } + } else { + // If there was a problem with the JS chooser, fall back to a random + // selection. + NS_WARNING("nsIContentProcessProvider failed to return a process"); + RefPtr random; + if (contentParents.Length() >= maxContentParents && + (random = RandomSelect(contentParents, aOpener, maxContentParents))) { + return random.forget(); + } + } + + // Try to take the preallocated process only for the default process type. + RefPtr p; + if (aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) && + (p = PreallocatedProcessManager::Take())) { + // For pre-allocated process we have not set the opener yet. + p->mOpener = aOpener; + contentParents.AppendElement(p); return p.forget(); } } - // Try to take the preallocated process only for the default process type. - if (aRemoteType.Equals(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)) && - (p = PreallocatedProcessManager::Take())) { - // For pre-allocated process we have not set the opener yet. - p->mOpener = aOpener; - } else { - p = new ContentParent(aOpener, aRemoteType); + // Create a new process from scratch. + RefPtr p = new ContentParent(aOpener, aRemoteType); - if (!p->LaunchSubprocess(aPriority)) { - return nullptr; - } - - p->Init(); + if (!p->LaunchSubprocess(aPriority)) { + return nullptr; } + p->Init(); + contentParents.AppendElement(p); return p.forget(); } @@ -1204,6 +1316,8 @@ ContentParent::Init() RefPtr gmps(GeckoMediaPluginServiceParent::GetSingleton()); gmps->UpdateContentProcessGMPCapabilities(); + + mScriptableHelper = new ScriptableCPInfo(this); } namespace { @@ -1274,6 +1388,11 @@ ContentParent::SetPriorityAndCheckIsAlive(ProcessPriority aPriority) void ContentParent::ShutDownProcess(ShutDownMethod aMethod) { + if (mScriptableHelper) { + static_cast(mScriptableHelper.get())->ProcessDied(); + mScriptableHelper = nullptr; + } + // Shutting down by sending a shutdown message works differently than the // other methods. We first call Shutdown() in the child. After the child is // ready, it calls FinishShutdown() on us. Then we close the channel. diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index e5f023b31b08..115937034000 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -45,6 +45,7 @@ #define LARGE_ALLOCATION_REMOTE_TYPE "webLargeAllocation" class nsConsoleService; +class nsIContentProcessInfo; class nsICycleCollectorLogSink; class nsIDumpGCAndCCLogsCallback; class nsITabParent; @@ -371,6 +372,10 @@ public: { return mOpener; } + nsIContentProcessInfo* ScriptableHelper() const + { + return mScriptableHelper; + } bool NeedsPermissionsUpdate() const { @@ -1173,6 +1178,7 @@ private: RefPtr mConsoleService; nsConsoleService* GetConsoleService(); + nsCOMPtr mScriptableHelper; nsTArray> mIdleListeners;