diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 50caa03c4e44..02d6995196b6 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -153,9 +153,9 @@ #endif #ifdef MOZ_NUWA_PROCESS -#include #include "ipc/Nuwa.h" #endif +#include "NuwaChild.h" #ifdef MOZ_GAMEPAD #include "mozilla/dom/GamepadService.h" @@ -220,19 +220,6 @@ using namespace mozilla::system; #endif using namespace mozilla::widget; -#ifdef MOZ_NUWA_PROCESS -static bool sNuwaForking = false; - -// The size of the reserved stack (in unsigned ints). It's used to reserve space -// to push sigsetjmp() in NuwaCheckpointCurrentThread() to higher in the stack -// so that after it returns and do other work we don't garble the stack we want -// to preserve in NuwaCheckpointCurrentThread(). -#define RESERVED_INT_STACK 128 - -// A sentinel value for checking whether RESERVED_INT_STACK is large enough. -#define STACK_SENTINEL_VALUE 0xdeadbeef -#endif - namespace mozilla { namespace dom { @@ -539,7 +526,7 @@ ContentChild* ContentChild::sSingleton; // Performs initialization that is not fork-safe, i.e. that must be done after // forking from the Nuwa process. -static void +void InitOnContentProcessCreated() { #ifdef MOZ_NUWA_PROCESS @@ -2206,8 +2193,25 @@ ContentChild::RecvCycleCollect() #ifdef MOZ_NUWA_PROCESS static void -OnFinishNuwaPreparation () +OnFinishNuwaPreparation() { + // We want to ensure that the PBackground actor gets cloned in the Nuwa + // process before we freeze. Also, we have to do this to avoid deadlock. + // Protocols that are "opened" (e.g. PBackground, PCompositor) block the + // main thread to wait for the IPC thread during the open operation. + // NuwaSpawnWait() blocks the IPC thread to wait for the main thread when + // the Nuwa process is forked. Unless we ensure that the two cannot happen + // at the same time then we risk deadlock. Spinning the event loop here + // guarantees the ordering is safe for PBackground. + while (!BackgroundChild::GetForCurrentThread()) { + if (NS_WARN_IF(!NS_ProcessNextEvent())) { + return; + } + } + + // This will create the actor. + unused << mozilla::dom::NuwaChild::GetSingleton(); + MakeNuwaProcess(); } #endif @@ -2493,87 +2497,6 @@ ContentChild::DeallocPOfflineCacheUpdateChild(POfflineCacheUpdateChild* actor) return true; } -#ifdef MOZ_NUWA_PROCESS -class CallNuwaSpawn : public nsRunnable -{ -public: - NS_IMETHOD Run() - { - NuwaSpawn(); - if (IsNuwaProcess()) { - return NS_OK; - } - - // In the new process. - ContentChild* child = ContentChild::GetSingleton(); - child->SetProcessName(NS_LITERAL_STRING("(Preallocated app)"), false); - - // Perform other after-fork initializations. - InitOnContentProcessCreated(); - - return NS_OK; - } -}; - -static void -DoNuwaFork() -{ - NuwaSpawnPrepare(); // NuwaSpawn will be blocked. - - { - nsCOMPtr callSpawn(new CallNuwaSpawn()); - NS_DispatchToMainThread(callSpawn); - } - - // IOThread should be blocked here for waiting NuwaSpawn(). - NuwaSpawnWait(); // Now! NuwaSpawn can go. - // Here, we can make sure the spawning was finished. -} - -/** - * This function should keep IO thread in a stable state and freeze it - * until the spawning is finished. - */ -static void -RunNuwaFork() -{ - if (NuwaCheckpointCurrentThread()) { - DoNuwaFork(); - } -} -#endif - -bool -ContentChild::RecvNuwaFork() -{ -#ifdef MOZ_NUWA_PROCESS - if (sNuwaForking) { // No reentry. - return true; - } - sNuwaForking = true; - - // We want to ensure that the PBackground actor gets cloned in the Nuwa - // process before we freeze. Also, we have to do this to avoid deadlock. - // Protocols that are "opened" (e.g. PBackground, PCompositor) block the - // main thread to wait for the IPC thread during the open operation. - // NuwaSpawnWait() blocks the IPC thread to wait for the main thread when - // the Nuwa process is forked. Unless we ensure that the two cannot happen - // at the same time then we risk deadlock. Spinning the event loop here - // guarantees the ordering is safe for PBackground. - while (!BackgroundChild::GetForCurrentThread()) { - if (NS_WARN_IF(!NS_ProcessNextEvent())) { - return false; - } - } - - MessageLoop* ioloop = XRE_GetIOMessageLoop(); - ioloop->PostTask(FROM_HERE, NewRunnableFunction(RunNuwaFork)); - return true; -#else - return false; // Makes the underlying IPC channel abort. -#endif -} - bool ContentChild::RecvOnAppThemeChanged() { @@ -2918,109 +2841,3 @@ ContentChild::RecvTestGraphicsDeviceReset(const uint32_t& aResetReason) } // namespace dom } // namespace mozilla -extern "C" { - -#if defined(MOZ_NUWA_PROCESS) -NS_EXPORT void -GetProtoFdInfos(NuwaProtoFdInfo* aInfoList, - size_t aInfoListSize, - size_t* aInfoSize) -{ - size_t i = 0; - - mozilla::dom::ContentChild* content = - mozilla::dom::ContentChild::GetSingleton(); - aInfoList[i].protoId = content->GetProtocolId(); - aInfoList[i].originFd = - content->GetTransport()->GetFileDescriptor(); - i++; - - IToplevelProtocol* actors[NUWA_TOPLEVEL_MAX]; - size_t count = content->GetOpenedActorsUnsafe(actors, ArrayLength(actors)); - for (size_t j = 0; j < count; j++) { - IToplevelProtocol* actor = actors[j]; - if (i >= aInfoListSize) { - NS_RUNTIMEABORT("Too many top level protocols!"); - } - - aInfoList[i].protoId = actor->GetProtocolId(); - aInfoList[i].originFd = - actor->GetTransport()->GetFileDescriptor(); - i++; - } - - if (i > NUWA_TOPLEVEL_MAX) { - NS_RUNTIMEABORT("Too many top level protocols!"); - } - *aInfoSize = i; -} - -class RunAddNewIPCProcess : public nsRunnable -{ -public: - RunAddNewIPCProcess(pid_t aPid, - nsTArray& aMaps) - : mPid(aPid) - { - mMaps.SwapElements(aMaps); - } - - NS_IMETHOD Run() - { - mozilla::dom::ContentChild::GetSingleton()-> - SendAddNewProcess(mPid, mMaps); - - MOZ_ASSERT(sNuwaForking); - sNuwaForking = false; - - return NS_OK; - } - -private: - pid_t mPid; - nsTArray mMaps; -}; - -/** - * AddNewIPCProcess() is called by Nuwa process to tell the parent - * process that a new process is created. - * - * In the newly created process, ResetContentChildTransport() is called to - * reset fd for the IPC Channel and the session. - */ -NS_EXPORT void -AddNewIPCProcess(pid_t aPid, NuwaProtoFdInfo* aInfoList, size_t aInfoListSize) -{ - nsTArray maps; - - for (size_t i = 0; i < aInfoListSize; i++) { - int _fd = aInfoList[i].newFds[NUWA_NEWFD_PARENT]; - mozilla::ipc::FileDescriptor fd(_fd); - mozilla::ipc::ProtocolFdMapping map(aInfoList[i].protoId, fd); - maps.AppendElement(map); - } - - nsRefPtr runner = new RunAddNewIPCProcess(aPid, maps); - NS_DispatchToMainThread(runner); -} - -NS_EXPORT void -OnNuwaProcessReady() -{ - mozilla::dom::ContentChild* content = - mozilla::dom::ContentChild::GetSingleton(); - content->SendNuwaReady(); -} - -NS_EXPORT void -AfterNuwaFork() -{ - SetCurrentProcessPrivileges(base::PRIVILEGES_DEFAULT); -#if defined(XP_LINUX) && defined(MOZ_SANDBOX) - mozilla::SandboxEarlyInit(XRE_GetProcessType(), /* isNuwa: */ false); -#endif -} - -#endif // MOZ_NUWA_PROCESS - -} diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index 94963c61fdc6..5fe6698b8f45 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -363,8 +363,6 @@ public: const bool& aIsHotSwappable) override; virtual bool RecvVolumeRemoved(const nsString& aFsName) override; - virtual bool RecvNuwaFork() override; - virtual bool RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority) override; virtual bool RecvMinimizeMemoryUsage() override; @@ -517,6 +515,9 @@ private: DISALLOW_EVIL_CONSTRUCTORS(ContentChild); }; +void +InitOnContentProcessCreated(); + uint64_t NextWindowID(); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index aef9a0096683..f4e3b6c0fb19 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -42,6 +42,7 @@ #include "mozilla/dom/ExternalHelperAppParent.h" #include "mozilla/dom/FileSystemRequestParent.h" #include "mozilla/dom/GeolocationBinding.h" +#include "mozilla/dom/NuwaParent.h" #include "mozilla/dom/PContentBridgeParent.h" #include "mozilla/dom/PContentPermissionRequestParent.h" #include "mozilla/dom/PCycleCollectWithLogsParent.h" @@ -75,6 +76,7 @@ #include "mozilla/layers/SharedBufferManagerParent.h" #include "mozilla/LookAndFeel.h" #include "mozilla/media/MediaParent.h" +#include "mozilla/Move.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/plugins/PluginBridge.h" #include "mozilla/Preferences.h" @@ -2841,47 +2843,54 @@ ContentParent::RecvDataStoreGetStores( return true; } -bool -ContentParent::RecvNuwaReady() +void +ContentParent::ForkNewProcess(bool aBlocking) { #ifdef MOZ_NUWA_PROCESS - if (!IsNuwaProcess()) { - NS_ERROR( - nsPrintfCString( - "Terminating child process %d for unauthorized IPC message: NuwaReady", - Pid()).get()); + uint32_t pid; + auto fds = MakeUnique>(); - KillHard("NuwaReady"); - return false; - } - sNuwaReady = true; - PreallocatedProcessManager::OnNuwaReady(); - return true; + MOZ_ASSERT(IsNuwaProcess() && mNuwaParent); + + if (mNuwaParent->ForkNewProcess(pid, mozilla::Move(fds), aBlocking)) { + OnNewProcessCreated(pid, mozilla::Move(fds)); + } #else - NS_ERROR("ContentParent::RecvNuwaReady() not implemented!"); - return false; + NS_ERROR("ContentParent::ForkNewProcess() not implemented!"); #endif } -bool -ContentParent::RecvAddNewProcess(const uint32_t& aPid, - InfallibleTArray&& aFds) +void +ContentParent::OnNuwaReady() { #ifdef MOZ_NUWA_PROCESS - if (!IsNuwaProcess()) { - NS_ERROR( - nsPrintfCString( - "Terminating child process %d for unauthorized IPC message: " - "AddNewProcess(%d)", Pid(), aPid).get()); + // Protection from unauthorized IPC message is done in PNuwa protocol. + // Just assert that this actor is really for the Nuwa process. + MOZ_ASSERT(IsNuwaProcess()); + + sNuwaReady = true; + PreallocatedProcessManager::OnNuwaReady(); + return; +#else + NS_ERROR("ContentParent::OnNuwaReady() not implemented!"); + return; +#endif +} + +void +ContentParent::OnNewProcessCreated(uint32_t aPid, + UniquePtr>&& aFds) +{ +#ifdef MOZ_NUWA_PROCESS + // Protection from unauthorized IPC message is done in PNuwa protocol. + // Just assert that this actor is really for the Nuwa process. + MOZ_ASSERT(IsNuwaProcess()); - KillHard("AddNewProcess"); - return false; - } nsRefPtr content; content = new ContentParent(this, MAGIC_PREALLOCATED_APP_MANIFEST_URL, aPid, - Move(aFds)); + Move(*aFds.get())); content->Init(); size_t numNuwaPrefUpdates = sNuwaPrefUpdates ? @@ -2909,10 +2918,10 @@ ContentParent::RecvAddNewProcess(const uint32_t& aPid, "Unexpected values"); PreallocatedProcessManager::PublishSpareProcess(content); - return true; + return; #else - NS_ERROR("ContentParent::RecvAddNewProcess() not implemented!"); - return false; + NS_ERROR("ContentParent::OnNewProcessCreated() not implemented!"); + return; #endif } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index e7b207dc1d29..fe9af7072c3f 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_ContentParent_h #define mozilla_dom_ContentParent_h +#include "mozilla/dom/NuwaParent.h" #include "mozilla/dom/PContentParent.h" #include "mozilla/dom/nsIContentParent.h" #include "mozilla/ipc/GeckoChildProcessHost.h" @@ -383,6 +384,9 @@ public: bool HasGamepadListener() const { return mHasGamepadListener; } + void SetNuwaParent(NuwaParent* aNuwaParent) { mNuwaParent = aNuwaParent; } + void ForkNewProcess(bool aBlocking); + protected: void OnChannelConnected(int32_t pid) override; virtual void ActorDestroy(ActorDestroyReason why) override; @@ -775,10 +779,10 @@ private: virtual bool RecvSystemMessageHandled() override; - virtual bool RecvNuwaReady() override; - - virtual bool RecvAddNewProcess(const uint32_t& aPid, - InfallibleTArray&& aFds) override; + // Callbacks from NuwaParent. + void OnNuwaReady(); + void OnNewProcessCreated(uint32_t aPid, + UniquePtr>&& aFds); virtual bool RecvCreateFakeVolume(const nsString& fsName, const nsString& mountPoint) override; @@ -916,6 +920,9 @@ private: friend class CrashReporterParent; + // Allows NuwaParent to access OnNuwaReady() and OnNewProcessCreated(). + friend class NuwaParent; + nsRefPtr mConsoleService; nsConsoleService* GetConsoleService(); @@ -933,6 +940,11 @@ private: #endif PProcessHangMonitorParent* mHangMonitorActor; + + // NuwaParent and ContentParent hold strong references to each other. The + // cycle will be broken when either actor is destroyed. + nsRefPtr mNuwaParent; + #ifdef MOZ_ENABLE_PROFILER_SPS nsRefPtr mGatherer; #endif diff --git a/dom/ipc/NuwaChild.cpp b/dom/ipc/NuwaChild.cpp new file mode 100644 index 000000000000..cda41ae4dbc0 --- /dev/null +++ b/dom/ipc/NuwaChild.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "ContentChild.h" +#ifdef MOZ_NUWA_PROCESS +#include "ipc/Nuwa.h" +#endif +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/ProtocolUtils.h" +#if defined(MOZ_CONTENT_SANDBOX) +#if defined(XP_LINUX) +#include "mozilla/Sandbox.h" +#include "mozilla/SandboxInfo.h" +#elif defined(XP_MACOSX) +#include "mozilla/Sandbox.h" +#endif +#endif +#include "mozilla/unused.h" +#include "nsXULAppAPI.h" +#include "NuwaChild.h" + + +using namespace mozilla::ipc; +using namespace mozilla::dom; + +namespace mozilla { +namespace dom { + +#ifdef MOZ_NUWA_PROCESS + +namespace { + +class CallNuwaSpawn: public nsRunnable +{ +public: + NS_IMETHOD Run() + { + NuwaSpawn(); + if (IsNuwaProcess()) { + return NS_OK; + } + + // In the new process. + ContentChild* child = ContentChild::GetSingleton(); + child->InitProcessAttributes(); + + // Perform other after-fork initializations. + InitOnContentProcessCreated(); + + return NS_OK; + } +}; + +static void +DoNuwaFork() +{ + NuwaSpawnPrepare(); // NuwaSpawn will be blocked. + + { + nsCOMPtr callSpawn(new CallNuwaSpawn()); + NS_DispatchToMainThread(callSpawn); + } + + // IOThread should be blocked here for waiting NuwaSpawn(). + NuwaSpawnWait(); // Now! NuwaSpawn can go. + // Here, we can make sure the spawning was finished. +} + +/** + * This function should keep IO thread in a stable state and freeze it + * until the spawning is finished. + */ +static void +RunNuwaFork() +{ + if (NuwaCheckpointCurrentThread()) { + DoNuwaFork(); + } +} + +static bool sNuwaForking = false; + +void +NuwaFork() +{ + if (sNuwaForking) { // No reentry. + return; + } + sNuwaForking = true; + + MessageLoop* ioloop = XRE_GetIOMessageLoop(); + ioloop->PostTask(FROM_HERE, NewRunnableFunction(RunNuwaFork)); +} + +} // Anonymous namespace. + +#endif + +NuwaChild* NuwaChild::sSingleton; + +NuwaChild* +NuwaChild::GetSingleton() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingleton) { + PNuwaChild* nuwaChild = + BackgroundChild::GetForCurrentThread()->SendPNuwaConstructor(); + MOZ_ASSERT(nuwaChild); + + sSingleton = static_cast(nuwaChild); + } + + return sSingleton; +} + + +bool +NuwaChild::RecvFork() +{ +#ifdef MOZ_NUWA_PROCESS + if (!IsNuwaProcess()) { + NS_ERROR( + nsPrintfCString( + "Terminating child process %d for unauthorized IPC message: " + "RecvFork(%d)", getpid()).get()); + return false; + } + + nsCOMPtr runnable = + NS_NewRunnableFunction(&NuwaFork); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); + + return true; +#else + NS_ERROR("NuwaChild::RecvFork() not implemented!"); + return false; +#endif +} + +} // namespace dom +} // namespace mozilla + + +extern "C" { + +#if defined(MOZ_NUWA_PROCESS) +NS_EXPORT void +GetProtoFdInfos(NuwaProtoFdInfo* aInfoList, + size_t aInfoListSize, + size_t* aInfoSize) +{ + size_t i = 0; + + mozilla::dom::ContentChild* content = + mozilla::dom::ContentChild::GetSingleton(); + aInfoList[i].protoId = content->GetProtocolId(); + aInfoList[i].originFd = + content->GetTransport()->GetFileDescriptor(); + i++; + + IToplevelProtocol* actors[NUWA_TOPLEVEL_MAX]; + size_t count = content->GetOpenedActorsUnsafe(actors, ArrayLength(actors)); + for (size_t j = 0; j < count; j++) { + IToplevelProtocol* actor = actors[j]; + if (i >= aInfoListSize) { + NS_RUNTIMEABORT("Too many top level protocols!"); + } + + aInfoList[i].protoId = actor->GetProtocolId(); + aInfoList[i].originFd = + actor->GetTransport()->GetFileDescriptor(); + i++; + } + + if (i > NUWA_TOPLEVEL_MAX) { + NS_RUNTIMEABORT("Too many top level protocols!"); + } + *aInfoSize = i; +} + +class RunAddNewIPCProcess : public nsRunnable +{ +public: + RunAddNewIPCProcess(pid_t aPid, + nsTArray& aMaps) + : mPid(aPid) + { + mMaps.SwapElements(aMaps); + } + + NS_IMETHOD Run() + { + NuwaChild::GetSingleton()->SendAddNewProcess(mPid, mMaps); + + MOZ_ASSERT(sNuwaForking); + sNuwaForking = false; + + return NS_OK; + } + +private: + pid_t mPid; + nsTArray mMaps; +}; + +/** + * AddNewIPCProcess() is called by Nuwa process to tell the parent + * process that a new process is created. + * + * In the newly created process, ResetContentChildTransport() is called to + * reset fd for the IPC Channel and the session. + */ +NS_EXPORT void +AddNewIPCProcess(pid_t aPid, NuwaProtoFdInfo* aInfoList, size_t aInfoListSize) +{ + nsTArray maps; + + for (size_t i = 0; i < aInfoListSize; i++) { + int _fd = aInfoList[i].newFds[NUWA_NEWFD_PARENT]; + mozilla::ipc::FileDescriptor fd(_fd); + mozilla::ipc::ProtocolFdMapping map(aInfoList[i].protoId, fd); + maps.AppendElement(map); + } + + nsRefPtr runner = new RunAddNewIPCProcess(aPid, maps); + NS_DispatchToMainThread(runner); +} + +NS_EXPORT void +OnNuwaProcessReady() +{ + NuwaChild* nuwaChild = NuwaChild::GetSingleton(); + MOZ_ASSERT(nuwaChild); + + mozilla::unused << nuwaChild->SendNotifyReady(); +} + +NS_EXPORT void +AfterNuwaFork() +{ + SetCurrentProcessPrivileges(base::PRIVILEGES_DEFAULT); +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + mozilla::SandboxEarlyInit(XRE_GetProcessType(), /* isNuwa: */ false); +#endif +} + +#endif // MOZ_NUWA_PROCESS + +} diff --git a/dom/ipc/NuwaChild.h b/dom/ipc/NuwaChild.h new file mode 100644 index 000000000000..3a65d16081b8 --- /dev/null +++ b/dom/ipc/NuwaChild.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_NuwaChild_h +#define mozilla_dom_NuwaChild_h + +#include "mozilla/Assertions.h" +#include "mozilla/dom/PNuwaChild.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace dom { +class NuwaChild: public mozilla::dom::PNuwaChild +{ +public: + virtual bool RecvFork() override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override + { } + + static NuwaChild* GetSingleton(); + +private: + static NuwaChild* sSingleton; +}; + +} // namespace dom +} // namespace mozilla + +#endif + diff --git a/dom/ipc/NuwaParent.cpp b/dom/ipc/NuwaParent.cpp new file mode 100644 index 000000000000..c37770389630 --- /dev/null +++ b/dom/ipc/NuwaParent.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/unused.h" +#include "nsThreadUtils.h" +#include "NuwaParent.h" + +using namespace mozilla::ipc; +using namespace mozilla::dom; +using namespace IPC; + +namespace mozilla { +namespace dom { + +/*static*/ NuwaParent* +NuwaParent::Alloc() { + nsRefPtr actor = new NuwaParent(); + return actor.forget().take(); +} + +/*static*/ bool +NuwaParent::ActorConstructed(mozilla::dom::PNuwaParent *aActor) +{ + NuwaParent* actor = static_cast(aActor); + actor->ActorConstructed(); + + return true; +} + +/*static*/ bool +NuwaParent::Dealloc(mozilla::dom::PNuwaParent *aActor) +{ + nsRefPtr actor = dont_AddRef(static_cast(aActor)); + return true; +} + +NuwaParent::NuwaParent() + : mBlocked(false) + , mMonitor("NuwaParent") + , mClonedActor(nullptr) + , mWorkerThread(do_GetCurrentThread()) + , mNewProcessPid(0) +{ + AssertIsOnBackgroundThread(); +} + +NuwaParent::~NuwaParent() +{ + // Both the worker thread and the main thread (ContentParent) hold a ref to + // this. The instance may be destroyed on either thread. + MOZ_ASSERT(!mContentParent); +} + +inline void +NuwaParent::AssertIsOnWorkerThread() +{ + nsCOMPtr currentThread = do_GetCurrentThread(); + MOZ_ASSERT(currentThread == mWorkerThread); +} + +bool +NuwaParent::ActorConstructed() +{ + AssertIsOnWorkerThread(); + MOZ_ASSERT(Manager()); + MOZ_ASSERT(!mContentParent); + + mContentParent = BackgroundParent::GetContentParent(Manager()); + if (!mContentParent) { + return false; + } + + // mContentParent is guaranteed to be alive. It's safe to set its backward ref + // to this. + mContentParent->SetNuwaParent(this); + return true; +} + +mozilla::ipc::IProtocol* +NuwaParent::CloneProtocol(Channel* aChannel, + ProtocolCloneContext* aCtx) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsRefPtr self = this; + + MonitorAutoLock lock(mMonitor); + + // Alloc NuwaParent on the worker thread. + nsCOMPtr runnable = NS_NewRunnableFunction([self] () -> void + { + MonitorAutoLock lock(self->mMonitor); + // XXX Calling NuwaParent::Alloc() leads to a compilation error. Use + // self->Alloc() as a workaround. + self->mClonedActor = self->Alloc(); + lock.Notify(); + }); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mWorkerThread->Dispatch(runnable, + NS_DISPATCH_NORMAL))); + + while (!mClonedActor) { + lock.Wait(); + } + nsRefPtr actor = mClonedActor; + mClonedActor = nullptr; + + // mManager of the cloned actor is assigned after returning from this method. + // We can't call ActorConstructed() right after Alloc() in the above runnable. + // To be safe we dispatch a runnable to the current thread to do it. + runnable = NS_NewRunnableFunction([actor] () -> void + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr nested = NS_NewRunnableFunction([actor] () -> void + { + AssertIsOnBackgroundThread(); + + // Call NuwaParent::ActorConstructed() on the worker thread. + actor->ActorConstructed(); + + // The actor can finally be deleted after fully constructed. + mozilla::unused << actor->Send__delete__(actor); + }); + MOZ_ASSERT(nested); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + actor->mWorkerThread->Dispatch(nested, NS_DISPATCH_NORMAL))); + }); + + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); + + return actor; +} + +void +NuwaParent::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnWorkerThread(); + + nsRefPtr self = this; + nsCOMPtr runnable = NS_NewRunnableFunction([self] () -> void + { + // These extra nsRefPtr serve as kungFuDeathGrip to keep both objects from + // deletion in breaking the ref cycle. + nsRefPtr contentParent = self->mContentParent; + + contentParent->SetNuwaParent(nullptr); + // Need to clear the ref to ContentParent on the main thread. + self->mContentParent = nullptr; + }); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); +} + +bool +NuwaParent::RecvNotifyReady() +{ +#ifdef MOZ_NUWA_PROCESS + if (!mContentParent || !mContentParent->IsNuwaProcess()) { + NS_ERROR("Received NotifyReady() message from a non-Nuwa process."); + return false; + } + + // Creating a NonOwningRunnableMethod here is safe because refcount changes of + // mContentParent have to go the the main thread. The mContentParent will + // be alive when the runnable runs. + nsCOMPtr runnable = + NS_NewNonOwningRunnableMethod(mContentParent.get(), + &ContentParent::OnNuwaReady); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); + + return true; +#else + NS_ERROR("NuwaParent::RecvNotifyReady() not implemented!"); + return false; +#endif +} + +bool +NuwaParent::RecvAddNewProcess(const uint32_t& aPid, + nsTArray&& aFds) +{ +#ifdef MOZ_NUWA_PROCESS + if (!mContentParent || !mContentParent->IsNuwaProcess()) { + NS_ERROR("Received AddNewProcess() message from a non-Nuwa process."); + return false; + } + + mNewProcessPid = aPid; + mNewProcessFds->SwapElements(aFds); + MonitorAutoLock lock(mMonitor); + if (mBlocked) { + // Unblock ForkNewProcess(). + mMonitor.Notify(); + mBlocked = false; + } else { + nsCOMPtr runnable = + NS_NewNonOwningRunnableMethodWithArgs< + uint32_t, + UniquePtr>&& >( + mContentParent.get(), + &ContentParent::OnNewProcessCreated, + mNewProcessPid, + Move(mNewProcessFds)); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); + } + return true; +#else + NS_ERROR("NuwaParent::RecvAddNewProcess() not implemented!"); + return false; +#endif +} + +bool +NuwaParent::ForkNewProcess(uint32_t& aPid, + UniquePtr>&& aFds, + bool aBlocking) +{ + MOZ_ASSERT(mWorkerThread); + MOZ_ASSERT(NS_IsMainThread()); + + mNewProcessFds = Move(aFds); + + nsRefPtr self = this; + nsCOMPtr runnable = NS_NewRunnableFunction([self] () -> void + { + mozilla::unused << self->SendFork(); + }); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mWorkerThread->Dispatch(runnable, + NS_DISPATCH_NORMAL))); + if (!aBlocking) { + return false; + } + + MonitorAutoLock lock(mMonitor); + mBlocked = true; + while (mBlocked) { + // This will be notified in NuwaParent::RecvAddNewProcess(). + lock.Wait(); + } + + if (!mNewProcessPid) { + return false; + } + + aPid = mNewProcessPid; + aFds = Move(mNewProcessFds); + + mNewProcessPid = 0; + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/NuwaParent.h b/dom/ipc/NuwaParent.h new file mode 100644 index 000000000000..538bc5122ac9 --- /dev/null +++ b/dom/ipc/NuwaParent.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_NuwaParent_h +#define mozilla_dom_NuwaParent_h + +#include "base/message_loop.h" +#include "mozilla/dom/PNuwaParent.h" +#include "mozilla/Monitor.h" +#include "mozilla/nsRefPtr.h" + +namespace mozilla { +namespace dom { + +class ContentParent; + +class NuwaParent : public mozilla::dom::PNuwaParent +{ +public: + explicit NuwaParent(); + + // Called on the main thread. + bool ForkNewProcess(uint32_t& aPid, + UniquePtr>&& aFds, + bool aBlocking); + + // Called on the background thread. + bool ActorConstructed(); + + // Both the worker thread and the main thread hold a ref to this. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NuwaParent) + + // Functions to be invoked by the manager of this actor to alloc/dealloc the + // actor. + static NuwaParent* Alloc(); + static bool ActorConstructed(mozilla::dom::PNuwaParent *aActor); + static bool Dealloc(mozilla::dom::PNuwaParent *aActor); + +protected: + virtual ~NuwaParent(); + + virtual bool RecvNotifyReady() override; + virtual bool RecvAddNewProcess(const uint32_t& aPid, + nsTArray&& aFds) override; + virtual mozilla::ipc::IProtocol* + CloneProtocol(Channel* aChannel, + ProtocolCloneContext* aCtx) override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + void AssertIsOnWorkerThread(); + + bool mBlocked; + mozilla::Monitor mMonitor; + NuwaParent* mClonedActor; + + nsCOMPtr mWorkerThread; + + uint32_t mNewProcessPid; + UniquePtr> mNewProcessFds; + + // The mutual reference will be broken on the main thread. + nsRefPtr mContentParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index ee2f39cf8108..791908915cd5 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -579,9 +579,6 @@ child: // Notify volume is removed. VolumeRemoved(nsString fsName); - // Ask the Nuwa process to create a new child process. - NuwaFork(); - NotifyProcessPriorityChanged(ProcessPriority priority); MinimizeMemoryUsage(); @@ -888,10 +885,6 @@ parent: // Notify the parent that the child has finished handling a system message. async SystemMessageHandled(); - NuwaReady(); - - sync AddNewProcess(uint32_t pid, ProtocolFdMapping[] aFds); - // called by the child (test code only) to propagate volume changes to the parent async CreateFakeVolume(nsString fsName, nsString mountPoint); async SetFakeVolumeState(nsString fsName, int32_t fsState); diff --git a/dom/ipc/PNuwa.ipdl b/dom/ipc/PNuwa.ipdl new file mode 100644 index 000000000000..8da39194d4c0 --- /dev/null +++ b/dom/ipc/PNuwa.ipdl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PBackground; +include ProtocolTypes; + +namespace mozilla { +namespace dom { + +sync protocol PNuwa +{ + manager PBackground; + +child: + // Ask the Nuwa process to create a new child process. + async Fork(); + + // This message will be sent to non-Nuwa process, or to Nuwa process during + // test. + async __delete__(); + +parent: + async NotifyReady(); + sync AddNewProcess(uint32_t pid, ProtocolFdMapping[] aFds); +}; + +} // namespace layout +} // namespace mozilla + diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp index b9cb984b283c..1f67c3127e54 100644 --- a/dom/ipc/PreallocatedProcessManager.cpp +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -278,10 +278,15 @@ PreallocatedProcessManagerImpl::GetSpareProcess() { MOZ_ASSERT(NS_IsMainThread()); - if (mSpareProcesses.IsEmpty()) { + if (!mIsNuwaReady) { return nullptr; } + if (mSpareProcesses.IsEmpty()) { + // After this call, there should be a spare process. + mPreallocatedAppProcess->ForkNewProcess(true); + } + nsRefPtr process = mSpareProcesses.LastElement(); mSpareProcesses.RemoveElementAt(mSpareProcesses.Length() - 1); @@ -369,7 +374,7 @@ PreallocatedProcessManagerImpl::PreallocatedProcessReady() void PreallocatedProcessManagerImpl::NuwaFork() { - mozilla::unused << mPreallocatedAppProcess->SendNuwaFork(); + mPreallocatedAppProcess->ForkNewProcess(false); } #endif diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index 62e8852dddee..911d214bfeef 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -34,6 +34,8 @@ EXPORTS.mozilla.dom += [ 'FilePickerParent.h', 'nsIContentChild.h', 'nsIContentParent.h', + 'NuwaChild.h', + 'NuwaParent.h', 'PermissionMessageUtils.h', 'StructuredCloneUtils.h', 'TabChild.h', @@ -62,6 +64,8 @@ UNIFIED_SOURCES += [ 'FilePickerParent.cpp', 'nsIContentChild.cpp', 'nsIContentParent.cpp', + 'NuwaChild.cpp', + 'NuwaParent.cpp', 'PermissionMessageUtils.cpp', 'PreallocatedProcessManager.cpp', 'ProcessPriorityManager.cpp', @@ -101,6 +105,7 @@ IPDL_SOURCES += [ 'PDocumentRenderer.ipdl', 'PFilePicker.ipdl', 'PMemoryReportRequest.ipdl', + 'PNuwa.ipdl', 'PPluginWidget.ipdl', 'PProcessHangMonitor.ipdl', 'PScreenManager.ipdl', diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp index ef30c6c19528..79f6394d1693 100644 --- a/ipc/glue/BackgroundChildImpl.cpp +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -14,6 +14,7 @@ #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/MessagePortChild.h" +#include "mozilla/dom/NuwaChild.h" #include "mozilla/ipc/PBackgroundTestChild.h" #include "mozilla/layout/VsyncChild.h" #include "mozilla/net/PUDPSocketChild.h" @@ -57,6 +58,7 @@ using mozilla::net::PUDPSocketChild; using mozilla::dom::cache::PCacheChild; using mozilla::dom::cache::PCacheStorageChild; using mozilla::dom::cache::PCacheStreamControlChild; +using mozilla::dom::PNuwaChild; // ----------------------------------------------------------------------------- // BackgroundChildImpl::ThreadLocal @@ -350,6 +352,21 @@ BackgroundChildImpl::DeallocPMessagePortChild(PMessagePortChild* aActor) return true; } +PNuwaChild* +BackgroundChildImpl::AllocPNuwaChild() +{ + return new mozilla::dom::NuwaChild(); +} + +bool +BackgroundChildImpl::DeallocPNuwaChild(PNuwaChild* aActor) +{ + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/BackgroundChildImpl.h b/ipc/glue/BackgroundChildImpl.h index fce00b0b6e1e..dea4b2c1cce0 100644 --- a/ipc/glue/BackgroundChildImpl.h +++ b/ipc/glue/BackgroundChildImpl.h @@ -122,6 +122,12 @@ protected: virtual bool DeallocPMessagePortChild(PMessagePortChild* aActor) override; + + virtual PNuwaChild* + AllocPNuwaChild() override; + + virtual bool + DeallocPNuwaChild(PNuwaChild* aActor) override; }; class BackgroundChildImpl::ThreadLocal final diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp index a44fc54e60da..5ae227a74a6b 100644 --- a/ipc/glue/BackgroundParentImpl.cpp +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -9,6 +9,7 @@ #include "mozilla/AppProcessChecker.h" #include "mozilla/Assertions.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/NuwaParent.h" #include "mozilla/dom/PBlobParent.h" #include "mozilla/dom/MessagePortParent.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" @@ -42,6 +43,8 @@ using mozilla::dom::cache::PCacheStorageParent; using mozilla::dom::cache::PCacheStreamControlParent; using mozilla::dom::MessagePortParent; using mozilla::dom::PMessagePortParent; +using mozilla::dom::PNuwaParent; +using mozilla::dom::NuwaParent; using mozilla::dom::UDPSocketParent; namespace { @@ -233,6 +236,24 @@ BackgroundParentImpl::DeallocPFileDescriptorSetParent( return true; } +PNuwaParent* +BackgroundParentImpl::AllocPNuwaParent() +{ + return mozilla::dom::NuwaParent::Alloc(); +} + +bool +BackgroundParentImpl::RecvPNuwaConstructor(PNuwaParent* aActor) +{ + return mozilla::dom::NuwaParent::ActorConstructed(aActor); +} + +bool +BackgroundParentImpl::DeallocPNuwaParent(PNuwaParent *aActor) +{ + return mozilla::dom::NuwaParent::Dealloc(aActor); +} + BackgroundParentImpl::PVsyncParent* BackgroundParentImpl::AllocPVsyncParent() { diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index b1fddf32a9ec..16f72fb7468d 100644 --- a/ipc/glue/BackgroundParentImpl.h +++ b/ipc/glue/BackgroundParentImpl.h @@ -86,6 +86,15 @@ protected: virtual bool DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override; + virtual PNuwaParent* + AllocPNuwaParent() override; + + virtual bool + RecvPNuwaConstructor(PNuwaParent* aActor) override; + + virtual bool + DeallocPNuwaParent(PNuwaParent* aActor) override; + virtual PServiceWorkerManagerParent* AllocPServiceWorkerManagerParent() override; diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp index 3fe488548bcd..6a760ff7b778 100644 --- a/ipc/glue/MessageLink.cpp +++ b/ipc/glue/MessageLink.cpp @@ -13,8 +13,9 @@ #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #include "mozilla/Preferences.h" -#include "mozilla/dom/ContentParent.h" -#include "mozilla/hal_sandbox/PHalParent.h" +#include "mozilla/dom/PContent.h" +#include "mozilla/dom/PNuwa.h" +#include "mozilla/hal_sandbox/PHal.h" #endif #include "mozilla/Assertions.h" @@ -176,8 +177,8 @@ ProcessLink::SendMessage(Message *msg) #ifdef MOZ_NUWA_PROCESS if (mIsToNuwaProcess && mozilla::dom::ContentParent::IsNuwaReady()) { switch (msg->type()) { - case mozilla::dom::PContent::Msg_NuwaFork__ID: - case mozilla::dom::PContent::Reply_AddNewProcess__ID: + case mozilla::dom::PNuwa::Msg_Fork__ID: + case mozilla::dom::PNuwa::Reply_AddNewProcess__ID: case mozilla::dom::PContent::Msg_NotifyPhoneStateChange__ID: case mozilla::hal_sandbox::PHal::Msg_NotifyNetworkChange__ID: case GOODBYE_MESSAGE_TYPE: diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl index 72c929c4828b..1dca94805d10 100644 --- a/ipc/glue/PBackground.ipdl +++ b/ipc/glue/PBackground.ipdl @@ -11,6 +11,7 @@ include protocol PCacheStorage; include protocol PCacheStreamControl; include protocol PFileDescriptorSet; include protocol PMessagePort; +include protocol PNuwa; include protocol PServiceWorkerManager; include protocol PUDPSocket; include protocol PVsync; @@ -36,6 +37,7 @@ sync protocol PBackground manages PCacheStreamControl; manages PFileDescriptorSet; manages PMessagePort; + manages PNuwa; manages PServiceWorkerManager; manages PUDPSocket; manages PVsync; @@ -60,6 +62,8 @@ parent: PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId); + PNuwa(); + MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId); child: