summaryrefslogtreecommitdiffstats
path: root/dom/workers/remoteworkers/RemoteWorkerService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/remoteworkers/RemoteWorkerService.cpp')
-rw-r--r--dom/workers/remoteworkers/RemoteWorkerService.cpp346
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