WorkerThread.cpp (10465B)
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 "WorkerThread.h" 8 9 #include "WorkerPrivate.h" 10 #include "WorkerRunnable.h" 11 #include "mozilla/Assertions.h" 12 #include "mozilla/CycleCollectedJSContext.h" 13 #include "mozilla/EventQueue.h" 14 #include "mozilla/Logging.h" 15 #include "mozilla/NotNull.h" 16 #include "mozilla/ThreadEventQueue.h" 17 #include "mozilla/UniquePtr.h" 18 #include "mozilla/ipc/BackgroundChild.h" 19 #include "nsCOMPtr.h" 20 #include "nsDebug.h" 21 #include "nsICancelableRunnable.h" 22 #include "nsIEventTarget.h" 23 #include "nsIRunnable.h" 24 #include "nsIThreadInternal.h" 25 #include "nsString.h" 26 #include "nsThreadUtils.h" 27 #include "prthread.h" 28 29 static mozilla::LazyLogModule gWorkerThread("WorkerThread"); 30 #ifdef LOGV 31 # undef LOGV 32 #endif 33 #define LOGV(msg) MOZ_LOG(gWorkerThread, LogLevel::Verbose, msg); 34 35 namespace mozilla { 36 37 using namespace ipc; 38 39 namespace dom { 40 41 WorkerThreadFriendKey::WorkerThreadFriendKey() { 42 MOZ_COUNT_CTOR(WorkerThreadFriendKey); 43 } 44 45 WorkerThreadFriendKey::~WorkerThreadFriendKey() { 46 MOZ_COUNT_DTOR(WorkerThreadFriendKey); 47 } 48 49 class WorkerThread::Observer final : public nsIThreadObserver { 50 WorkerPrivate* mWorkerPrivate; 51 52 public: 53 explicit Observer(WorkerPrivate* aWorkerPrivate) 54 : mWorkerPrivate(aWorkerPrivate) { 55 MOZ_ASSERT(aWorkerPrivate); 56 aWorkerPrivate->AssertIsOnWorkerThread(); 57 } 58 59 NS_DECL_THREADSAFE_ISUPPORTS 60 61 private: 62 ~Observer() { mWorkerPrivate->AssertIsOnWorkerThread(); } 63 64 NS_DECL_NSITHREADOBSERVER 65 }; 66 67 WorkerThread::WorkerThread(ConstructorKey) 68 : nsThread( 69 MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()), 70 nsThread::NOT_MAIN_THREAD, 71 {.stackSize = nsIThreadManager::LargeStackSize()}), 72 mLock("WorkerThread::mLock"), 73 mWorkerPrivateCondVar(mLock, "WorkerThread::mWorkerPrivateCondVar"), 74 mWorkerPrivate(nullptr), 75 mOtherThreadsDispatchingViaEventTarget(0) 76 #ifdef DEBUG 77 , 78 mAcceptingNonWorkerRunnables(true) 79 #endif 80 { 81 } 82 83 WorkerThread::~WorkerThread() { 84 MOZ_ASSERT(!mWorkerPrivate); 85 MOZ_ASSERT(!mOtherThreadsDispatchingViaEventTarget); 86 MOZ_ASSERT(mAcceptingNonWorkerRunnables); 87 } 88 89 // static 90 SafeRefPtr<WorkerThread> WorkerThread::Create( 91 const WorkerThreadFriendKey& /* aKey */) { 92 SafeRefPtr<WorkerThread> thread = 93 MakeSafeRefPtr<WorkerThread>(ConstructorKey()); 94 if (NS_FAILED(thread->Init("DOM Worker"_ns))) { 95 NS_WARNING("Failed to create new thread!"); 96 return nullptr; 97 } 98 99 return thread; 100 } 101 102 void WorkerThread::SetWorker(const WorkerThreadFriendKey& /* aKey */, 103 WorkerPrivate* aWorkerPrivate) { 104 MOZ_ASSERT(PR_GetCurrentThread() == mThread); 105 MOZ_ASSERT(aWorkerPrivate); 106 107 { 108 MutexAutoLock lock(mLock); 109 110 MOZ_ASSERT(!mWorkerPrivate); 111 MOZ_ASSERT(mAcceptingNonWorkerRunnables); 112 113 mWorkerPrivate = aWorkerPrivate; 114 #ifdef DEBUG 115 mAcceptingNonWorkerRunnables = false; 116 #endif 117 } 118 119 mObserver = new Observer(aWorkerPrivate); 120 MOZ_ALWAYS_SUCCEEDS(AddObserver(mObserver)); 121 } 122 123 void WorkerThread::ClearEventQueueAndWorker( 124 const WorkerThreadFriendKey& /* aKey */) { 125 MOZ_ASSERT(PR_GetCurrentThread() == mThread); 126 127 MOZ_ALWAYS_SUCCEEDS(RemoveObserver(mObserver)); 128 mObserver = nullptr; 129 130 { 131 MutexAutoLock lock(mLock); 132 133 MOZ_ASSERT(mWorkerPrivate); 134 MOZ_ASSERT(!mAcceptingNonWorkerRunnables); 135 // mOtherThreadsDispatchingViaEventTarget can still be non-zero here 136 // because WorkerThread::Dispatch isn't atomic so a thread initiating 137 // dispatch can have dispatched a runnable at this thread allowing us to 138 // begin shutdown before that thread gets a chance to decrement 139 // mOtherThreadsDispatchingViaEventTarget back to 0. So we need to wait 140 // for that. 141 while (mOtherThreadsDispatchingViaEventTarget) { 142 mWorkerPrivateCondVar.Wait(); 143 } 144 // Need to clean up the dispatched runnables if 145 // mOtherThreadsDispatchingViaEventTarget was non-zero. 146 if (NS_HasPendingEvents(nullptr)) { 147 NS_ProcessPendingEvents(nullptr); 148 } 149 #ifdef DEBUG 150 mAcceptingNonWorkerRunnables = true; 151 #endif 152 mWorkerPrivate = nullptr; 153 } 154 } 155 156 nsresult WorkerThread::DispatchPrimaryRunnable( 157 const WorkerThreadFriendKey& /* aKey */, 158 already_AddRefed<nsIRunnable> aRunnable) { 159 nsCOMPtr<nsIRunnable> runnable(aRunnable); 160 161 #ifdef DEBUG 162 MOZ_ASSERT(PR_GetCurrentThread() != mThread); 163 MOZ_ASSERT(runnable); 164 { 165 MutexAutoLock lock(mLock); 166 167 MOZ_ASSERT(!mWorkerPrivate); 168 MOZ_ASSERT(mAcceptingNonWorkerRunnables); 169 } 170 #endif 171 172 nsresult rv = nsThread::Dispatch(runnable.forget(), NS_DISPATCH_FALLIBLE); 173 if (NS_WARN_IF(NS_FAILED(rv))) { 174 return rv; 175 } 176 177 return NS_OK; 178 } 179 180 nsresult WorkerThread::DispatchAnyThread( 181 const WorkerThreadFriendKey& /* aKey */, 182 RefPtr<WorkerRunnable> aWorkerRunnable) { 183 // May be called on any thread! 184 185 #ifdef DEBUG 186 { 187 const bool onWorkerThread = PR_GetCurrentThread() == mThread; 188 { 189 MutexAutoLock lock(mLock); 190 191 MOZ_ASSERT(mWorkerPrivate); 192 MOZ_ASSERT(!mAcceptingNonWorkerRunnables); 193 194 if (onWorkerThread) { 195 mWorkerPrivate->AssertIsOnWorkerThread(); 196 } 197 } 198 } 199 #endif 200 201 nsresult rv = 202 nsThread::Dispatch(aWorkerRunnable.forget(), NS_DISPATCH_FALLIBLE); 203 if (NS_WARN_IF(NS_FAILED(rv))) { 204 return rv; 205 } 206 207 // We don't need to notify the worker's condition variable here because we're 208 // being called from worker-controlled code and it will make sure to wake up 209 // the worker thread if needed. 210 211 return NS_OK; 212 } 213 214 NS_IMETHODIMP 215 WorkerThread::DispatchFromScript(nsIRunnable* aRunnable, DispatchFlags aFlags) { 216 return Dispatch(do_AddRef(aRunnable), aFlags); 217 } 218 219 NS_IMETHODIMP 220 WorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable, 221 DispatchFlags aFlags) { 222 // May be called on any thread! 223 224 // NOTE: To maintain historical behaviour we don't leak on error cases within 225 // WorkerThread::Dispatch even if `NS_DISPATCH_FALLIBLE` is not specified. We 226 // may want to change this for consistency in the future. 227 nsCOMPtr<nsIRunnable> runnable(aRunnable); 228 229 LOGV(("WorkerThread::Dispatch [%p] runnable: %p", this, runnable.get())); 230 231 const bool onWorkerThread = PR_GetCurrentThread() == mThread; 232 233 WorkerPrivate* workerPrivate = nullptr; 234 if (onWorkerThread) { 235 // If the mWorkerPrivate has already disconnected by 236 // WorkerPrivate::ResetWorkerPrivateInWorkerThread(), there is no chance 237 // that to execute this runnable. Return NS_ERROR_UNEXPECTED here. 238 if (!mWorkerPrivate) { 239 return NS_ERROR_UNEXPECTED; 240 } 241 // No need to lock here because it is only modified on this thread. 242 mWorkerPrivate->AssertIsOnWorkerThread(); 243 244 workerPrivate = mWorkerPrivate; 245 } else { 246 MutexAutoLock lock(mLock); 247 248 MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget < UINT32_MAX); 249 250 if (mWorkerPrivate) { 251 workerPrivate = mWorkerPrivate; 252 253 // Incrementing this counter will make the worker thread sleep if it 254 // somehow tries to unset mWorkerPrivate while we're using it. 255 mOtherThreadsDispatchingViaEventTarget++; 256 } 257 } 258 259 nsresult rv; 260 rv = nsThread::Dispatch(runnable.forget(), aFlags); 261 262 if (!onWorkerThread && workerPrivate) { 263 // We need to wake the worker thread if we're not already on the right 264 // thread and the dispatch succeeded. 265 if (NS_SUCCEEDED(rv)) { 266 MutexAutoLock workerLock(workerPrivate->mMutex); 267 268 workerPrivate->mCondVar.Notify(); 269 } 270 271 // Now unset our waiting flag. 272 { 273 MutexAutoLock lock(mLock); 274 275 MOZ_ASSERT(mOtherThreadsDispatchingViaEventTarget); 276 277 if (!--mOtherThreadsDispatchingViaEventTarget) { 278 mWorkerPrivateCondVar.Notify(); 279 } 280 } 281 } 282 283 if (NS_WARN_IF(NS_FAILED(rv))) { 284 LOGV(("WorkerThread::Dispatch [%p] failed, runnable: %p", this, 285 runnable.get())); 286 return rv; 287 } 288 289 return NS_OK; 290 } 291 292 NS_IMETHODIMP 293 WorkerThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { 294 return NS_ERROR_NOT_IMPLEMENTED; 295 } 296 297 uint32_t WorkerThread::RecursionDepth( 298 const WorkerThreadFriendKey& /* aKey */) const { 299 MOZ_ASSERT(PR_GetCurrentThread() == mThread); 300 301 return mNestedEventLoopDepth; 302 } 303 304 NS_IMETHODIMP 305 WorkerThread::HasPendingEvents(bool* aResult) { 306 MOZ_ASSERT(aResult); 307 const bool onWorkerThread = PR_GetCurrentThread() == mThread; 308 // If is on the worker thread, call nsThread::HasPendingEvents directly. 309 if (onWorkerThread) { 310 return nsThread::HasPendingEvents(aResult); 311 } 312 // Checking if is on the parent thread, otherwise, returns unexpected error. 313 { 314 MutexAutoLock lock(mLock); 315 // return directly if the mWorkerPrivate has not yet set or had already 316 // unset 317 if (!mWorkerPrivate) { 318 *aResult = false; 319 return NS_OK; 320 } 321 if (!mWorkerPrivate->IsOnParentThread()) { 322 *aResult = false; 323 return NS_ERROR_UNEXPECTED; 324 } 325 } 326 *aResult = mEvents->HasPendingEvent(); 327 return NS_OK; 328 } 329 330 NS_IMPL_ISUPPORTS(WorkerThread::Observer, nsIThreadObserver) 331 332 NS_IMETHODIMP 333 WorkerThread::Observer::OnDispatchedEvent() { 334 MOZ_CRASH("OnDispatchedEvent() should never be called!"); 335 } 336 337 NS_IMETHODIMP 338 WorkerThread::Observer::OnProcessNextEvent(nsIThreadInternal* /* aThread */, 339 bool aMayWait) { 340 mWorkerPrivate->AssertIsOnWorkerThread(); 341 342 // If the PBackground child is not created yet, then we must permit 343 // blocking event processing to support 344 // BackgroundChild::GetOrCreateCreateForCurrentThread(). If this occurs 345 // then we are spinning on the event queue at the start of 346 // PrimaryWorkerRunnable::Run() and don't want to process the event in 347 // mWorkerPrivate yet. 348 if (aMayWait) { 349 MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth() == 2); 350 MOZ_ASSERT(!BackgroundChild::GetForCurrentThread()); 351 return NS_OK; 352 } 353 354 mWorkerPrivate->OnProcessNextEvent(); 355 return NS_OK; 356 } 357 358 NS_IMETHODIMP 359 WorkerThread::Observer::AfterProcessNextEvent(nsIThreadInternal* /* aThread */, 360 bool /* aEventWasProcessed */) { 361 mWorkerPrivate->AssertIsOnWorkerThread(); 362 363 mWorkerPrivate->AfterProcessNextEvent(); 364 return NS_OK; 365 } 366 367 } // namespace dom 368 } // namespace mozilla