WorkerRunnable.cpp (25305B)
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 "WorkerRunnable.h" 8 9 #include "WorkerScope.h" 10 #include "js/RootingAPI.h" 11 #include "jsapi.h" 12 #include "jsfriendapi.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/CycleCollectedJSContext.h" 15 #include "mozilla/DebugOnly.h" 16 #include "mozilla/ErrorResult.h" 17 #include "mozilla/Logging.h" 18 #include "mozilla/Maybe.h" 19 #include "mozilla/TelemetryHistogramEnums.h" 20 #include "mozilla/TimeStamp.h" 21 #include "mozilla/dom/ScriptSettings.h" 22 #include "mozilla/dom/Worker.h" 23 #include "mozilla/dom/WorkerCommon.h" 24 #include "mozilla/glean/DomWorkersMetrics.h" 25 #include "nsDebug.h" 26 #include "nsGlobalWindowInner.h" 27 #include "nsID.h" 28 #include "nsIEventTarget.h" 29 #include "nsIGlobalObject.h" 30 #include "nsIRunnable.h" 31 #include "nsThreadUtils.h" 32 #include "nsWrapperCacheInlines.h" 33 34 namespace mozilla::dom { 35 36 static mozilla::LazyLogModule sWorkerRunnableLog("WorkerRunnable"); 37 38 #ifdef LOG 39 # undef LOG 40 #endif 41 #define LOG(args) MOZ_LOG(sWorkerRunnableLog, LogLevel::Verbose, args); 42 43 namespace { 44 45 const nsIID kWorkerRunnableIID = { 46 0x320cc0b5, 47 0xef12, 48 0x4084, 49 {0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68}}; 50 51 } // namespace 52 53 #ifdef DEBUG 54 WorkerRunnable::WorkerRunnable(const char* aName) 55 # ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 56 : mName(aName) { 57 LOG(("WorkerRunnable::WorkerRunnable [%p] (%s)", this, mName)); 58 } 59 # else 60 { 61 LOG(("WorkerRunnable::WorkerRunnable [%p]", this)); 62 } 63 # endif 64 #endif 65 66 // static 67 WorkerRunnable* WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) { 68 MOZ_ASSERT(aRunnable); 69 70 WorkerRunnable* runnable; 71 nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID, 72 reinterpret_cast<void**>(&runnable)); 73 if (NS_FAILED(rv)) { 74 return nullptr; 75 } 76 77 MOZ_ASSERT(runnable); 78 return runnable; 79 } 80 81 bool WorkerRunnable::Dispatch(WorkerPrivate* aWorkerPrivate) { 82 LOG(("WorkerRunnable::Dispatch [%p] aWorkerPrivate: %p", this, 83 aWorkerPrivate)); 84 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate); 85 bool ok = PreDispatch(aWorkerPrivate); 86 if (ok) { 87 ok = DispatchInternal(aWorkerPrivate); 88 } 89 PostDispatch(aWorkerPrivate, ok); 90 return ok; 91 } 92 93 NS_IMETHODIMP WorkerRunnable::Run() { return NS_OK; } 94 95 NS_IMPL_ADDREF(WorkerRunnable) 96 NS_IMPL_RELEASE(WorkerRunnable) 97 98 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 99 NS_IMETHODIMP 100 WorkerRunnable::GetName(nsACString& aName) { 101 if (mName) { 102 aName.AssignASCII(mName); 103 } else { 104 aName.Truncate(); 105 } 106 return NS_OK; 107 } 108 #endif 109 110 NS_INTERFACE_MAP_BEGIN(WorkerRunnable) 111 NS_INTERFACE_MAP_ENTRY(nsIRunnable) 112 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 113 NS_INTERFACE_MAP_ENTRY(nsINamed) 114 #endif 115 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable) 116 // kWorkerRunnableIID is special in that it does not AddRef its result. 117 if (aIID.Equals(kWorkerRunnableIID)) { 118 *aInstancePtr = this; 119 return NS_OK; 120 } else 121 NS_INTERFACE_MAP_END 122 123 WorkerParentThreadRunnable::WorkerParentThreadRunnable(const char* aName) 124 : WorkerRunnable(aName) { 125 LOG(("WorkerParentThreadRunnable::WorkerParentThreadRunnable [%p]", this)); 126 } 127 128 WorkerParentThreadRunnable::~WorkerParentThreadRunnable() = default; 129 130 bool WorkerParentThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) { 131 #ifdef DEBUG 132 MOZ_ASSERT(aWorkerPrivate); 133 aWorkerPrivate->AssertIsOnWorkerThread(); 134 #endif 135 return true; 136 } 137 138 bool WorkerParentThreadRunnable::DispatchInternal( 139 WorkerPrivate* aWorkerPrivate) { 140 LOG(("WorkerParentThreadRunnable::DispatchInternal [%p]", this)); 141 mWorkerParentRef = aWorkerPrivate->GetWorkerParentRef(); 142 RefPtr<WorkerParentThreadRunnable> runnable(this); 143 return NS_SUCCEEDED(aWorkerPrivate->DispatchToParent(runnable.forget())); 144 } 145 146 void WorkerParentThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate, 147 bool aDispatchResult) { 148 #ifdef DEBUG 149 MOZ_ASSERT(aWorkerPrivate); 150 aWorkerPrivate->AssertIsOnWorkerThread(); 151 #endif 152 } 153 154 bool WorkerParentThreadRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { 155 return true; 156 } 157 158 void WorkerParentThreadRunnable::PostRun(JSContext* aCx, 159 WorkerPrivate* aWorkerPrivate, 160 bool aRunResult) { 161 MOZ_ASSERT(aCx); 162 #ifdef DEBUG 163 MOZ_ASSERT(aWorkerPrivate); 164 aWorkerPrivate->AssertIsOnParentThread(); 165 #endif 166 } 167 168 NS_IMETHODIMP 169 WorkerParentThreadRunnable::Run() { 170 LOG(("WorkerParentThreadRunnable::Run [%p]", this)); 171 RefPtr<WorkerPrivate> workerPrivate; 172 MOZ_ASSERT(mWorkerParentRef); 173 workerPrivate = mWorkerParentRef->Private(); 174 if (!workerPrivate) { 175 NS_WARNING("Worker has already shut down!!!"); 176 return NS_OK; 177 } 178 #ifdef DEBUG 179 workerPrivate->AssertIsOnParentThread(); 180 #endif 181 182 WorkerPrivate* parent = workerPrivate->GetParent(); 183 bool isOnMainThread = !parent; 184 bool result = PreRun(workerPrivate); 185 MOZ_ASSERT(result); 186 187 LOG(("WorkerParentThreadRunnable::Run [%p] WorkerPrivate: %p, parent: %p", 188 this, workerPrivate.get(), parent)); 189 190 // Track down the appropriate global, if any, to use for the AutoEntryScript. 191 nsCOMPtr<nsIGlobalObject> globalObject; 192 if (isOnMainThread) { 193 MOZ_ASSERT(isOnMainThread == NS_IsMainThread()); 194 globalObject = nsGlobalWindowInner::Cast(workerPrivate->GetWindow()); 195 } else { 196 MOZ_ASSERT(parent == GetCurrentThreadWorkerPrivate()); 197 globalObject = parent->GlobalScope(); 198 MOZ_DIAGNOSTIC_ASSERT(globalObject); 199 } 200 // We might run script as part of WorkerRun, so we need an AutoEntryScript. 201 // This is part of the HTML spec for workers at: 202 // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker 203 // If we don't have a globalObject we have to use an AutoJSAPI instead, but 204 // this is OK as we won't be running script in these circumstances. 205 Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI; 206 Maybe<mozilla::dom::AutoEntryScript> aes; 207 JSContext* cx; 208 AutoJSAPI* jsapi; 209 210 if (globalObject) { 211 aes.emplace(globalObject, "Worker parent thread runnable", isOnMainThread); 212 jsapi = aes.ptr(); 213 cx = aes->cx(); 214 } else { 215 maybeJSAPI.emplace(); 216 maybeJSAPI->Init(); 217 jsapi = maybeJSAPI.ptr(); 218 cx = jsapi->cx(); 219 } 220 221 // Note that we can't assert anything about 222 // workerPrivate->ParentEventTargetRef()->GetWrapper() 223 // existing, since it may in fact have been GCed (and we may be one of the 224 // runnables cleaning up the worker as a result). 225 226 // If we are on the parent thread and that thread is not the main thread, 227 // then we must be a dedicated worker (because there are no 228 // Shared/ServiceWorkers whose parent is itself a worker) and then we 229 // definitely have a globalObject. If it _is_ the main thread, globalObject 230 // can be null for workers started from JSMs or other non-window contexts, 231 // sadly. 232 MOZ_ASSERT_IF(!isOnMainThread, 233 workerPrivate->IsDedicatedWorker() && globalObject); 234 235 // If we're on the parent thread we might be in a null realm in the 236 // situation described above when globalObject is null. Make sure to enter 237 // the realm of the worker's reflector if there is one. There might 238 // not be one if we're just starting to compile the script for this worker. 239 Maybe<JSAutoRealm> ar; 240 if (workerPrivate->IsDedicatedWorker() && 241 workerPrivate->ParentEventTargetRef() && 242 workerPrivate->ParentEventTargetRef()->GetWrapper()) { 243 JSObject* wrapper = workerPrivate->ParentEventTargetRef()->GetWrapper(); 244 245 // If we're on the parent thread and have a reflector and a globalObject, 246 // then the realms of cx, globalObject, and the worker's reflector 247 // should all match. 248 MOZ_ASSERT_IF(globalObject, 249 js::GetNonCCWObjectRealm(wrapper) == js::GetContextRealm(cx)); 250 MOZ_ASSERT_IF(globalObject, 251 js::GetNonCCWObjectRealm(wrapper) == 252 js::GetNonCCWObjectRealm( 253 globalObject->GetGlobalJSObjectPreserveColor())); 254 255 // If we're on the parent thread and have a reflector, then our 256 // JSContext had better be either in the null realm (and hence 257 // have no globalObject) or in the realm of our reflector. 258 MOZ_ASSERT(!js::GetContextRealm(cx) || 259 js::GetNonCCWObjectRealm(wrapper) == js::GetContextRealm(cx), 260 "Must either be in the null compartment or in our reflector " 261 "compartment"); 262 263 ar.emplace(cx, wrapper); 264 } 265 266 MOZ_ASSERT(!jsapi->HasException()); 267 result = WorkerRun(cx, workerPrivate); 268 jsapi->ReportException(); 269 270 // It would be nice to avoid passing a JSContext to PostRun, but in the case 271 // of ScriptExecutorRunnable we need to know the current compartment on the 272 // JSContext (the one we set up based on the global returned from PreRun) so 273 // that we can sanely do exception reporting. In particular, we want to make 274 // sure that we do our JS_SetPendingException while still in that compartment, 275 // because otherwise we might end up trying to create a cross-compartment 276 // wrapper when we try to move the JS exception from our runnable's 277 // ErrorResult to the JSContext, and that's not desirable in this case. 278 // 279 // We _could_ skip passing a JSContext here and then in 280 // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate 281 // and looking at its current compartment. But that seems like slightly weird 282 // action-at-a-distance... 283 // 284 // In any case, we do NOT try to change the compartment on the JSContext at 285 // this point; in the one case in which we could do that 286 // (CompileScriptRunnable) it actually doesn't matter which compartment we're 287 // in for PostRun. 288 PostRun(cx, workerPrivate, result); 289 MOZ_ASSERT(!jsapi->HasException()); 290 291 return result ? NS_OK : NS_ERROR_FAILURE; 292 } 293 294 nsresult WorkerParentThreadRunnable::Cancel() { 295 LOG(("WorkerParentThreadRunnable::Cancel [%p]", this)); 296 return NS_OK; 297 } 298 299 WorkerParentControlRunnable::WorkerParentControlRunnable(const char* aName) 300 : WorkerParentThreadRunnable(aName) {} 301 302 WorkerParentControlRunnable::~WorkerParentControlRunnable() = default; 303 304 nsresult WorkerParentControlRunnable::Cancel() { 305 LOG(("WorkerParentControlRunnable::Cancel [%p]", this)); 306 if (NS_FAILED(Run())) { 307 NS_WARNING("WorkerParentControlRunnable::Run() failed."); 308 } 309 return NS_OK; 310 } 311 312 WorkerThreadRunnable::WorkerThreadRunnable(const char* aName) 313 : WorkerRunnable(aName), mCallingCancelWithinRun(false) { 314 LOG(("WorkerThreadRunnable::WorkerThreadRunnable [%p]", this)); 315 } 316 317 nsIGlobalObject* WorkerThreadRunnable::DefaultGlobalObject( 318 WorkerPrivate* aWorkerPrivate) const { 319 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate); 320 if (IsDebuggerRunnable()) { 321 return aWorkerPrivate->DebuggerGlobalScope(); 322 } 323 return aWorkerPrivate->GlobalScope(); 324 } 325 326 bool WorkerThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) { 327 MOZ_ASSERT(aWorkerPrivate); 328 #ifdef DEBUG 329 aWorkerPrivate->AssertIsOnParentThread(); 330 #endif 331 return true; 332 } 333 334 bool WorkerThreadRunnable::DispatchInternal(WorkerPrivate* aWorkerPrivate) { 335 LOG(("WorkerThreadRunnable::DispatchInternal [%p]", this)); 336 RefPtr<WorkerThreadRunnable> runnable(this); 337 return NS_SUCCEEDED(aWorkerPrivate->Dispatch(runnable.forget())); 338 } 339 340 void WorkerThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate, 341 bool aDispatchResult) { 342 MOZ_ASSERT(aWorkerPrivate); 343 #ifdef DEBUG 344 aWorkerPrivate->AssertIsOnParentThread(); 345 #endif 346 } 347 348 bool WorkerThreadRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { 349 return true; 350 } 351 352 void WorkerThreadRunnable::PostRun(JSContext* aCx, 353 WorkerPrivate* aWorkerPrivate, 354 bool aRunResult) { 355 MOZ_ASSERT(aCx); 356 MOZ_ASSERT(aWorkerPrivate); 357 358 #ifdef DEBUG 359 aWorkerPrivate->AssertIsOnWorkerThread(); 360 #endif 361 } 362 363 NS_IMETHODIMP 364 WorkerThreadRunnable::Run() { 365 LOG(("WorkerThreadRunnable::Run [%p]", this)); 366 367 // The Worker initialization fails, there is no valid WorkerPrivate and 368 // WorkerJSContext to run this WorkerThreadRunnable. 369 if (mCleanPreStartDispatching) { 370 LOG(("Clean the pre-start dispatched WorkerThreadRunnable [%p]", this)); 371 return NS_OK; 372 } 373 374 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 375 MOZ_ASSERT_DEBUG_OR_FUZZING(workerPrivate); 376 #ifdef DEBUG 377 workerPrivate->AssertIsOnWorkerThread(); 378 #endif 379 380 if (!mCallingCancelWithinRun && 381 workerPrivate->CancelBeforeWorkerScopeConstructed()) { 382 mCallingCancelWithinRun = true; 383 Cancel(); 384 mCallingCancelWithinRun = false; 385 return NS_OK; 386 } 387 388 bool result = PreRun(workerPrivate); 389 if (!result) { 390 workerPrivate->AssertIsOnWorkerThread(); 391 MOZ_ASSERT(!JS_IsExceptionPending(workerPrivate->GetJSContext())); 392 // We can't enter a useful realm on the JSContext here; just pass it 393 // in as-is. 394 PostRun(workerPrivate->GetJSContext(), workerPrivate, false); 395 return NS_ERROR_FAILURE; 396 } 397 398 // Track down the appropriate global, if any, to use for the AutoEntryScript. 399 nsCOMPtr<nsIGlobalObject> globalObject = 400 workerPrivate->GetCurrentEventLoopGlobal(); 401 if (!globalObject) { 402 globalObject = DefaultGlobalObject(workerPrivate); 403 // Our worker thread may not be in a good state here if there is no 404 // JSContext avaliable. The way this manifests itself is that 405 // globalObject ends up null (though it's not clear to me how we can be 406 // running runnables at all when default globalObject(DebuggerGlobalScope 407 // for debugger runnable, and GlobalScope for normal runnables) is returning 408 // false!) and then when we try to init the AutoJSAPI either 409 // CycleCollectedJSContext::Get() returns null or it has a null JSContext. 410 // In any case, we used to have a check for 411 // GetCurrentWorkerThreadJSContext() being non-null here and that seems to 412 // avoid the problem, so let's keep doing that check even if we don't need 413 // the JSContext here at all. 414 if (NS_WARN_IF(!globalObject && !GetCurrentWorkerThreadJSContext())) { 415 return NS_ERROR_FAILURE; 416 } 417 } 418 419 // We might run script as part of WorkerRun, so we need an AutoEntryScript. 420 // This is part of the HTML spec for workers at: 421 // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker 422 // If we don't have a globalObject we have to use an AutoJSAPI instead, but 423 // this is OK as we won't be running script in these circumstances. 424 Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI; 425 Maybe<mozilla::dom::AutoEntryScript> aes; 426 JSContext* cx; 427 AutoJSAPI* jsapi; 428 if (globalObject) { 429 aes.emplace(globalObject, "Worker runnable", false); 430 jsapi = aes.ptr(); 431 cx = aes->cx(); 432 } else { 433 maybeJSAPI.emplace(); 434 maybeJSAPI->Init(); 435 jsapi = maybeJSAPI.ptr(); 436 cx = jsapi->cx(); 437 } 438 439 MOZ_ASSERT(!jsapi->HasException()); 440 result = WorkerRun(cx, workerPrivate); 441 jsapi->ReportException(); 442 443 // We can't even assert that this didn't create our global, since in the case 444 // of CompileScriptRunnable it _does_. 445 446 // It would be nice to avoid passing a JSContext to PostRun, but in the case 447 // of ScriptExecutorRunnable we need to know the current compartment on the 448 // JSContext (the one we set up based on the global returned from PreRun) so 449 // that we can sanely do exception reporting. In particular, we want to make 450 // sure that we do our JS_SetPendingException while still in that compartment, 451 // because otherwise we might end up trying to create a cross-compartment 452 // wrapper when we try to move the JS exception from our runnable's 453 // ErrorResult to the JSContext, and that's not desirable in this case. 454 // 455 // We _could_ skip passing a JSContext here and then in 456 // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate 457 // and looking at its current compartment. But that seems like slightly weird 458 // action-at-a-distance... 459 // 460 // In any case, we do NOT try to change the compartment on the JSContext at 461 // this point; in the one case in which we could do that 462 // (CompileScriptRunnable) it actually doesn't matter which compartment we're 463 // in for PostRun. 464 PostRun(cx, workerPrivate, result); 465 MOZ_ASSERT(!jsapi->HasException()); 466 467 return result ? NS_OK : NS_ERROR_FAILURE; 468 } 469 470 nsresult WorkerThreadRunnable::Cancel() { 471 LOG(("WorkerThreadRunnable::Cancel [%p]", this)); 472 return NS_OK; 473 } 474 475 void WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate, 476 bool aDispatchResult) {} 477 478 WorkerSyncRunnable::WorkerSyncRunnable(nsIEventTarget* aSyncLoopTarget, 479 const char* aName) 480 : WorkerThreadRunnable(aName), mSyncLoopTarget(aSyncLoopTarget) {} 481 482 WorkerSyncRunnable::WorkerSyncRunnable( 483 nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget, const char* aName) 484 : WorkerThreadRunnable(aName), 485 mSyncLoopTarget(std::move(aSyncLoopTarget)) {} 486 487 WorkerSyncRunnable::~WorkerSyncRunnable() = default; 488 489 bool WorkerSyncRunnable::DispatchInternal(WorkerPrivate* aWorkerPrivate) { 490 if (mSyncLoopTarget) { 491 #ifdef DEBUG 492 aWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); 493 #endif 494 RefPtr<WorkerSyncRunnable> runnable(this); 495 return NS_SUCCEEDED( 496 mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL)); 497 } 498 499 return WorkerThreadRunnable::DispatchInternal(aWorkerPrivate); 500 } 501 502 void MainThreadWorkerSyncRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate, 503 bool aDispatchResult) {} 504 505 MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable( 506 nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget, nsresult aResult) 507 : WorkerSyncRunnable(std::move(aSyncLoopTarget)), mResult(aResult) { 508 LOG(("MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable [%p]", 509 this)); 510 511 AssertIsOnMainThread(); 512 } 513 514 nsresult MainThreadStopSyncLoopRunnable::Cancel() { 515 LOG(("MainThreadStopSyncLoopRunnable::Cancel [%p]", this)); 516 nsresult rv = Run(); 517 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Run() failed"); 518 519 return rv; 520 } 521 522 bool MainThreadStopSyncLoopRunnable::WorkerRun(JSContext* aCx, 523 WorkerPrivate* aWorkerPrivate) { 524 aWorkerPrivate->AssertIsOnWorkerThread(); 525 MOZ_ASSERT(mSyncLoopTarget); 526 527 nsCOMPtr<nsIEventTarget> syncLoopTarget; 528 mSyncLoopTarget.swap(syncLoopTarget); 529 530 aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult); 531 return true; 532 } 533 534 bool MainThreadStopSyncLoopRunnable::DispatchInternal( 535 WorkerPrivate* aWorkerPrivate) { 536 MOZ_ASSERT(mSyncLoopTarget); 537 #ifdef DEBUG 538 aWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); 539 #endif 540 RefPtr<MainThreadStopSyncLoopRunnable> runnable(this); 541 return NS_SUCCEEDED( 542 mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL)); 543 } 544 545 void MainThreadStopSyncLoopRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate, 546 bool aDispatchResult) {} 547 548 WorkerControlRunnable::WorkerControlRunnable(const char* aName) 549 : WorkerThreadRunnable(aName) {} 550 551 nsresult WorkerControlRunnable::Cancel() { 552 LOG(("WorkerControlRunnable::Cancel [%p]", this)); 553 if (NS_FAILED(Run())) { 554 NS_WARNING("WorkerControlRunnable::Run() failed."); 555 } 556 557 return NS_OK; 558 } 559 560 WorkerMainThreadRunnable::WorkerMainThreadRunnable( 561 WorkerPrivate* aWorkerPrivate, const nsACString& aTelemetryKey, 562 const char* const aName) 563 : mozilla::Runnable("dom::WorkerMainThreadRunnable"), 564 mTelemetryKey(aTelemetryKey), 565 mName(aName) { 566 aWorkerPrivate->AssertIsOnWorkerThread(); 567 } 568 569 WorkerMainThreadRunnable::~WorkerMainThreadRunnable() = default; 570 571 void WorkerMainThreadRunnable::Dispatch(WorkerPrivate* aWorkerPrivate, 572 WorkerStatus aFailStatus, 573 mozilla::ErrorResult& aRv) { 574 aWorkerPrivate->AssertIsOnWorkerThread(); 575 576 TimeStamp startTime = TimeStamp::NowLoRes(); 577 578 RefPtr<StrongWorkerRef> workerRef; 579 if (aFailStatus < Canceling) { 580 // Nothing but logging debugging messages in the WorkerRef's 581 // shutdown callback. 582 // Stopping syncLoop in the shutdown callback could cause memory leaks or 583 // UAF when the main thread job completes. 584 workerRef = 585 StrongWorkerRef::Create(aWorkerPrivate, mName, [self = RefPtr{this}]() { 586 LOG( 587 ("WorkerMainThreadRunnable::Dispatch [%p](%s) Worker starts to " 588 "shutdown while underlying SyncLoop is still running", 589 self.get(), self->mName)); 590 }); 591 } else { 592 LOG( 593 ("WorkerMainThreadRunnable::Dispatch [%p](%s) Creating a SyncLoop when" 594 "the Worker is shutting down", 595 this, mName)); 596 workerRef = StrongWorkerRef::CreateForcibly(aWorkerPrivate, mName); 597 } 598 if (!workerRef) { 599 // WorkerRef creation can fail if the worker is not in a valid status. 600 aRv.ThrowInvalidStateError("The worker has already shut down"); 601 return; 602 } 603 mWorkerRef = MakeRefPtr<ThreadSafeWorkerRef>(workerRef); 604 605 AutoSyncLoopHolder syncLoop(aWorkerPrivate, aFailStatus); 606 607 mSyncLoopTarget = syncLoop.GetSerialEventTarget(); 608 if (!mSyncLoopTarget) { 609 // SyncLoop creation can fail if the worker is shutting down. 610 aRv.ThrowInvalidStateError("The worker is shutting down"); 611 return; 612 } 613 614 DebugOnly<nsresult> rv = aWorkerPrivate->DispatchToMainThread(this); 615 MOZ_ASSERT( 616 NS_SUCCEEDED(rv), 617 "Should only fail after xpcom-shutdown-threads and we're gone by then"); 618 619 bool success = NS_SUCCEEDED(syncLoop.Run()); 620 621 // syncLoop is done, release WorkerRef to unblock shutdown. 622 mWorkerRef = nullptr; 623 624 glean::workers::sync_worker_operation.Get(mTelemetryKey) 625 .AccumulateRawDuration(TimeStamp::NowLoRes() - startTime); 626 627 (void)startTime; // Shut the compiler up. 628 629 if (!success) { 630 aRv.ThrowUncatchableException(); 631 } 632 } 633 634 NS_IMETHODIMP 635 WorkerMainThreadRunnable::Run() { 636 AssertIsOnMainThread(); 637 638 bool runResult = MainThreadRun(); 639 640 RefPtr<MainThreadStopSyncLoopRunnable> response = 641 new MainThreadStopSyncLoopRunnable(std::move(mSyncLoopTarget), 642 runResult ? NS_OK : NS_ERROR_FAILURE); 643 644 MOZ_ASSERT(mWorkerRef); 645 MOZ_ALWAYS_TRUE(response->Dispatch(mWorkerRef->Private())); 646 647 return NS_OK; 648 } 649 650 bool WorkerSameThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) { 651 aWorkerPrivate->AssertIsOnWorkerThread(); 652 return true; 653 } 654 655 void WorkerSameThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate, 656 bool aDispatchResult) { 657 aWorkerPrivate->AssertIsOnWorkerThread(); 658 } 659 660 WorkerProxyToMainThreadRunnable::WorkerProxyToMainThreadRunnable() 661 : mozilla::Runnable("dom::WorkerProxyToMainThreadRunnable") {} 662 663 WorkerProxyToMainThreadRunnable::~WorkerProxyToMainThreadRunnable() = default; 664 665 bool WorkerProxyToMainThreadRunnable::Dispatch(WorkerPrivate* aWorkerPrivate) { 666 MOZ_ASSERT(aWorkerPrivate); 667 aWorkerPrivate->AssertIsOnWorkerThread(); 668 669 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( 670 aWorkerPrivate, "WorkerProxyToMainThreadRunnable"); 671 if (NS_WARN_IF(!workerRef)) { 672 RunBackOnWorkerThreadForCleanup(aWorkerPrivate); 673 return false; 674 } 675 676 MOZ_ASSERT(!mWorkerRef); 677 mWorkerRef = new ThreadSafeWorkerRef(workerRef); 678 679 if (ForMessaging() 680 ? NS_WARN_IF(NS_FAILED( 681 aWorkerPrivate->DispatchToMainThreadForMessaging(this))) 682 : NS_WARN_IF(NS_FAILED(aWorkerPrivate->DispatchToMainThread(this)))) { 683 ReleaseWorker(); 684 RunBackOnWorkerThreadForCleanup(aWorkerPrivate); 685 return false; 686 } 687 688 return true; 689 } 690 691 NS_IMETHODIMP 692 WorkerProxyToMainThreadRunnable::Run() { 693 AssertIsOnMainThread(); 694 RunOnMainThread(mWorkerRef->Private()); 695 PostDispatchOnMainThread(); 696 return NS_OK; 697 } 698 699 void WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread() { 700 class ReleaseRunnable final : public MainThreadWorkerControlRunnable { 701 RefPtr<WorkerProxyToMainThreadRunnable> mRunnable; 702 703 public: 704 explicit ReleaseRunnable(WorkerProxyToMainThreadRunnable* aRunnable) 705 : MainThreadWorkerControlRunnable("ReleaseRunnable"), 706 mRunnable(aRunnable) { 707 MOZ_ASSERT(aRunnable); 708 } 709 710 virtual nsresult Cancel() override { 711 MOZ_ASSERT(GetCurrentThreadWorkerPrivate()); 712 (void)WorkerRun(nullptr, GetCurrentThreadWorkerPrivate()); 713 return NS_OK; 714 } 715 716 virtual bool WorkerRun(JSContext* aCx, 717 WorkerPrivate* aWorkerPrivate) override { 718 MOZ_ASSERT(aWorkerPrivate); 719 aWorkerPrivate->AssertIsOnWorkerThread(); 720 721 if (mRunnable) { 722 mRunnable->RunBackOnWorkerThreadForCleanup(aWorkerPrivate); 723 724 // Let's release the worker thread. 725 mRunnable->ReleaseWorker(); 726 mRunnable = nullptr; 727 } 728 729 return true; 730 } 731 732 private: 733 ~ReleaseRunnable() = default; 734 }; 735 736 RefPtr<WorkerControlRunnable> runnable = new ReleaseRunnable(this); 737 (void)NS_WARN_IF(!runnable->Dispatch(mWorkerRef->Private())); 738 } 739 740 void WorkerProxyToMainThreadRunnable::ReleaseWorker() { mWorkerRef = nullptr; } 741 742 } // namespace mozilla::dom