diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/workers/remoteworkers/RemoteWorkerService.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers/remoteworkers/RemoteWorkerService.cpp')
-rw-r--r-- | dom/workers/remoteworkers/RemoteWorkerService.cpp | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/dom/workers/remoteworkers/RemoteWorkerService.cpp b/dom/workers/remoteworkers/RemoteWorkerService.cpp new file mode 100644 index 0000000000..b62c551c22 --- /dev/null +++ b/dom/workers/remoteworkers/RemoteWorkerService.cpp @@ -0,0 +1,346 @@ +/* -*- 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 "RemoteWorkerService.h" + +#include "mozilla/dom/PRemoteWorkerParent.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "nsIObserverService.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nsXPCOMPrivate.h" +#include "RemoteWorkerController.h" +#include "RemoteWorkerServiceChild.h" +#include "RemoteWorkerServiceParent.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +StaticMutex sRemoteWorkerServiceMutex; +StaticRefPtr<RemoteWorkerService> sRemoteWorkerService; + +} // namespace + +/** + * Block shutdown until the RemoteWorkers have shutdown so that we do not try + * and shutdown the RemoteWorkerService "Worker Launcher" thread until they have + * cleanly shutdown. + * + * Note that this shutdown blocker is not used to initiate shutdown of any of + * the workers directly; their shutdown is initiated from PBackground in the + * parent process. The shutdown blocker just exists to avoid races around + * shutting down the worker launcher thread after all of the workers have + * shutdown and torn down their actors. + * + * Currently, it should be the case that the ContentParent should want to keep + * the content processes alive until the RemoteWorkers have all reported their + * shutdown over IPC (on the "Worker Launcher" thread). So for an orderly + * content process shutdown that is waiting for there to no longer be a reason + * to keep the content process alive, this blocker should only hang around for + * a brief period of time, helping smooth out lifecycle edge cases. + * + * In the event the content process is trying to shutdown while the + * RemoteWorkers think they should still be alive, it's possible that this + * blocker could expose the relevant logic error in the parent process if no + * attempt is made to shutdown the RemoteWorker. + * + * ## Major Implementation Note: This is not actually an nsIAsyncShutdownClient + * + * Until https://bugzilla.mozilla.org/show_bug.cgi?id=1760855 provides us with a + * non-JS implementation of nsIAsyncShutdownService, this implementation + * actually uses event loop spinning. The patch on + * https://bugzilla.mozilla.org/show_bug.cgi?id=1775784 that changed us to use + * this hack can be reverted when the time is right. + * + * Event loop spinning is handled by `RemoteWorkerService::Observe` and it calls + * our exposed `ShouldBlockShutdown()` to know when to stop spinning. + */ +class RemoteWorkerServiceShutdownBlocker final { + ~RemoteWorkerServiceShutdownBlocker() = default; + + public: + explicit RemoteWorkerServiceShutdownBlocker(RemoteWorkerService* aService) + : mService(aService), mBlockShutdown(true) {} + + void RemoteWorkersAllGoneAllowShutdown() { + mService->FinishShutdown(); + mService = nullptr; + + mBlockShutdown = false; + } + + bool ShouldBlockShutdown() { return mBlockShutdown; } + + NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceShutdownBlocker); + + RefPtr<RemoteWorkerService> mService; + bool mBlockShutdown; +}; + +RemoteWorkerServiceKeepAlive::RemoteWorkerServiceKeepAlive( + RemoteWorkerServiceShutdownBlocker* aBlocker) + : mBlocker(aBlocker) { + MOZ_ASSERT(NS_IsMainThread()); +} + +RemoteWorkerServiceKeepAlive::~RemoteWorkerServiceKeepAlive() { + // Dispatch a runnable to the main thread to tell the Shutdown Blocker to + // remove itself and notify the RemoteWorkerService it can finish its + // shutdown. We dispatch this to the main thread even if we are already on + // the main thread. + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction(__func__, [blocker = std::move(mBlocker)] { + blocker->RemoteWorkersAllGoneAllowShutdown(); + }); + MOZ_ALWAYS_SUCCEEDS( + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget())); +} + +/* static */ +void RemoteWorkerService::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + MOZ_ASSERT(!sRemoteWorkerService); + + RefPtr<RemoteWorkerService> service = new RemoteWorkerService(); + + // ## Content Process Initialization Case + // + // We are being told to initialize now that we know what our remote type is. + // Now is a fine time to call InitializeOnMainThread. + if (!XRE_IsParentProcess()) { + nsresult rv = service->InitializeOnMainThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + sRemoteWorkerService = service; + return; + } + // ## Parent Process Initialization Case + // + // Otherwise we are in the parent process and were invoked by + // nsLayoutStatics::Initialize. We wait until profile-after-change to kick + // off the Worker Launcher thread and have it connect to PBackground. This is + // an appropriate time for remote worker APIs to come online, especially + // because the PRemoteWorkerService mechanism needs processes to eagerly + // register themselves with PBackground since the design explicitly intends to + // avoid blocking on the main threads. (Disclaimer: Currently, things block + // on the main thread.) + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return; + } + + nsresult rv = obs->AddObserver(service, "profile-after-change", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + sRemoteWorkerService = service; +} + +/* static */ +nsIThread* RemoteWorkerService::Thread() { + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + MOZ_ASSERT(sRemoteWorkerService); + MOZ_ASSERT(sRemoteWorkerService->mThread); + return sRemoteWorkerService->mThread; +} + +/* static */ +already_AddRefed<RemoteWorkerServiceKeepAlive> +RemoteWorkerService::MaybeGetKeepAlive() { + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + // In normal operation no one should be calling this without a service + // existing, so assert, but we'll also handle this being null as it is a + // plausible shutdown race. + MOZ_ASSERT(sRemoteWorkerService); + if (!sRemoteWorkerService) { + return nullptr; + } + + // Note that this value can be null, but this all handles that. + auto lockedKeepAlive = sRemoteWorkerService->mKeepAlive.Lock(); + RefPtr<RemoteWorkerServiceKeepAlive> extraRef = *lockedKeepAlive; + return extraRef.forget(); +} + +nsresult RemoteWorkerService::InitializeOnMainThread() { + // I would like to call this thread "DOM Remote Worker Launcher", but the max + // length is 16 chars. + nsresult rv = NS_NewNamedThread("Worker Launcher", getter_AddRefs(mThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return NS_ERROR_FAILURE; + } + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mShutdownBlocker = new RemoteWorkerServiceShutdownBlocker(this); + + { + RefPtr<RemoteWorkerServiceKeepAlive> keepAlive = + new RemoteWorkerServiceKeepAlive(mShutdownBlocker); + + auto lockedKeepAlive = mKeepAlive.Lock(); + *lockedKeepAlive = std::move(keepAlive); + } + + RefPtr<RemoteWorkerService> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "InitializeThread", [self]() { self->InitializeOnTargetThread(); }); + + rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +RemoteWorkerService::RemoteWorkerService() + : mKeepAlive(nullptr, "RemoteWorkerService::mKeepAlive") { + MOZ_ASSERT(NS_IsMainThread()); +} + +RemoteWorkerService::~RemoteWorkerService() = default; + +void RemoteWorkerService::InitializeOnTargetThread() { + MOZ_ASSERT(mThread); + MOZ_ASSERT(mThread->IsOnCurrentThread()); + + PBackgroundChild* backgroundActor = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!backgroundActor)) { + return; + } + + RefPtr<RemoteWorkerServiceChild> serviceActor = + MakeAndAddRef<RemoteWorkerServiceChild>(); + if (NS_WARN_IF(!backgroundActor->SendPRemoteWorkerServiceConstructor( + serviceActor))) { + return; + } + + // Now we are ready! + mActor = serviceActor; +} + +void RemoteWorkerService::CloseActorOnTargetThread() { + MOZ_ASSERT(mThread); + MOZ_ASSERT(mThread->IsOnCurrentThread()); + + // If mActor is nullptr it means that initialization failed. + if (mActor) { + // Here we need to shutdown the IPC protocol. + mActor->Send__delete__(mActor); + mActor = nullptr; + } +} + +NS_IMETHODIMP +RemoteWorkerService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + MOZ_ASSERT(mThread); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + // Note that nsObserverList::NotifyObservers will hold a strong reference to + // our instance throughout the entire duration of this call, so it is not + // necessary for us to hold a kungFuDeathGrip here. + + // Drop our keep-alive. This could immediately result in our blocker saying + // it's okay for us to shutdown. SpinEventLoopUntil checks the predicate + // before spinning, so in the ideal case we will not spin the loop at all. + BeginShutdown(); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "RemoteWorkerService::Observe"_ns, + [&]() { return !mShutdownBlocker->ShouldBlockShutdown(); })); + + mShutdownBlocker = nullptr; + + return NS_OK; + } + + MOZ_ASSERT(!strcmp(aTopic, "profile-after-change")); + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "profile-after-change"); + } + + return InitializeOnMainThread(); +} + +void RemoteWorkerService::BeginShutdown() { + // Drop our keepalive reference which may allow near-immediate removal of the + // blocker. + auto lockedKeepAlive = mKeepAlive.Lock(); + *lockedKeepAlive = nullptr; +} + +void RemoteWorkerService::FinishShutdown() { + // Clear the singleton before spinning the event loop when shutting down the + // thread so that MaybeGetKeepAlive() can assert if there are any late calls + // and to better reflect the actual state. + // + // Our caller, the RemoteWorkerServiceShutdownBlocker, will continue to hold a + // strong reference to us until we return from this call, so there are no + // lifecycle implications to dropping this reference. + { + StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); + sRemoteWorkerService = nullptr; + } + + RefPtr<RemoteWorkerService> self = this; + nsCOMPtr<nsIRunnable> r = + NS_NewRunnableFunction("RemoteWorkerService::CloseActorOnTargetThread", + [self]() { self->CloseActorOnTargetThread(); }); + + mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + + // We've posted a shutdown message; now shutdown the thread. This will spin + // a nested event loop waiting for the thread to process all pending events + // (including the just dispatched CloseActorOnTargetThread which will close + // the actor), ensuring to block main thread shutdown long enough to avoid + // races. + mThread->Shutdown(); + mThread = nullptr; +} + +NS_IMPL_ISUPPORTS(RemoteWorkerService, nsIObserver) + +} // namespace dom +} // namespace mozilla |