RemoteWorkerService.cpp (15171B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "RemoteWorkerService.h" 8 9 #include "RemoteWorkerController.h" 10 #include "RemoteWorkerDebuggerManagerChild.h" 11 #include "RemoteWorkerDebuggerManagerParent.h" 12 #include "RemoteWorkerServiceChild.h" 13 #include "RemoteWorkerServiceParent.h" 14 #include "mozilla/SchedulerGroup.h" 15 #include "mozilla/Services.h" 16 #include "mozilla/SpinEventLoopUntil.h" 17 #include "mozilla/StaticMutex.h" 18 #include "mozilla/StaticPtr.h" 19 #include "mozilla/dom/PRemoteWorkerDebuggerParent.h" 20 #include "mozilla/dom/PRemoteWorkerParent.h" 21 #include "mozilla/ipc/BackgroundChild.h" 22 #include "mozilla/ipc/BackgroundParent.h" 23 #include "mozilla/ipc/PBackgroundChild.h" 24 #include "mozilla/ipc/PBackgroundParent.h" 25 #include "nsIObserverService.h" 26 #include "nsIThread.h" 27 #include "nsThreadUtils.h" 28 #include "nsXPCOMPrivate.h" 29 30 namespace mozilla { 31 32 using namespace ipc; 33 34 namespace dom { 35 36 namespace { 37 38 StaticMutex sRemoteWorkerServiceMutex; 39 StaticRefPtr<RemoteWorkerService> sRemoteWorkerService; 40 41 } // namespace 42 43 /** 44 * Block shutdown until the RemoteWorkers have shutdown so that we do not try 45 * and shutdown the RemoteWorkerService "Worker Launcher" thread until they have 46 * cleanly shutdown. 47 * 48 * Note that this shutdown blocker is not used to initiate shutdown of any of 49 * the workers directly; their shutdown is initiated from PBackground in the 50 * parent process. The shutdown blocker just exists to avoid races around 51 * shutting down the worker launcher thread after all of the workers have 52 * shutdown and torn down their actors. 53 * 54 * Currently, it should be the case that the ContentParent should want to keep 55 * the content processes alive until the RemoteWorkers have all reported their 56 * shutdown over IPC (on the "Worker Launcher" thread). So for an orderly 57 * content process shutdown that is waiting for there to no longer be a reason 58 * to keep the content process alive, this blocker should only hang around for 59 * a brief period of time, helping smooth out lifecycle edge cases. 60 * 61 * In the event the content process is trying to shutdown while the 62 * RemoteWorkers think they should still be alive, it's possible that this 63 * blocker could expose the relevant logic error in the parent process if no 64 * attempt is made to shutdown the RemoteWorker. 65 * 66 * ## Major Implementation Note: This is not actually an nsIAsyncShutdownClient 67 * 68 * Until https://bugzilla.mozilla.org/show_bug.cgi?id=1760855 provides us with a 69 * non-JS implementation of nsIAsyncShutdownService, this implementation 70 * actually uses event loop spinning. The patch on 71 * https://bugzilla.mozilla.org/show_bug.cgi?id=1775784 that changed us to use 72 * this hack can be reverted when the time is right. 73 * 74 * Event loop spinning is handled by `RemoteWorkerService::Observe` and it calls 75 * our exposed `ShouldBlockShutdown()` to know when to stop spinning. 76 */ 77 class RemoteWorkerServiceShutdownBlocker final { 78 ~RemoteWorkerServiceShutdownBlocker() = default; 79 80 public: 81 explicit RemoteWorkerServiceShutdownBlocker(RemoteWorkerService* aService) 82 : mService(aService), mBlockShutdown(true) {} 83 84 void RemoteWorkersAllGoneAllowShutdown() { 85 mService->FinishShutdown(); 86 mService = nullptr; 87 88 mBlockShutdown = false; 89 } 90 91 bool ShouldBlockShutdown() { return mBlockShutdown; } 92 93 NS_INLINE_DECL_REFCOUNTING(RemoteWorkerServiceShutdownBlocker); 94 95 RefPtr<RemoteWorkerService> mService; 96 bool mBlockShutdown; 97 }; 98 99 RemoteWorkerServiceKeepAlive::RemoteWorkerServiceKeepAlive( 100 RemoteWorkerServiceShutdownBlocker* aBlocker) 101 : mBlocker(aBlocker) { 102 MOZ_ASSERT(NS_IsMainThread()); 103 } 104 105 RemoteWorkerServiceKeepAlive::~RemoteWorkerServiceKeepAlive() { 106 // Dispatch a runnable to the main thread to tell the Shutdown Blocker to 107 // remove itself and notify the RemoteWorkerService it can finish its 108 // shutdown. We dispatch this to the main thread even if we are already on 109 // the main thread. 110 nsCOMPtr<nsIRunnable> r = 111 NS_NewRunnableFunction(__func__, [blocker = std::move(mBlocker)] { 112 blocker->RemoteWorkersAllGoneAllowShutdown(); 113 }); 114 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); 115 } 116 117 /* static */ 118 void RemoteWorkerService::InitializeParent() { 119 MOZ_ASSERT(NS_IsMainThread()); 120 MOZ_ASSERT(XRE_IsParentProcess()); 121 122 StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); 123 MOZ_ASSERT(!sRemoteWorkerService); 124 125 RefPtr<RemoteWorkerService> service = new RemoteWorkerService(); 126 127 // ## Parent Process Initialization Case 128 // 129 // Otherwise we are in the parent process and were invoked by 130 // nsLayoutStatics::Initialize. We wait until profile-after-change to kick 131 // off the Worker Launcher thread and have it connect to PBackground. This is 132 // an appropriate time for remote worker APIs to come online, especially 133 // because the PRemoteWorkerService mechanism needs processes to eagerly 134 // register themselves with PBackground since the design explicitly intends to 135 // avoid blocking on the main threads. (Disclaimer: Currently, things block 136 // on the main thread.) 137 138 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 139 if (NS_WARN_IF(!obs)) { 140 return; 141 } 142 143 nsresult rv = obs->AddObserver(service, "profile-after-change", false); 144 if (NS_WARN_IF(NS_FAILED(rv))) { 145 return; 146 } 147 148 sRemoteWorkerService = service; 149 } 150 151 /* static */ 152 void RemoteWorkerService::InitializeChild( 153 mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint, 154 mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild> 155 aDebuggerChildEp) { 156 MOZ_ASSERT(NS_IsMainThread()); 157 MOZ_ASSERT(!XRE_IsParentProcess()); 158 159 StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); 160 MOZ_ASSERT(!sRemoteWorkerService); 161 162 RefPtr<RemoteWorkerService> service = new RemoteWorkerService(); 163 164 // ## Content Process Initialization Case 165 // 166 // We are being told to initialize now that we know what our remote type is. 167 // Now is a fine time to call InitializeOnMainThread. 168 169 nsresult rv = service->InitializeOnMainThread(std::move(aEndpoint), 170 std::move(aDebuggerChildEp)); 171 if (NS_WARN_IF(NS_FAILED(rv))) { 172 return; 173 } 174 175 sRemoteWorkerService = service; 176 } 177 178 /* static */ 179 nsIThread* RemoteWorkerService::Thread() { 180 StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); 181 MOZ_ASSERT(sRemoteWorkerService); 182 MOZ_ASSERT(sRemoteWorkerService->mThread); 183 return sRemoteWorkerService->mThread; 184 } 185 186 /* static */ 187 void RemoteWorkerService::RegisterRemoteDebugger( 188 RemoteWorkerDebuggerInfo aDebuggerInfo, 189 mozilla::ipc::Endpoint<PRemoteWorkerDebuggerParent> aDebuggerParentEp) { 190 StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); 191 MOZ_ASSERT(sRemoteWorkerService); 192 MOZ_ASSERT(sRemoteWorkerService->mThread); 193 194 // If we are on WorkerLauncher thread, direcly call 195 // RemoteWorkerDebuggerManager::SendRegister. 196 if (sRemoteWorkerService->mThread->IsOnCurrentThread()) { 197 MOZ_ASSERT(sRemoteWorkerService->mDebuggerManagerChild); 198 (void)sRemoteWorkerService->mDebuggerManagerChild->SendRegister( 199 std::move(aDebuggerInfo), std::move(aDebuggerParentEp)); 200 return; 201 } 202 203 // For top-level workers in parent process, directly call RecvRegister(). 204 if (XRE_IsParentProcess() && NS_IsMainThread()) { 205 MOZ_ASSERT(sRemoteWorkerService->mDebuggerManagerParent); 206 (void)sRemoteWorkerService->mDebuggerManagerParent->RecvRegister( 207 std::move(aDebuggerInfo), std::move(aDebuggerParentEp)); 208 return; 209 } 210 211 // We are on other thread in the case of this is a Child worker. Dispatch this 212 // method to WorkerLauncher thread. 213 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 214 "RemoteWorkerService::RegisterRemoteDebugger", 215 [debuggerInfo = std::move(aDebuggerInfo), 216 debuggerParentEp = std::move(aDebuggerParentEp)]() mutable { 217 RemoteWorkerService::RegisterRemoteDebugger( 218 std::move(debuggerInfo), std::move(debuggerParentEp)); 219 }); 220 (void)NS_WARN_IF( 221 NS_FAILED(sRemoteWorkerService->mThread->Dispatch(r.forget()))); 222 } 223 224 /* static */ 225 already_AddRefed<RemoteWorkerServiceKeepAlive> 226 RemoteWorkerService::MaybeGetKeepAlive() { 227 StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); 228 // In normal operation no one should be calling this without a service 229 // existing, so assert, but we'll also handle this being null as it is a 230 // plausible shutdown race. 231 MOZ_ASSERT(sRemoteWorkerService); 232 if (!sRemoteWorkerService) { 233 return nullptr; 234 } 235 236 // Note that this value can be null, but this all handles that. 237 auto lockedKeepAlive = sRemoteWorkerService->mKeepAlive.Lock(); 238 RefPtr<RemoteWorkerServiceKeepAlive> extraRef = *lockedKeepAlive; 239 return extraRef.forget(); 240 } 241 242 nsresult RemoteWorkerService::InitializeOnMainThread( 243 mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint, 244 mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild> 245 aDebuggerChildEp) { 246 // I would like to call this thread "DOM Remote Worker Launcher", but the max 247 // length is 16 chars. 248 nsresult rv = NS_NewNamedThread("Worker Launcher", getter_AddRefs(mThread)); 249 if (NS_WARN_IF(NS_FAILED(rv))) { 250 return rv; 251 } 252 253 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 254 if (NS_WARN_IF(!obs)) { 255 return NS_ERROR_FAILURE; 256 } 257 258 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 259 if (NS_WARN_IF(NS_FAILED(rv))) { 260 return rv; 261 } 262 263 mShutdownBlocker = new RemoteWorkerServiceShutdownBlocker(this); 264 265 { 266 RefPtr<RemoteWorkerServiceKeepAlive> keepAlive = 267 new RemoteWorkerServiceKeepAlive(mShutdownBlocker); 268 269 auto lockedKeepAlive = mKeepAlive.Lock(); 270 *lockedKeepAlive = std::move(keepAlive); 271 } 272 273 RefPtr<RemoteWorkerService> self = this; 274 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( 275 "InitializeThread", 276 [self, endpoint = std::move(aEndpoint), 277 debuggerChildEp = std::move(aDebuggerChildEp)]() mutable { 278 self->InitializeOnTargetThread(std::move(endpoint), 279 std::move(debuggerChildEp)); 280 }); 281 282 rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); 283 if (NS_WARN_IF(NS_FAILED(rv))) { 284 return rv; 285 } 286 287 return NS_OK; 288 } 289 290 RemoteWorkerService::RemoteWorkerService() 291 : mKeepAlive(nullptr, "RemoteWorkerService::mKeepAlive") { 292 MOZ_ASSERT(NS_IsMainThread()); 293 } 294 295 RemoteWorkerService::~RemoteWorkerService() = default; 296 297 void RemoteWorkerService::InitializeOnTargetThread( 298 mozilla::ipc::Endpoint<PRemoteWorkerServiceChild> aEndpoint, 299 mozilla::ipc::Endpoint<PRemoteWorkerDebuggerManagerChild> 300 aDebuggerMgrEndpoint) { 301 MOZ_ASSERT(mThread); 302 MOZ_ASSERT(mThread->IsOnCurrentThread()); 303 304 RefPtr<RemoteWorkerDebuggerManagerChild> debuggerManagerActor = 305 MakeRefPtr<RemoteWorkerDebuggerManagerChild>(); 306 if (NS_WARN_IF(!aDebuggerMgrEndpoint.Bind(debuggerManagerActor))) { 307 return; 308 } 309 310 RefPtr<RemoteWorkerServiceChild> serviceActor = 311 MakeAndAddRef<RemoteWorkerServiceChild>(); 312 if (NS_WARN_IF(!aEndpoint.Bind(serviceActor))) { 313 return; 314 } 315 316 mDebuggerManagerChild = std::move(debuggerManagerActor); 317 mActor = std::move(serviceActor); 318 } 319 320 void RemoteWorkerService::CloseActorOnTargetThread() { 321 MOZ_ASSERT(mThread); 322 MOZ_ASSERT(mThread->IsOnCurrentThread()); 323 324 // If mActor is nullptr it means that initialization failed. 325 if (mActor) { 326 // Here we need to shutdown the IPC protocol. 327 mActor->Close(); 328 mActor = nullptr; 329 } 330 if (mDebuggerManagerChild) { 331 mDebuggerManagerChild->Close(); 332 mDebuggerManagerChild = nullptr; 333 } 334 } 335 336 NS_IMETHODIMP 337 RemoteWorkerService::Observe(nsISupports* aSubject, const char* aTopic, 338 const char16_t* aData) { 339 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 340 MOZ_ASSERT(mThread); 341 342 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 343 if (obs) { 344 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); 345 } 346 347 // Note that nsObserverList::NotifyObservers will hold a strong reference to 348 // our instance throughout the entire duration of this call, so it is not 349 // necessary for us to hold a kungFuDeathGrip here. 350 351 // Drop our keep-alive. This could immediately result in our blocker saying 352 // it's okay for us to shutdown. SpinEventLoopUntil checks the predicate 353 // before spinning, so in the ideal case we will not spin the loop at all. 354 BeginShutdown(); 355 356 MOZ_ALWAYS_TRUE(SpinEventLoopUntil( 357 "RemoteWorkerService::Observe"_ns, 358 [&]() { return !mShutdownBlocker->ShouldBlockShutdown(); })); 359 360 mShutdownBlocker = nullptr; 361 362 return NS_OK; 363 } 364 365 MOZ_ASSERT(!strcmp(aTopic, "profile-after-change")); 366 MOZ_ASSERT(XRE_IsParentProcess()); 367 368 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 369 if (obs) { 370 obs->RemoveObserver(this, "profile-after-change"); 371 } 372 373 Endpoint<PRemoteWorkerServiceChild> childEp; 374 RefPtr<RemoteWorkerServiceParent> parentActor = 375 RemoteWorkerServiceParent::CreateForProcess(nullptr, &childEp); 376 NS_ENSURE_TRUE(parentActor, NS_ERROR_FAILURE); 377 378 Endpoint<PRemoteWorkerDebuggerManagerChild> debuggerChildEp; 379 mDebuggerManagerParent = 380 RemoteWorkerDebuggerManagerParent::CreateForProcess(&debuggerChildEp); 381 NS_ENSURE_TRUE(mDebuggerManagerParent, NS_ERROR_FAILURE); 382 383 return InitializeOnMainThread(std::move(childEp), std::move(debuggerChildEp)); 384 } 385 386 void RemoteWorkerService::BeginShutdown() { 387 // Drop our keepalive reference which may allow near-immediate removal of the 388 // blocker. 389 auto lockedKeepAlive = mKeepAlive.Lock(); 390 *lockedKeepAlive = nullptr; 391 } 392 393 void RemoteWorkerService::FinishShutdown() { 394 // Clear the singleton before spinning the event loop when shutting down the 395 // thread so that MaybeGetKeepAlive() can assert if there are any late calls 396 // and to better reflect the actual state. 397 // 398 // Our caller, the RemoteWorkerServiceShutdownBlocker, will continue to hold a 399 // strong reference to us until we return from this call, so there are no 400 // lifecycle implications to dropping this reference. 401 { 402 StaticMutexAutoLock lock(sRemoteWorkerServiceMutex); 403 sRemoteWorkerService = nullptr; 404 } 405 406 RefPtr<RemoteWorkerService> self = this; 407 nsCOMPtr<nsIRunnable> r = 408 NS_NewRunnableFunction("RemoteWorkerService::CloseActorOnTargetThread", 409 [self]() { self->CloseActorOnTargetThread(); }); 410 411 mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL); 412 413 // We've posted a shutdown message; now shutdown the thread. This will spin 414 // a nested event loop waiting for the thread to process all pending events 415 // (including the just dispatched CloseActorOnTargetThread which will close 416 // the actor), ensuring to block main thread shutdown long enough to avoid 417 // races. 418 mThread->Shutdown(); 419 mThread = nullptr; 420 } 421 422 NS_IMPL_ISUPPORTS(RemoteWorkerService, nsIObserver) 423 424 } // namespace dom 425 } // namespace mozilla