1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
|
/* -*- 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 "RemoteWorkerController.h"
#include "RemoteWorkerDebuggerManagerChild.h"
#include "RemoteWorkerDebuggerManagerParent.h"
#include "RemoteWorkerServiceChild.h"
#include "RemoteWorkerServiceParent.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Services.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/PRemoteWorkerDebuggerParent.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 "nsIObserverService.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "nsXPCOMPrivate.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(r.forget()));
}
/* static */
void RemoteWorkerService::InitializeParent() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(XRE_IsParentProcess());
StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
MOZ_ASSERT(!sRemoteWorkerService);
RefPtr<RemoteWorkerService> service = new RemoteWorkerService();
// ## 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 */
void RemoteWorkerService::InitializeChild(
mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint,
mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild>
aDebuggerChildEp) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!XRE_IsParentProcess());
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.
nsresult rv = service->InitializeOnMainThread(std::move(aEndpoint),
std::move(aDebuggerChildEp));
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 */
void RemoteWorkerService::RegisterRemoteDebugger(
RemoteWorkerDebuggerInfo aDebuggerInfo,
mozilla::ipc::Endpoint<PRemoteWorkerDebuggerParent> aDebuggerParentEp) {
StaticMutexAutoLock lock(sRemoteWorkerServiceMutex);
MOZ_ASSERT(sRemoteWorkerService);
MOZ_ASSERT(sRemoteWorkerService->mThread);
// If we are on WorkerLauncher thread, direcly call
// RemoteWorkerDebuggerManager::SendRegister.
if (sRemoteWorkerService->mThread->IsOnCurrentThread()) {
MOZ_ASSERT(sRemoteWorkerService->mDebuggerManagerChild);
(void)sRemoteWorkerService->mDebuggerManagerChild->SendRegister(
std::move(aDebuggerInfo), std::move(aDebuggerParentEp));
return;
}
// For top-level workers in parent process, directly call RecvRegister().
if (XRE_IsParentProcess() && NS_IsMainThread()) {
MOZ_ASSERT(sRemoteWorkerService->mDebuggerManagerParent);
(void)sRemoteWorkerService->mDebuggerManagerParent->RecvRegister(
std::move(aDebuggerInfo), std::move(aDebuggerParentEp));
return;
}
// We are on other thread in the case of this is a Child worker. Dispatch this
// method to WorkerLauncher thread.
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"RemoteWorkerService::RegisterRemoteDebugger",
[debuggerInfo = std::move(aDebuggerInfo),
debuggerParentEp = std::move(aDebuggerParentEp)]() mutable {
RemoteWorkerService::RegisterRemoteDebugger(
std::move(debuggerInfo), std::move(debuggerParentEp));
});
(void)NS_WARN_IF(
NS_FAILED(sRemoteWorkerService->mThread->Dispatch(r.forget())));
}
/* 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(
mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint,
mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild>
aDebuggerChildEp) {
// 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, endpoint = std::move(aEndpoint),
debuggerChildEp = std::move(aDebuggerChildEp)]() mutable {
self->InitializeOnTargetThread(std::move(endpoint),
std::move(debuggerChildEp));
});
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(
mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint,
mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild>
aDebuggerMgrEndpoint) {
MOZ_ASSERT(mThread);
MOZ_ASSERT(mThread->IsOnCurrentThread());
RefPtr<RemoteWorkerDebuggerManagerChild> debuggerManagerActor =
MakeRefPtr<RemoteWorkerDebuggerManagerChild>();
if (NS_WARN_IF(!aDebuggerMgrEndpoint.Bind(debuggerManagerActor))) {
return;
}
RefPtr<RemoteWorkerServiceChild> serviceActor =
MakeAndAddRef<RemoteWorkerServiceChild>();
if (NS_WARN_IF(!aEndpoint.Bind(serviceActor))) {
return;
}
mDebuggerManagerChild = std::move(debuggerManagerActor);
mActor = std::move(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->Close();
mActor = nullptr;
}
if (mDebuggerManagerChild) {
mDebuggerManagerChild->Close();
mDebuggerManagerChild = 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");
}
Endpoint<PRemoteWorkerServiceChild> childEp;
RefPtr<RemoteWorkerServiceParent> parentActor =
RemoteWorkerServiceParent::CreateForProcess(nullptr, &childEp);
NS_ENSURE_TRUE(parentActor, NS_ERROR_FAILURE);
Endpoint<PRemoteWorkerDebuggerManagerChild> debuggerChildEp;
mDebuggerManagerParent =
RemoteWorkerDebuggerManagerParent::CreateForProcess(&debuggerChildEp);
NS_ENSURE_TRUE(mDebuggerManagerParent, NS_ERROR_FAILURE);
return InitializeOnMainThread(std::move(childEp), std::move(debuggerChildEp));
}
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
|