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