From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- ipc/glue/BackgroundImpl.cpp | 1306 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1306 insertions(+) create mode 100644 ipc/glue/BackgroundImpl.cpp (limited to 'ipc/glue/BackgroundImpl.cpp') diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp new file mode 100644 index 0000000000..420f047f42 --- /dev/null +++ b/ipc/glue/BackgroundImpl.cpp @@ -0,0 +1,1306 @@ +/* -*- 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 "BackgroundChild.h" +#include "BackgroundParent.h" + +#include "BackgroundChildImpl.h" +#include "BackgroundParentImpl.h" +#include "MainThreadUtils.h" +#include "base/process_util.h" +#include "base/task.h" +#include "FileDescriptor.h" +#include "GeckoProfiler.h" +#include "InputStreamUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/ipc/BackgroundStarterChild.h" +#include "mozilla/ipc/BackgroundStarterParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PBackgroundStarter.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/net/SocketProcessBridgeChild.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISupportsImpl.h" +#include "nsIThread.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" +#include "nsXULAppAPI.h" +#include "nsXPCOMPrivate.h" +#include "prthread.h" + +#include + +#ifdef RELEASE_OR_BETA +# define THREADSAFETY_ASSERT MOZ_ASSERT +#else +# define THREADSAFETY_ASSERT MOZ_RELEASE_ASSERT +#endif + +#define CRASH_IN_CHILD_PROCESS(_msg) \ + do { \ + if (XRE_IsParentProcess()) { \ + MOZ_ASSERT(false, _msg); \ + } else { \ + MOZ_CRASH(_msg); \ + } \ + } while (0) + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; +using namespace mozilla::net; + +namespace { + +class ChildImpl; + +// ----------------------------------------------------------------------------- +// Utility Functions +// ----------------------------------------------------------------------------- + +void AssertIsInMainOrSocketProcess() { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess()); +} + +void AssertIsOnMainThread() { THREADSAFETY_ASSERT(NS_IsMainThread()); } + +// ----------------------------------------------------------------------------- +// ParentImpl Declaration +// ----------------------------------------------------------------------------- + +class ParentImpl final : public BackgroundParentImpl { + friend class ChildImpl; + friend class mozilla::ipc::BackgroundParent; + friend class mozilla::ipc::BackgroundStarterParent; + + private: + class ShutdownObserver; + + struct MOZ_STACK_CLASS TimerCallbackClosure { + nsIThread* mThread; + nsTArray* mLiveActors; + + TimerCallbackClosure(nsIThread* aThread, + nsTArray* aLiveActors) + : mThread(aThread), mLiveActors(aLiveActors) { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aThread); + MOZ_ASSERT(aLiveActors); + } + }; + + // The length of time we will wait at shutdown for all actors to clean + // themselves up before forcing them to be destroyed. + static const uint32_t kShutdownTimerDelayMS = 10000; + + // This is only modified on the main thread. It is null if the thread does not + // exist or is shutting down. + static StaticRefPtr sBackgroundThread; + + // This is created and destroyed on the main thread but only modified on the + // background thread. It is specific to each instance of sBackgroundThread. + static nsTArray* sLiveActorsForBackgroundThread; + + // This is only modified on the main thread. + static StaticRefPtr sShutdownTimer; + + // This exists so that that [Assert]IsOnBackgroundThread() can continue to + // work during shutdown. + static Atomic sBackgroundPRThread; + + // Maintains a count of live actors so that the background thread can be shut + // down when it is no longer needed. + // May be incremented on either the background thread (by an existing actor) + // or on the main thread, but must be decremented on the main thread. + static Atomic sLiveActorCount; + + // This is only modified on the main thread. It is true after the shutdown + // observer is registered and is never unset thereafter. + static bool sShutdownObserverRegistered; + + // This is only modified on the main thread. It prevents us from trying to + // create the background thread after application shutdown has started. + static bool sShutdownHasStarted; + + // null if this is a same-process or socket process actor. + const RefPtr mContent; + + // Set when the actor is opened successfully and used to handle shutdown + // hangs. Only touched on the background thread. + nsTArray* mLiveActorArray; + + // Set at construction to indicate whether this parent actor corresponds to a + // child actor in another process or to a child actor from a different thread + // in the same process. + const bool mIsOtherProcessActor; + + // Set after ActorDestroy has been called. Only touched on the background + // thread. + bool mActorDestroyed; + + public: + static bool IsOnBackgroundThread() { + return PR_GetCurrentThread() == sBackgroundPRThread; + } + + static void AssertIsOnBackgroundThread() { + THREADSAFETY_ASSERT(IsOnBackgroundThread()); + } + + // `ParentImpl` instances need to be deleted on the main thread, despite IPC + // controlling them on a background thread. Use `_WITH_DELETE_ON_MAIN_THREAD` + // to force destruction to occur on the desired thread. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(ParentImpl, + override) + + void Destroy(); + + private: + // Forwarded from BackgroundParent. + static bool IsOtherProcessActor(PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static ThreadsafeContentParentHandle* GetContentParentHandle( + PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static uint64_t GetChildID(PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static bool AllocStarter(ContentParent* aContent, + Endpoint&& aEndpoint, + bool aCrossProcess = true); + + static bool CreateBackgroundThread(); + + static void ShutdownBackgroundThread(); + + static void ShutdownTimerCallback(nsITimer* aTimer, void* aClosure); + + // NOTE: ParentImpl could be used in 4 cases below. + // 1. Within the parent process. + // 2. Between parent process and content process. + // 3. Between socket process and content process. + // 4. Between parent process and socket process. + // |aContent| should be not null for case 2. For cases 1, 3 and 4, it's null. + explicit ParentImpl(ThreadsafeContentParentHandle* aContent, + bool aIsOtherProcessActor) + : mContent(aContent), + mLiveActorArray(nullptr), + mIsOtherProcessActor(aIsOtherProcessActor), + mActorDestroyed(false) { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess()); + MOZ_ASSERT_IF(!aIsOtherProcessActor, XRE_IsParentProcess()); + } + + ~ParentImpl() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + } + + void MainThreadActorDestroy(); + + void SetLiveActorArray(nsTArray* aLiveActorArray) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aLiveActorArray); + MOZ_ASSERT(!aLiveActorArray->Contains(this)); + MOZ_ASSERT(!mLiveActorArray); + MOZ_ASSERT(mIsOtherProcessActor); + + mLiveActorArray = aLiveActorArray; + mLiveActorArray->AppendElement(this); + } + + // These methods are only called by IPDL. + virtual void ActorDestroy(ActorDestroyReason aWhy) override; +}; + +// ----------------------------------------------------------------------------- +// ChildImpl Declaration +// ----------------------------------------------------------------------------- + +class ChildImpl final : public BackgroundChildImpl { + friend class mozilla::ipc::BackgroundChild; + friend class mozilla::ipc::BackgroundChildImpl; + friend class mozilla::ipc::BackgroundStarterChild; + + typedef base::ProcessId ProcessId; + + class ShutdownObserver; + + public: + struct ThreadLocalInfo { + ThreadLocalInfo() +#ifdef DEBUG + : mClosed(false) +#endif + { + } + + RefPtr mActor; + UniquePtr mConsumerThreadLocal; +#ifdef DEBUG + bool mClosed; +#endif + }; + + private: + // A thread-local index that is not valid. + static constexpr unsigned int kBadThreadLocalIndex = + static_cast(-1); + + // ThreadInfoWrapper encapsulates ThreadLocalInfo and ThreadLocalIndex and + // also provides some common functions for creating PBackground IPC actor. + class ThreadInfoWrapper final { + friend class ChildImpl; + + public: + using ActorCreateFunc = void (*)(ThreadLocalInfo*, unsigned int, + nsIEventTarget*, ChildImpl**); + + ThreadInfoWrapper() = default; + + void Startup() { + MOZ_ASSERT(mThreadLocalIndex == kBadThreadLocalIndex, + "ThreadInfoWrapper::Startup() called more than once!"); + + PRStatus status = + PR_NewThreadPrivateIndex(&mThreadLocalIndex, ThreadLocalDestructor); + MOZ_RELEASE_ASSERT(status == PR_SUCCESS, + "PR_NewThreadPrivateIndex failed!"); + + MOZ_ASSERT(mThreadLocalIndex != kBadThreadLocalIndex); + } + + void Shutdown() { + if (sShutdownHasStarted) { + MOZ_ASSERT_IF(mThreadLocalIndex != kBadThreadLocalIndex, + !PR_GetThreadPrivate(mThreadLocalIndex)); + return; + } + + if (mThreadLocalIndex == kBadThreadLocalIndex) { + return; + } + + RefPtr starter; + { + auto lock = mStarter.Lock(); + starter = lock->forget(); + } + if (starter) { + CloseStarter(starter); + } + + ThreadLocalInfo* threadLocalInfo; +#ifdef DEBUG + threadLocalInfo = + static_cast(PR_GetThreadPrivate(mThreadLocalIndex)); + MOZ_ASSERT(!threadLocalInfo); +#endif + + threadLocalInfo = mMainThreadInfo; + if (threadLocalInfo) { +#ifdef DEBUG + MOZ_ASSERT(!threadLocalInfo->mClosed); + threadLocalInfo->mClosed = true; +#endif + + ThreadLocalDestructor(threadLocalInfo); + mMainThreadInfo = nullptr; + } + } + + template + void InitStarter(Actor* aActor) { + AssertIsOnMainThread(); + + // Create a pair of endpoints and send them to the other process. + Endpoint parent; + Endpoint child; + MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints( + aActor->OtherPid(), base::GetCurrentProcId(), &parent, &child)); + MOZ_ALWAYS_TRUE(aActor->SendInitBackground(std::move(parent))); + + InitStarter(std::move(child)); + } + + void InitStarter(Endpoint&& aEndpoint) { + AssertIsOnMainThread(); + + base::ProcessId otherPid = aEndpoint.OtherPid(); + + nsCOMPtr taskQueue; + MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( + "PBackgroundStarter Queue", getter_AddRefs(taskQueue))); + + RefPtr starter = + new BackgroundStarterChild(otherPid, taskQueue); + + taskQueue->Dispatch(NS_NewRunnableFunction( + "PBackgroundStarterChild Init", + [starter, endpoint = std::move(aEndpoint)]() mutable { + MOZ_ALWAYS_TRUE(endpoint.Bind(starter)); + })); + + // Swap in the newly initialized `BackgroundStarterChild`, and close the + // previous one if we're replacing an existing PBackgroundStarterChild + // instance. + RefPtr prevStarter; + { + auto lock = mStarter.Lock(); + prevStarter = lock->forget(); + *lock = starter.forget(); + } + if (prevStarter) { + CloseStarter(prevStarter); + } + } + + void CloseForCurrentThread() { + MOZ_ASSERT(!NS_IsMainThread()); + + if (mThreadLocalIndex == kBadThreadLocalIndex) { + return; + } + + auto* threadLocalInfo = + static_cast(PR_GetThreadPrivate(mThreadLocalIndex)); + + if (!threadLocalInfo) { + return; + } + +#ifdef DEBUG + MOZ_ASSERT(!threadLocalInfo->mClosed); + threadLocalInfo->mClosed = true; +#endif + + // Clearing the thread local will synchronously close the actor. + DebugOnly status = + PR_SetThreadPrivate(mThreadLocalIndex, nullptr); + MOZ_ASSERT(status == PR_SUCCESS); + } + + PBackgroundChild* GetOrCreateForCurrentThread() { + // Processes can be told to do final CC's during shutdown even though + // they never finished starting (and thus call this), because they + // hadn't gotten far enough to call Startup() before shutdown began. + if (mThreadLocalIndex == kBadThreadLocalIndex) { + NS_ERROR("BackgroundChild::Startup() was never called"); + return nullptr; + } + if (NS_IsMainThread() && ChildImpl::sShutdownHasStarted) { + return nullptr; + } + + auto* threadLocalInfo = NS_IsMainThread() + ? mMainThreadInfo + : static_cast( + PR_GetThreadPrivate(mThreadLocalIndex)); + + if (!threadLocalInfo) { + auto newInfo = MakeUnique(); + + if (NS_IsMainThread()) { + mMainThreadInfo = newInfo.get(); + } else { + if (PR_SetThreadPrivate(mThreadLocalIndex, newInfo.get()) != + PR_SUCCESS) { + CRASH_IN_CHILD_PROCESS("PR_SetThreadPrivate failed!"); + return nullptr; + } + } + + threadLocalInfo = newInfo.release(); + } + + if (threadLocalInfo->mActor) { + return threadLocalInfo->mActor; + } + + RefPtr starter; + { + auto lock = mStarter.Lock(); + starter = *lock; + } + if (!starter) { + CRASH_IN_CHILD_PROCESS("No BackgroundStarterChild"); + return nullptr; + } + + Endpoint parent; + Endpoint child; + nsresult rv; + rv = PBackground::CreateEndpoints( + starter->mOtherPid, base::GetCurrentProcId(), &parent, &child); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create top level actor!"); + return nullptr; + } + + RefPtr strongActor = new ChildImpl(); + if (!child.Bind(strongActor)) { + CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!"); + return nullptr; + } + strongActor->SetActorAlive(); + threadLocalInfo->mActor = strongActor; + + // Dispatch to the background task queue to create the relevant actor in + // the remote process. + starter->mTaskQueue->Dispatch(NS_NewRunnableFunction( + "PBackground GetOrCreateForCurrentThread", + [starter, endpoint = std::move(parent)]() mutable { + if (!starter->SendInitBackground(std::move(endpoint))) { + NS_WARNING("Failed to create toplevel actor"); + } + })); + return strongActor; + } + + private: + static void CloseStarter(BackgroundStarterChild* aStarter) { + aStarter->mTaskQueue->Dispatch(NS_NewRunnableFunction( + "PBackgroundStarterChild Close", + [starter = RefPtr{aStarter}] { starter->Close(); })); + } + + // This is only modified on the main thread. It is the thread-local index + // that we use to store the BackgroundChild for each thread. + unsigned int mThreadLocalIndex = kBadThreadLocalIndex; + + // On the main thread, we store TLS in this global instead of in + // mThreadLocalIndex. That way, cooperative main threads all share the same + // thread info. + ThreadLocalInfo* mMainThreadInfo = nullptr; + + // The starter which will be used to launch PBackground instances of this + // type. Only modified on the main thread, but may be read by any thread + // wanting to start background actors. + StaticDataMutex> mStarter{"mStarter"}; + }; + + // For PBackground between parent and content process. + static ThreadInfoWrapper sParentAndContentProcessThreadInfo; + + // For PBackground between socket and content process. + static ThreadInfoWrapper sSocketAndContentProcessThreadInfo; + + // For PBackground between socket and parent process. + static ThreadInfoWrapper sSocketAndParentProcessThreadInfo; + + // This is only modified on the main thread. It prevents us from trying to + // create the background thread after application shutdown has started. + static bool sShutdownHasStarted; + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) + nsISerialEventTarget* mOwningEventTarget; +#endif + +#ifdef DEBUG + bool mActorWasAlive; + bool mActorDestroyed; +#endif + + public: + static void Shutdown(); + + void AssertIsOnOwningThread() { + THREADSAFETY_ASSERT(mOwningEventTarget); + +#ifdef RELEASE_OR_BETA + DebugOnly current; +#else + bool current; +#endif + THREADSAFETY_ASSERT( + NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t))); + THREADSAFETY_ASSERT(current); + } + + void AssertActorDestroyed() { + MOZ_ASSERT(mActorDestroyed, "ChildImpl::ActorDestroy not called in time"); + } + + explicit ChildImpl() +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) + : mOwningEventTarget(GetCurrentSerialEventTarget()) +#endif +#ifdef DEBUG + , + mActorWasAlive(false), + mActorDestroyed(false) +#endif + { + AssertIsOnOwningThread(); + } + + void SetActorAlive() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mActorWasAlive); + MOZ_ASSERT(!mActorDestroyed); + +#ifdef DEBUG + mActorWasAlive = true; +#endif + } + + NS_INLINE_DECL_REFCOUNTING(ChildImpl, override) + + private: + // Forwarded from BackgroundChild. + static void Startup(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetForCurrentThread(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateForCurrentThread(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateSocketActorForCurrentThread(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread(); + + static void CloseForCurrentThread(); + + // Forwarded from BackgroundChildImpl. + static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread(); + + // Forwarded from BackgroundChild. + static void InitContentStarter(mozilla::dom::ContentChild* aContent); + + // Forwarded from BackgroundChild. + static void InitSocketStarter(mozilla::net::SocketProcessChild* aSocket); + + // Forwarded from BackgroundChild. + static void InitSocketBridgeStarter( + mozilla::net::SocketProcessBridgeChild* aSocketBridge); + + static void ThreadLocalDestructor(void* aThreadLocal); + + // This class is reference counted. + ~ChildImpl() { MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); } + + // Only called by IPDL. + virtual void ActorDestroy(ActorDestroyReason aWhy) override; +}; + +// ----------------------------------------------------------------------------- +// ParentImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ParentImpl::ShutdownObserver final : public nsIObserver { + public: + ShutdownObserver() { AssertIsOnMainThread(); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~ShutdownObserver() { AssertIsOnMainThread(); } +}; + +// ----------------------------------------------------------------------------- +// ChildImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ChildImpl::ShutdownObserver final : public nsIObserver { + public: + ShutdownObserver() { AssertIsOnMainThread(); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~ShutdownObserver() { AssertIsOnMainThread(); } +}; + +} // namespace + +namespace mozilla { +namespace ipc { + +bool IsOnBackgroundThread() { return ParentImpl::IsOnBackgroundThread(); } + +#ifdef DEBUG + +void AssertIsOnBackgroundThread() { ParentImpl::AssertIsOnBackgroundThread(); } + +#endif // DEBUG + +} // namespace ipc +} // namespace mozilla + +// ----------------------------------------------------------------------------- +// BackgroundParent Public Methods +// ----------------------------------------------------------------------------- + +// static +bool BackgroundParent::IsOtherProcessActor( + PBackgroundParent* aBackgroundActor) { + return ParentImpl::IsOtherProcessActor(aBackgroundActor); +} + +// static +ThreadsafeContentParentHandle* BackgroundParent::GetContentParentHandle( + PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetContentParentHandle(aBackgroundActor); +} + +// static +uint64_t BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetChildID(aBackgroundActor); +} + +// static +bool BackgroundParent::AllocStarter( + ContentParent* aContent, Endpoint&& aEndpoint) { + return ParentImpl::AllocStarter(aContent, std::move(aEndpoint)); +} + +// ----------------------------------------------------------------------------- +// BackgroundChild Public Methods +// ----------------------------------------------------------------------------- + +// static +void BackgroundChild::Startup() { ChildImpl::Startup(); } + +// static +PBackgroundChild* BackgroundChild::GetForCurrentThread() { + return ChildImpl::GetForCurrentThread(); +} + +// static +PBackgroundChild* BackgroundChild::GetOrCreateForCurrentThread() { + return ChildImpl::GetOrCreateForCurrentThread(); +} + +// static +PBackgroundChild* BackgroundChild::GetOrCreateSocketActorForCurrentThread() { + return ChildImpl::GetOrCreateSocketActorForCurrentThread(); +} + +// static +PBackgroundChild* +BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread() { + return ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread(); +} + +// static +void BackgroundChild::CloseForCurrentThread() { + ChildImpl::CloseForCurrentThread(); +} + +// static +void BackgroundChild::InitContentStarter(ContentChild* aContent) { + ChildImpl::InitContentStarter(aContent); +} + +// static +void BackgroundChild::InitSocketStarter(net::SocketProcessChild* aSocket) { + ChildImpl::InitSocketStarter(aSocket); +} + +// static +void BackgroundChild::InitSocketBridgeStarter( + net::SocketProcessBridgeChild* aSocketBridge) { + ChildImpl::InitSocketBridgeStarter(aSocketBridge); +} + +// ----------------------------------------------------------------------------- +// BackgroundChildImpl Public Methods +// ----------------------------------------------------------------------------- + +// static +BackgroundChildImpl::ThreadLocal* +BackgroundChildImpl::GetThreadLocalForCurrentThread() { + return ChildImpl::GetThreadLocalForCurrentThread(); +} + +// ----------------------------------------------------------------------------- +// ParentImpl Static Members +// ----------------------------------------------------------------------------- + +StaticRefPtr ParentImpl::sBackgroundThread; + +nsTArray* ParentImpl::sLiveActorsForBackgroundThread; + +StaticRefPtr ParentImpl::sShutdownTimer; + +Atomic ParentImpl::sBackgroundPRThread; + +Atomic ParentImpl::sLiveActorCount; + +bool ParentImpl::sShutdownObserverRegistered = false; + +bool ParentImpl::sShutdownHasStarted = false; + +// ----------------------------------------------------------------------------- +// ChildImpl Static Members +// ----------------------------------------------------------------------------- + +ChildImpl::ThreadInfoWrapper ChildImpl::sParentAndContentProcessThreadInfo; + +ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndContentProcessThreadInfo; + +ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndParentProcessThreadInfo; + +bool ChildImpl::sShutdownHasStarted = false; + +// ----------------------------------------------------------------------------- +// ParentImpl Implementation +// ----------------------------------------------------------------------------- + +// static +bool ParentImpl::IsOtherProcessActor(PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + return static_cast(aBackgroundActor)->mIsOtherProcessActor; +} + +// static +ThreadsafeContentParentHandle* ParentImpl::GetContentParentHandle( + PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + return static_cast(aBackgroundActor)->mContent.get(); +} + +// static +uint64_t ParentImpl::GetChildID(PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + auto actor = static_cast(aBackgroundActor); + if (actor->mContent) { + return actor->mContent->ChildID(); + } + + return 0; +} + +// static +bool ParentImpl::AllocStarter(ContentParent* aContent, + Endpoint&& aEndpoint, + bool aCrossProcess) { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + + MOZ_ASSERT(aEndpoint.IsValid()); + + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return false; + } + + sLiveActorCount++; + + RefPtr actor = new BackgroundStarterParent( + aContent ? aContent->ThreadsafeHandle() : nullptr, aCrossProcess); + + if (NS_FAILED(sBackgroundThread->Dispatch(NS_NewRunnableFunction( + "BackgroundStarterParent::ConnectActorRunnable", + [actor = std::move(actor), endpoint = std::move(aEndpoint), + liveActorArray = sLiveActorsForBackgroundThread]() mutable { + MOZ_ASSERT(endpoint.IsValid()); + MOZ_ALWAYS_TRUE(endpoint.Bind(actor)); + actor->SetLiveActorArray(liveActorArray); + })))) { + NS_WARNING("Failed to dispatch connect runnable!"); + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + } + + return true; +} + +// static +bool ParentImpl::CreateBackgroundThread() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(!sBackgroundThread); + MOZ_ASSERT(!sLiveActorsForBackgroundThread); + + if (sShutdownHasStarted) { + NS_WARNING( + "Trying to create background thread after shutdown has " + "already begun!"); + return false; + } + + nsCOMPtr newShutdownTimer; + + if (!sShutdownTimer) { + newShutdownTimer = NS_NewTimer(); + if (!newShutdownTimer) { + return false; + } + } + + if (!sShutdownObserverRegistered) { + nsCOMPtr obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return false; + } + + nsCOMPtr observer = new ShutdownObserver(); + + nsresult rv = obs->AddObserver( + observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + sShutdownObserverRegistered = true; + } + + nsCOMPtr thread; + if (NS_FAILED(NS_NewNamedThread( + "IPDL Background", getter_AddRefs(thread), + NS_NewRunnableFunction( + "Background::ParentImpl::CreateBackgroundThreadRunnable", []() { + DebugOnly oldBackgroundThread = + sBackgroundPRThread.exchange(PR_GetCurrentThread()); + + MOZ_ASSERT_IF(oldBackgroundThread, + PR_GetCurrentThread() != oldBackgroundThread); + })))) { + NS_WARNING("NS_NewNamedThread failed!"); + return false; + } + + sBackgroundThread = thread.forget(); + + sLiveActorsForBackgroundThread = new nsTArray(1); + + if (!sShutdownTimer) { + MOZ_ASSERT(newShutdownTimer); + sShutdownTimer = newShutdownTimer; + } + + return true; +} + +// static +void ParentImpl::ShutdownBackgroundThread() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(sShutdownHasStarted); + MOZ_ASSERT_IF(!sBackgroundThread, !sLiveActorCount); + MOZ_ASSERT_IF(sBackgroundThread, sShutdownTimer); + + nsCOMPtr shutdownTimer = sShutdownTimer.get(); + sShutdownTimer = nullptr; + + if (sBackgroundThread) { + nsCOMPtr thread = sBackgroundThread.get(); + sBackgroundThread = nullptr; + + UniquePtr> liveActors( + sLiveActorsForBackgroundThread); + sLiveActorsForBackgroundThread = nullptr; + + MOZ_ASSERT_IF(!sShutdownHasStarted, !sLiveActorCount); + + if (sLiveActorCount) { + // We need to spin the event loop while we wait for all the actors to be + // cleaned up. We also set a timeout to force-kill any hanging actors. + TimerCallbackClosure closure(thread, liveActors.get()); + + MOZ_ALWAYS_SUCCEEDS(shutdownTimer->InitWithNamedFuncCallback( + &ShutdownTimerCallback, &closure, kShutdownTimerDelayMS, + nsITimer::TYPE_ONE_SHOT, "ParentImpl::ShutdownTimerCallback")); + + SpinEventLoopUntil("ParentImpl::ShutdownBackgroundThread"_ns, + [&]() { return !sLiveActorCount; }); + + MOZ_ASSERT(liveActors->IsEmpty()); + + MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel()); + } + + // Dispatch this runnable to unregister the PR thread from the profiler. + MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(NS_NewRunnableFunction( + "Background::ParentImpl::ShutdownBackgroundThreadRunnable", []() { + // It is possible that another background thread was created while + // this thread was shutting down. In that case we can't assert + // anything about sBackgroundPRThread and we should not modify it + // here. + sBackgroundPRThread.compareExchange(PR_GetCurrentThread(), nullptr); + }))); + + MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); + } +} + +// static +void ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(sShutdownHasStarted); + MOZ_ASSERT(sLiveActorCount); + + auto closure = static_cast(aClosure); + MOZ_ASSERT(closure); + + // Don't let the stack unwind until the ForceCloseBackgroundActorsRunnable has + // finished. + sLiveActorCount++; + + InvokeAsync( + closure->mThread, __func__, + [liveActors = closure->mLiveActors]() { + MOZ_ASSERT(liveActors); + + if (!liveActors->IsEmpty()) { + // Copy the array since calling Close() could mutate the + // actual array. + nsTArray actorsToClose(liveActors->Clone()); + for (IToplevelProtocol* actor : actorsToClose) { + actor->Close(); + } + } + return GenericPromise::CreateAndResolve(true, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, []() { + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + }); +} + +void ParentImpl::Destroy() { + // May be called on any thread! + + AssertIsInMainOrSocketProcess(); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NewNonOwningRunnableMethod("ParentImpl::MainThreadActorDestroy", this, + &ParentImpl::MainThreadActorDestroy))); +} + +void ParentImpl::MainThreadActorDestroy() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent); + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + + // This may be the last reference! + Release(); +} + +void ParentImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mActorDestroyed); + MOZ_ASSERT_IF(mIsOtherProcessActor, mLiveActorArray); + + BackgroundParentImpl::ActorDestroy(aWhy); + + mActorDestroyed = true; + + if (mLiveActorArray) { + MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this)); + mLiveActorArray = nullptr; + } + + // This is tricky. We should be able to call Destroy() here directly because + // we're not going to touch 'this' or our MessageChannel any longer on this + // thread. Destroy() dispatches the MainThreadActorDestroy runnable and when + // it runs it will destroy 'this' and our associated MessageChannel. However, + // IPDL is about to call MessageChannel::Clear() on this thread! To avoid + // racing with the main thread we must ensure that the MessageChannel lives + // long enough to be cleared in this call stack. + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(NewNonOwningRunnableMethod( + "ParentImpl::Destroy", this, &ParentImpl::Destroy))); +} + +NS_IMPL_ISUPPORTS(ParentImpl::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ParentImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(!sShutdownHasStarted); + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); + + sShutdownHasStarted = true; + + // Do this first before calling (and spinning the event loop in) + // ShutdownBackgroundThread(). + ChildImpl::Shutdown(); + + ShutdownBackgroundThread(); + + return NS_OK; +} + +BackgroundStarterParent::BackgroundStarterParent( + ThreadsafeContentParentHandle* aContent, bool aCrossProcess) + : mCrossProcess(aCrossProcess), mContent(aContent) { + AssertIsOnMainThread(); + AssertIsInMainOrSocketProcess(); + MOZ_ASSERT_IF(!mCrossProcess, !mContent); + MOZ_ASSERT_IF(!mCrossProcess, XRE_IsParentProcess()); +} + +void BackgroundStarterParent::SetLiveActorArray( + nsTArray* aLiveActorArray) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aLiveActorArray); + MOZ_ASSERT(!aLiveActorArray->Contains(this)); + MOZ_ASSERT(!mLiveActorArray); + MOZ_ASSERT_IF(!mCrossProcess, OtherPid() == base::GetCurrentProcId()); + + mLiveActorArray = aLiveActorArray; + mLiveActorArray->AppendElement(this); +} + +IPCResult BackgroundStarterParent::RecvInitBackground( + Endpoint&& aEndpoint) { + AssertIsOnBackgroundThread(); + + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, + "Cannot initialize PBackground with invalid endpoint"); + } + + ParentImpl* actor = new ParentImpl(mContent, mCrossProcess); + + // Take a reference on this thread. If Open() fails then we will release this + // reference in Destroy. + NS_ADDREF(actor); + + ParentImpl::sLiveActorCount++; + + if (!aEndpoint.Bind(actor)) { + actor->Destroy(); + return IPC_OK(); + } + + if (mCrossProcess) { + actor->SetLiveActorArray(mLiveActorArray); + } + return IPC_OK(); +} + +void BackgroundStarterParent::ActorDestroy(ActorDestroyReason aReason) { + AssertIsOnBackgroundThread(); + + if (mLiveActorArray) { + MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this)); + mLiveActorArray = nullptr; + } + + // Make sure to decrement `sLiveActorCount` on the main thread. + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NS_NewRunnableFunction("BackgroundStarterParent::MainThreadDestroy", + [] { ParentImpl::sLiveActorCount--; }))); +} + +// ----------------------------------------------------------------------------- +// ChildImpl Implementation +// ----------------------------------------------------------------------------- + +// static +void ChildImpl::Startup() { + // This happens on the main thread but before XPCOM has started so we can't + // assert that we're being called on the main thread here. + + sParentAndContentProcessThreadInfo.Startup(); + sSocketAndContentProcessThreadInfo.Startup(); + sSocketAndParentProcessThreadInfo.Startup(); + + nsCOMPtr observerService = services::GetObserverService(); + MOZ_RELEASE_ASSERT(observerService); + + nsCOMPtr observer = new ShutdownObserver(); + + nsresult rv = observerService->AddObserver( + observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + // Initialize a starter actor to allow starting PBackground within the parent + // process. + if (XRE_IsParentProcess()) { + Endpoint parent; + Endpoint child; + MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints( + base::GetCurrentProcId(), base::GetCurrentProcId(), &parent, &child)); + + MOZ_ALWAYS_TRUE(ParentImpl::AllocStarter(nullptr, std::move(parent), + /* aCrossProcess */ false)); + sParentAndContentProcessThreadInfo.InitStarter(std::move(child)); + } +} + +// static +void ChildImpl::Shutdown() { + AssertIsOnMainThread(); + + sParentAndContentProcessThreadInfo.Shutdown(); + sSocketAndContentProcessThreadInfo.Shutdown(); + sSocketAndParentProcessThreadInfo.Shutdown(); + + sShutdownHasStarted = true; +} + +// static +PBackgroundChild* ChildImpl::GetForCurrentThread() { + MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex != + kBadThreadLocalIndex); + + auto threadLocalInfo = + NS_IsMainThread() + ? sParentAndContentProcessThreadInfo.mMainThreadInfo + : static_cast(PR_GetThreadPrivate( + sParentAndContentProcessThreadInfo.mThreadLocalIndex)); + + if (!threadLocalInfo) { + return nullptr; + } + + return threadLocalInfo->mActor; +} + +/* static */ +PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread() { + return sParentAndContentProcessThreadInfo.GetOrCreateForCurrentThread(); +} + +/* static */ +PBackgroundChild* ChildImpl::GetOrCreateSocketActorForCurrentThread() { + return sSocketAndContentProcessThreadInfo.GetOrCreateForCurrentThread(); +} + +/* static */ +PBackgroundChild* +ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread() { + return sSocketAndParentProcessThreadInfo.GetOrCreateForCurrentThread(); +} + +// static +void ChildImpl::CloseForCurrentThread() { + MOZ_ASSERT(!NS_IsMainThread(), + "PBackground for the main thread should be shut down via " + "ChildImpl::Shutdown()."); + + sParentAndContentProcessThreadInfo.CloseForCurrentThread(); + sSocketAndContentProcessThreadInfo.CloseForCurrentThread(); + sSocketAndParentProcessThreadInfo.CloseForCurrentThread(); +} + +// static +BackgroundChildImpl::ThreadLocal* ChildImpl::GetThreadLocalForCurrentThread() { + MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex != + kBadThreadLocalIndex, + "BackgroundChild::Startup() was never called!"); + + auto threadLocalInfo = + NS_IsMainThread() + ? sParentAndContentProcessThreadInfo.mMainThreadInfo + : static_cast(PR_GetThreadPrivate( + sParentAndContentProcessThreadInfo.mThreadLocalIndex)); + + if (!threadLocalInfo) { + return nullptr; + } + + if (!threadLocalInfo->mConsumerThreadLocal) { + threadLocalInfo->mConsumerThreadLocal = + MakeUnique(); + } + + return threadLocalInfo->mConsumerThreadLocal.get(); +} + +// static +void ChildImpl::InitContentStarter(mozilla::dom::ContentChild* aContent) { + sParentAndContentProcessThreadInfo.InitStarter(aContent); +} + +// static +void ChildImpl::InitSocketStarter(mozilla::net::SocketProcessChild* aSocket) { + sSocketAndParentProcessThreadInfo.InitStarter(aSocket); +} + +// static +void ChildImpl::InitSocketBridgeStarter( + mozilla::net::SocketProcessBridgeChild* aSocketBridge) { + sSocketAndContentProcessThreadInfo.InitStarter(aSocketBridge); +} + +// static +void ChildImpl::ThreadLocalDestructor(void* aThreadLocal) { + auto threadLocalInfo = static_cast(aThreadLocal); + + if (threadLocalInfo) { + MOZ_ASSERT(threadLocalInfo->mClosed); + + if (threadLocalInfo->mActor) { + threadLocalInfo->mActor->Close(); + threadLocalInfo->mActor->AssertActorDestroyed(); + } + + delete threadLocalInfo; + } +} + +void ChildImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + +#ifdef DEBUG + MOZ_ASSERT(!mActorDestroyed); + mActorDestroyed = true; +#endif + + BackgroundChildImpl::ActorDestroy(aWhy); +} + +NS_IMPL_ISUPPORTS(ChildImpl::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ChildImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + AssertIsOnMainThread(); + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); + + ChildImpl::Shutdown(); + + return NS_OK; +} -- cgit v1.2.3