diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js index 2b737e9a2c1..858c0d9319e 100644 --- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -1757,4 +1757,13 @@ BrowserElementChild.prototype = { } }; -var api = new BrowserElementChild(); +var api = null; +if ('DoPreloadPostfork' in this && typeof this.DoPreloadPostfork === 'function') { + // If we are preloaded, instantiate BrowserElementChild after a content + // process is forked. + this.DoPreloadPostfork(function() { + api = new BrowserElementChild(); + }); +} else { + api = new BrowserElementChild(); +} diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 53c1327861f..4ff19830f7b 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2291,11 +2291,7 @@ ContentChild::RecvAppInit() // PreloadSlowThings() may set the docshell of the first TabChild // inactive, and we can only safely restore it to active from // BrowserElementChild.js. - if ((mIsForApp || mIsForBrowser) -#ifdef MOZ_NUWA_PROCESS - && !IsNuwaProcess() -#endif - ) { + if (mIsForApp || mIsForBrowser) { PreloadSlowThings(); } diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 22ca5ed560c..222d737484b 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -23,6 +23,9 @@ #include "mozilla/plugins/PluginWidgetChild.h" #include "mozilla/IMEStateManager.h" #include "mozilla/ipc/DocumentRendererChild.h" +#ifdef MOZ_NUWA_PROCESS +#include "ipc/Nuwa.h" +#endif #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/layers/APZCCallbackHelper.h" #include "mozilla/layers/APZCTreeManager.h" @@ -450,10 +453,67 @@ TabChild::FindTabChild(const TabId& aTabId) return tabChild.forget(); } +static void +PreloadSlowThingsPostFork(void* aUnused) +{ + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(nullptr, "preload-postfork", nullptr); +} + +#ifdef MOZ_NUWA_PROCESS +class MessageChannelAutoBlock MOZ_STACK_CLASS +{ +public: + MessageChannelAutoBlock() + { + SetMessageChannelBlocked(true); + } + + ~MessageChannelAutoBlock() + { + SetMessageChannelBlocked(false); + } + +private: + void SetMessageChannelBlocked(bool aBlock) + { + if (!IsNuwaProcess()) { + return; + } + + mozilla::dom::ContentChild* content = + mozilla::dom::ContentChild::GetSingleton(); + if (aBlock) { + content->GetIPCChannel()->Block(); + } else { + content->GetIPCChannel()->Unblock(); + } + + nsTArray actors; + content->GetOpenedActors(actors); + for (size_t j = 0; j < actors.Length(); j++) { + IToplevelProtocol* actor = actors[j]; + if (aBlock) { + actor->GetIPCChannel()->Block(); + } else { + actor->GetIPCChannel()->Unblock(); + } + } + } +}; +#endif + +static bool sPreloaded = false; + /*static*/ void TabChild::PreloadSlowThings() { - MOZ_ASSERT(!sPreallocatedTab); + if (sPreloaded) { + // If we are alredy initialized in Nuwa, don't redo preloading. + return; + } + sPreloaded = true; // Pass nullptr to aManager since at this point the TabChild is // not connected to any manager. Any attempt to use the TabChild @@ -465,6 +525,13 @@ TabChild::PreloadSlowThings() !tab->InitTabChildGlobal(DONT_LOAD_SCRIPTS)) { return; } + +#ifdef MOZ_NUWA_PROCESS + // Temporarily block the IPC channels to the chrome process when we are + // preloading. + MessageChannelAutoBlock autoblock; +#endif + // Just load and compile these scripts, but don't run them. tab->TryCacheLoadAndCompileScript(BROWSER_ELEMENT_CHILD_SCRIPT, true); // Load, compile, and run these scripts. @@ -472,6 +539,16 @@ TabChild::PreloadSlowThings() NS_LITERAL_STRING("chrome://global/content/preload.js"), true); +#ifdef MOZ_NUWA_PROCESS + if (IsNuwaProcess()) { + NuwaAddFinalConstructor(PreloadSlowThingsPostFork, nullptr); + } else { + PreloadSlowThingsPostFork(nullptr); + } +#else + PreloadSlowThingsPostFork(nullptr); +#endif + nsCOMPtr docShell = do_GetInterface(tab->WebNavigation()); if (nsIPresShell* presShell = docShell->GetPresShell()) { // Initialize and do an initial reflow of the about:blank diff --git a/dom/ipc/preload.js b/dom/ipc/preload.js index ed09d174808..44b3d8f6d17 100644 --- a/dom/ipc/preload.js +++ b/dom/ipc/preload.js @@ -9,6 +9,17 @@ const BrowserElementIsPreloaded = true; +const DoPreloadPostfork = function(aCallback) { + Services.obs.addObserver({ + _callback: aCallback, + + observe: function() { + this._callback(); + Services.obs.removeObserver(this, "preload-postfork"); + } + }, "preload-postfork", false); +}; + (function (global) { "use strict"; @@ -16,7 +27,6 @@ const BrowserElementIsPreloaded = true; let Cc = Components.classes; let Ci = Components.interfaces; - Cu.import("resource://gre/modules/AppsServiceChild.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/BrowserElementPromptService.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); @@ -30,12 +40,10 @@ const BrowserElementIsPreloaded = true; Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci["nsIAppShellService"]); Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci["nsIWindowMediator"]); - Cc["@mozilla.org/AppsService;1"].getService(Ci["nsIAppsService"]); Cc["@mozilla.org/base/telemetry;1"].getService(Ci["nsITelemetry"]); Cc["@mozilla.org/categorymanager;1"].getService(Ci["nsICategoryManager"]); Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci["nsIMessageSender"]); Cc["@mozilla.org/consoleservice;1"].getService(Ci["nsIConsoleService"]); - Cc["@mozilla.org/cookieService;1"].getService(Ci["nsICookieService"]); Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci["nsIURIFixup"]); Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci["nsIDOMRequestService"]); Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci["nsIPromptService"]); @@ -53,14 +61,12 @@ const BrowserElementIsPreloaded = true; Cc["@mozilla.org/network/idn-service;1"].getService(Ci["nsIIDNService"]); Cc["@mozilla.org/network/io-service;1"].getService(Ci["nsIIOService2"]); Cc["@mozilla.org/network/mime-hdrparam;1"].getService(Ci["nsIMIMEHeaderParam"]); - Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(Ci["nsIProtocolProxyService"]); Cc["@mozilla.org/network/socket-transport-service;1"].getService(Ci["nsISocketTransportService"]); Cc["@mozilla.org/network/stream-transport-service;1"].getService(Ci["nsIStreamTransportService"]); Cc["@mozilla.org/network/url-parser;1?auth=maybe"].getService(Ci["nsIURLParser"]); Cc["@mozilla.org/network/url-parser;1?auth=no"].getService(Ci["nsIURLParser"]); Cc["@mozilla.org/network/url-parser;1?auth=yes"].getService(Ci["nsIURLParser"]); Cc["@mozilla.org/observer-service;1"].getService(Ci["nsIObserverService"]); - Cc["@mozilla.org/permissionmanager;1"].getService(Ci["nsIPermissionManager"]); Cc["@mozilla.org/preferences-service;1"].getService(Ci["nsIPrefBranch"]); Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci["nsIScriptSecurityManager"]); Cc["@mozilla.org/storage/service;1"].getService(Ci["mozIStorageService"]); @@ -70,7 +76,6 @@ const BrowserElementIsPreloaded = true; Cc["@mozilla.org/uriloader;1"].getService(Ci["nsIURILoader"]); Cc["@mozilla.org/cspcontext;1"].createInstance(Ci["nsIContentSecurityPolicy"]); Cc["@mozilla.org/settingsManager;1"].createInstance(Ci["nsISupports"]); - Cc["@mozilla.org/webapps;1"].createInstance(Ci["nsISupports"]); /* Applications Specific Helper */ try { @@ -104,7 +109,44 @@ const BrowserElementIsPreloaded = true; Services.io.getProtocolHandler("app"); Services.io.getProtocolHandler("default"); - docShell.isActive = false; - docShell.createAboutBlankContentViewer(null); + // Register an observer for topic "preload_postfork" after we fork a content + // process. + DoPreloadPostfork(function () { + // Load AppsServiceChild.jsm after fork since it sends an async message to + // the chrome process in its init() function. + Cu.import("resource://gre/modules/AppsServiceChild.jsm"); + // Load UserCustomizations.jsm after fork since it sends an async message to + // the chrome process in its init() function. + try { + if (Services.prefs.getBoolPref("dom.apps.customization.enabled")) { + Cu.import("resource://gre/modules/UserCustomizations.jsm"); + } + } catch(e) {} + + // Load nsIAppsService after fork since its implementation loads + // AppsServiceChild.jsm + Cc["@mozilla.org/AppsService;1"].getService(Ci["nsIAppsService"]); + + // Load nsICookieService after fork since it sends an IPC constructor + // message to the chrome process. + Cc["@mozilla.org/cookieService;1"].getService(Ci["nsICookieService"]); + + // Load nsIPermissionManager after fork since it sends a message to the + // chrome process to read permissions. + Cc["@mozilla.org/permissionmanager;1"].getService(Ci["nsIPermissionManager"]); + + // Create this instance after fork since it loads AppsServiceChild.jsm + Cc["@mozilla.org/webapps;1"].createInstance(Ci["nsISupports"]); + + // Load nsIProtocolProxyService after fork since it asynchronously accesses + // the "Proxy Resolution" thread after it's frozen. + Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(Ci["nsIProtocolProxyService"]); + + // Call docShell.createAboutBlankContentViewer() after fork since it has IPC + // activity in the PCompositor protocol. + docShell.createAboutBlankContentViewer(null); + docShell.isActive = false; + }); })(this); + diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h index 243446debba..17230973340 100644 --- a/ipc/glue/MessageChannel.h +++ b/ipc/glue/MessageChannel.h @@ -175,6 +175,16 @@ class MessageChannel : HasResultCodes sIsPumpingMessages = aIsPumping; } +#ifdef MOZ_NUWA_PROCESS + void Block() { + mLink->Block(); + } + + void Unblock() { + mLink->Unblock(); + } +#endif + #ifdef OS_WIN struct MOZ_STACK_CLASS SyncStackFrame { diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp index 6a760ff7b77..219c2281140 100644 --- a/ipc/glue/MessageLink.cpp +++ b/ipc/glue/MessageLink.cpp @@ -16,6 +16,10 @@ #include "mozilla/dom/PContent.h" #include "mozilla/dom/PNuwa.h" #include "mozilla/hal_sandbox/PHal.h" +#if defined(DEBUG) || defined(ENABLE_TESTS) +#include "jsprf.h" +extern "C" char* PrintJSStack(); +#endif #endif #include "mozilla/Assertions.h" @@ -75,6 +79,7 @@ ProcessLink::ProcessLink(MessageChannel *aChan) , mExistingListener(nullptr) #ifdef MOZ_NUWA_PROCESS , mIsToNuwaProcess(false) + , mIsBlocked(false) #endif { } @@ -175,6 +180,8 @@ ProcessLink::SendMessage(Message *msg) mChan->mMonitor->AssertCurrentThreadOwns(); #ifdef MOZ_NUWA_PROCESS + // Parent to child: check whether we are sending some unexpected message to + // the Nuwa process. if (mIsToNuwaProcess && mozilla::dom::ContentParent::IsNuwaReady()) { switch (msg->type()) { case mozilla::dom::PNuwa::Msg_Fork__ID: @@ -193,6 +200,19 @@ ProcessLink::SendMessage(Message *msg) #endif } } + + // Nuwa to parent: check whether we are currently blocked. + if (IsNuwaProcess() && mIsBlocked) { +#if defined(ENABLE_TESTS) || defined(DEBUG) + char* jsstack = PrintJSStack(); + printf_stderr("Fatal error: sending a message to the chrome process" + "with a blocked IPC channel from \n%s", + jsstack ? jsstack : ""); + JS_smprintf_free(jsstack); + MOZ_CRASH(); +#endif + } + #endif mIOLoop->PostTask( diff --git a/ipc/glue/MessageLink.h b/ipc/glue/MessageLink.h index 1b7035f8589..bc66cda2f9d 100644 --- a/ipc/glue/MessageLink.h +++ b/ipc/glue/MessageLink.h @@ -128,6 +128,12 @@ class MessageLink virtual bool Unsound_IsClosed() const = 0; virtual uint32_t Unsound_NumQueuedMessages() const = 0; +#ifdef MOZ_NUWA_PROCESS + // To be overridden by ProcessLink. + virtual void Block() {} + virtual void Unblock() {} +#endif + protected: MessageChannel *mChan; }; @@ -175,12 +181,22 @@ class ProcessLink virtual bool Unsound_IsClosed() const override; virtual uint32_t Unsound_NumQueuedMessages() const override; +#ifdef MOZ_NUWA_PROCESS + void Block() override { + mIsBlocked = true; + } + void Unblock() override { + mIsBlocked = false; + } +#endif + protected: Transport* mTransport; MessageLoop* mIOLoop; // thread where IO happens Transport::Listener* mExistingListener; // channel's previous listener #ifdef MOZ_NUWA_PROCESS bool mIsToNuwaProcess; + bool mIsBlocked; #endif }; diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h index 489a7c69923..0bd17a5c2ef 100644 --- a/ipc/glue/ProtocolUtils.h +++ b/ipc/glue/ProtocolUtils.h @@ -235,6 +235,8 @@ public: void GetOpenedActors(nsTArray& aActors); + virtual MessageChannel* GetIPCChannel() = 0; + // This Unsafe version should only be used when all other threads are // frozen, since it performs no locking. It also takes a stack-allocated // array and its size (number of elements) rather than an nsTArray. The Nuwa diff --git a/mozglue/build/Nuwa.cpp b/mozglue/build/Nuwa.cpp index 3418f9900db..42d9bad34a3 100644 --- a/mozglue/build/Nuwa.cpp +++ b/mozglue/build/Nuwa.cpp @@ -2077,4 +2077,28 @@ IsNuwaReady() { return sNuwaReady; } +#if defined(DEBUG) || defined(ENABLE_TESTS) +MFBT_API void +NuwaAssertNotFrozen(unsigned int aThread, const char* aThreadName) { + if (!sIsNuwaProcess || !sIsFreezing) { + return; + } + + thread_info_t *tinfo = GetThreadInfo(static_cast(aThread)); + if (!tinfo) { + return; + } + + if ((tinfo->flags & TINFO_FLAG_NUWA_SUPPORT) && + !(tinfo->flags & TINFO_FLAG_NUWA_EXPLICIT_CHECKPOINT)) { + __android_log_print(ANDROID_LOG_FATAL, "Nuwa", + "Fatal error: the Nuwa process is about to deadlock in " + "accessing a frozen thread (%s, tid=%d).", + aThreadName ? aThreadName : "(unnamed)", + tinfo->origNativeThreadID); + abort(); + } +} +#endif + } // extern "C" diff --git a/mozglue/build/Nuwa.h b/mozglue/build/Nuwa.h index a96b62124dd..37c8baebf60 100644 --- a/mozglue/build/Nuwa.h +++ b/mozglue/build/Nuwa.h @@ -186,6 +186,16 @@ MFBT_API bool IsNuwaProcess(); * @return If the Nuwa process is ready for spawning new processes. */ MFBT_API bool IsNuwaReady(); + +#if defined(DEBUG) || defined(ENABLE_TESTS) +/** + * Asserts that aThread is not frozen. + */ +MFBT_API void NuwaAssertNotFrozen(unsigned int aThread, + const char* aThreadName); +#else +#define NuwaAssertNotFrozen(aThread, aThreadName) do {} while(0); +#endif }; #endif /* __NUWA_H_ */ diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index eaf569f6f24..18857113614 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -41,6 +41,10 @@ #include "nsICrashReporter.h" #endif +#ifdef MOZ_NUWA_PROCESS +#include "private/pprthred.h" +#endif + #ifdef XP_LINUX #include #include @@ -511,6 +515,12 @@ nsThread::PutEvent(already_AddRefed&& aEvent, nsNestedEventTarget* { nsCOMPtr obs; +#ifdef MOZ_NUWA_PROCESS + // On debug build or when tests are enabled, assert that we are not about to + // create a deadlock in the Nuwa process. + NuwaAssertNotFrozen(PR_GetThreadID(mThread), PR_GetThreadName(mThread)); +#endif + { MutexAutoLock lock(mLock); nsChainedEventQueue* queue = aTarget ? aTarget->mQueue : &mEventsRoot;