tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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