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 "WorkerThread.h"
10 #include "WorkerPrivate.h"
11 #include "WorkerRunnable.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Atomics.h"
14 #include "mozilla/CycleCollectedJSContext.h"
15 #include "mozilla/EventQueue.h"
16 #include "mozilla/Logging.h"
17 #include "mozilla/MacroForEach.h"
18 #include "mozilla/NotNull.h"
19 #include "mozilla/ThreadEventQueue.h"
20 #include "mozilla/UniquePtr.h"
21 #include "mozilla/ipc/BackgroundChild.h"
24 #include "nsICancelableRunnable.h"
25 #include "nsIEventTarget.h"
26 #include "nsIRunnable.h"
27 #include "nsIThreadInternal.h"
29 #include "nsThreadUtils.h"
32 static mozilla::LazyLogModule
gWorkerThread("WorkerThread");
36 #define LOGV(msg) MOZ_LOG(gWorkerThread, LogLevel::Verbose, msg);
46 // The C stack size. We use the same stack size on all platforms for
49 // Note: Our typical equation of 256 machine words works out to 2MB on 64-bit
50 // platforms. Since that works out to the size of a VM huge page, that can
51 // sometimes lead to an OS allocating an entire huge page for the stack at once.
52 // To avoid this, we subtract the size of 2 pages, to be safe.
53 const uint32_t kWorkerStackSize
= 256 * sizeof(size_t) * 1024 - 8192;
57 WorkerThreadFriendKey::WorkerThreadFriendKey() {
58 MOZ_COUNT_CTOR(WorkerThreadFriendKey
);
61 WorkerThreadFriendKey::~WorkerThreadFriendKey() {
62 MOZ_COUNT_DTOR(WorkerThreadFriendKey
);
65 class WorkerThread::Observer final
: public nsIThreadObserver
{
66 WorkerPrivate
* mWorkerPrivate
;
69 explicit Observer(WorkerPrivate
* aWorkerPrivate
)
70 : mWorkerPrivate(aWorkerPrivate
) {
71 MOZ_ASSERT(aWorkerPrivate
);
72 aWorkerPrivate
->AssertIsOnWorkerThread();
75 NS_DECL_THREADSAFE_ISUPPORTS
78 ~Observer() { mWorkerPrivate
->AssertIsOnWorkerThread(); }
80 NS_DECL_NSITHREADOBSERVER
83 WorkerThread::WorkerThread(ConstructorKey
)
85 MakeNotNull
<ThreadEventQueue
*>(MakeUnique
<mozilla::EventQueue
>()),
86 nsThread::NOT_MAIN_THREAD
, {.stackSize
= kWorkerStackSize
}),
87 mLock("WorkerThread::mLock"),
88 mWorkerPrivateCondVar(mLock
, "WorkerThread::mWorkerPrivateCondVar"),
89 mWorkerPrivate(nullptr),
90 mOtherThreadsDispatchingViaEventTarget(0)
93 mAcceptingNonWorkerRunnables(true)
98 WorkerThread::~WorkerThread() {
99 MOZ_ASSERT(!mWorkerPrivate
);
100 MOZ_ASSERT(!mOtherThreadsDispatchingViaEventTarget
);
101 MOZ_ASSERT(mAcceptingNonWorkerRunnables
);
105 SafeRefPtr
<WorkerThread
> WorkerThread::Create(
106 const WorkerThreadFriendKey
& /* aKey */) {
107 SafeRefPtr
<WorkerThread
> thread
=
108 MakeSafeRefPtr
<WorkerThread
>(ConstructorKey());
109 if (NS_FAILED(thread
->Init("DOM Worker"_ns
))) {
110 NS_WARNING("Failed to create new thread!");
117 void WorkerThread::SetWorker(const WorkerThreadFriendKey
& /* aKey */,
118 WorkerPrivate
* aWorkerPrivate
) {
119 MOZ_ASSERT(PR_GetCurrentThread() == mThread
);
120 MOZ_ASSERT(aWorkerPrivate
);
123 MutexAutoLock
lock(mLock
);
125 MOZ_ASSERT(!mWorkerPrivate
);
126 MOZ_ASSERT(mAcceptingNonWorkerRunnables
);
128 mWorkerPrivate
= aWorkerPrivate
;
130 mAcceptingNonWorkerRunnables
= false;
134 mObserver
= new Observer(aWorkerPrivate
);
135 MOZ_ALWAYS_SUCCEEDS(AddObserver(mObserver
));
138 void WorkerThread::ClearEventQueueAndWorker(
139 const WorkerThreadFriendKey
& /* aKey */) {
140 MOZ_ASSERT(PR_GetCurrentThread() == mThread
);
142 MOZ_ALWAYS_SUCCEEDS(RemoveObserver(mObserver
));
146 MutexAutoLock
lock(mLock
);
148 MOZ_ASSERT(mWorkerPrivate
);
149 MOZ_ASSERT(!mAcceptingNonWorkerRunnables
);
150 // mOtherThreadsDispatchingViaEventTarget can still be non-zero here
151 // because WorkerThread::Dispatch isn't atomic so a thread initiating
152 // dispatch can have dispatched a runnable at this thread allowing us to
153 // begin shutdown before that thread gets a chance to decrement
154 // mOtherThreadsDispatchingViaEventTarget back to 0. So we need to wait
156 while (mOtherThreadsDispatchingViaEventTarget
) {
157 mWorkerPrivateCondVar
.Wait();
159 // Need to clean up the dispatched runnables if
160 // mOtherThreadsDispatchingViaEventTarget was non-zero.
161 if (NS_HasPendingEvents(nullptr)) {
162 NS_ProcessPendingEvents(nullptr);
165 mAcceptingNonWorkerRunnables
= true;
167 mWorkerPrivate
= nullptr;
171 nsresult
WorkerThread::DispatchPrimaryRunnable(
172 const WorkerThreadFriendKey
& /* aKey */,
173 already_AddRefed
<nsIRunnable
> aRunnable
) {
174 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
177 MOZ_ASSERT(PR_GetCurrentThread() != mThread
);
178 MOZ_ASSERT(runnable
);
180 MutexAutoLock
lock(mLock
);
182 MOZ_ASSERT(!mWorkerPrivate
);
183 MOZ_ASSERT(mAcceptingNonWorkerRunnables
);
187 nsresult rv
= nsThread::Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
188 if (NS_WARN_IF(NS_FAILED(rv
))) {
195 nsresult
WorkerThread::DispatchAnyThread(
196 const WorkerThreadFriendKey
& /* aKey */,
197 RefPtr
<WorkerRunnable
> aWorkerRunnable
) {
198 // May be called on any thread!
202 const bool onWorkerThread
= PR_GetCurrentThread() == mThread
;
204 MutexAutoLock
lock(mLock
);
206 MOZ_ASSERT(mWorkerPrivate
);
207 MOZ_ASSERT(!mAcceptingNonWorkerRunnables
);
209 if (onWorkerThread
) {
210 mWorkerPrivate
->AssertIsOnWorkerThread();
217 nsThread::Dispatch(aWorkerRunnable
.forget(), NS_DISPATCH_NORMAL
);
218 if (NS_WARN_IF(NS_FAILED(rv
))) {
222 // We don't need to notify the worker's condition variable here because we're
223 // being called from worker-controlled code and it will make sure to wake up
224 // the worker thread if needed.
230 WorkerThread::DispatchFromScript(nsIRunnable
* aRunnable
, uint32_t aFlags
) {
231 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
);
232 return Dispatch(runnable
.forget(), aFlags
);
236 WorkerThread::Dispatch(already_AddRefed
<nsIRunnable
> aRunnable
,
238 // May be called on any thread!
239 nsCOMPtr
<nsIRunnable
> runnable(aRunnable
); // in case we exit early
241 LOGV(("WorkerThread::Dispatch [%p] runnable: %p", this, runnable
.get()));
243 // Workers only support asynchronous dispatch.
244 if (NS_WARN_IF(aFlags
!= NS_DISPATCH_NORMAL
)) {
245 return NS_ERROR_UNEXPECTED
;
248 const bool onWorkerThread
= PR_GetCurrentThread() == mThread
;
250 WorkerPrivate
* workerPrivate
= nullptr;
251 if (onWorkerThread
) {
252 // If the mWorkerPrivate has already disconnected by
253 // WorkerPrivate::ResetWorkerPrivateInWorkerThread(), there is no chance
254 // that to execute this runnable. Return NS_ERROR_UNEXPECTED here.
255 if (!mWorkerPrivate
) {
256 return NS_ERROR_UNEXPECTED
;
258 // No need to lock here because it is only modified on this thread.
259 mWorkerPrivate
->AssertIsOnWorkerThread();
261 workerPrivate
= mWorkerPrivate
;
263 MutexAutoLock
lock(mLock
);
265 MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget
< UINT32_MAX
);
267 if (mWorkerPrivate
) {
268 workerPrivate
= mWorkerPrivate
;
270 // Incrementing this counter will make the worker thread sleep if it
271 // somehow tries to unset mWorkerPrivate while we're using it.
272 mOtherThreadsDispatchingViaEventTarget
++;
277 rv
= nsThread::Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
279 if (!onWorkerThread
&& workerPrivate
) {
280 // We need to wake the worker thread if we're not already on the right
281 // thread and the dispatch succeeded.
282 if (NS_SUCCEEDED(rv
)) {
283 MutexAutoLock
workerLock(workerPrivate
->mMutex
);
285 workerPrivate
->mCondVar
.Notify();
288 // Now unset our waiting flag.
290 MutexAutoLock
lock(mLock
);
292 MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget
);
294 if (!--mOtherThreadsDispatchingViaEventTarget
) {
295 mWorkerPrivateCondVar
.Notify();
300 if (NS_WARN_IF(NS_FAILED(rv
))) {
301 LOGV(("WorkerThread::Dispatch [%p] failed, runnable: %p", this,
310 WorkerThread::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t) {
311 return NS_ERROR_NOT_IMPLEMENTED
;
314 uint32_t WorkerThread::RecursionDepth(
315 const WorkerThreadFriendKey
& /* aKey */) const {
316 MOZ_ASSERT(PR_GetCurrentThread() == mThread
);
318 return mNestedEventLoopDepth
;
322 WorkerThread::HasPendingEvents(bool* aResult
) {
324 const bool onWorkerThread
= PR_GetCurrentThread() == mThread
;
325 // If is on the worker thread, call nsThread::HasPendingEvents directly.
326 if (onWorkerThread
) {
327 return nsThread::HasPendingEvents(aResult
);
329 // Checking if is on the parent thread, otherwise, returns unexpected error.
331 MutexAutoLock
lock(mLock
);
332 // return directly if the mWorkerPrivate has not yet set or had already
334 if (!mWorkerPrivate
) {
338 if (!mWorkerPrivate
->IsOnParentThread()) {
340 return NS_ERROR_UNEXPECTED
;
343 *aResult
= mEvents
->HasPendingEvent();
347 NS_IMPL_ISUPPORTS(WorkerThread::Observer
, nsIThreadObserver
)
350 WorkerThread::Observer::OnDispatchedEvent() {
351 MOZ_CRASH("OnDispatchedEvent() should never be called!");
355 WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal
* /* aThread */,
357 mWorkerPrivate
->AssertIsOnWorkerThread();
359 // If the PBackground child is not created yet, then we must permit
360 // blocking event processing to support
361 // BackgroundChild::GetOrCreateCreateForCurrentThread(). If this occurs
362 // then we are spinning on the event queue at the start of
363 // PrimaryWorkerRunnable::Run() and don't want to process the event in
364 // mWorkerPrivate yet.
366 MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth() == 2);
367 MOZ_ASSERT(!BackgroundChild::GetForCurrentThread());
371 mWorkerPrivate
->OnProcessNextEvent();
376 WorkerThread::Observer::AfterProcessNextEvent(nsIThreadInternal
* /* aThread */,
377 bool /* aEventWasProcessed */) {
378 mWorkerPrivate
->AssertIsOnWorkerThread();
380 mWorkerPrivate
->AfterProcessNextEvent();
385 } // namespace mozilla