DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (25c1ba3e39da)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ThreadEventTarget.h"
#include "mozilla/ThreadEventQueue.h"

#include "LeakRefPtr.h"
#include "mozilla/TimeStamp.h"
#include "nsComponentManagerUtils.h"
#include "nsITimer.h"
#include "nsThreadManager.h"
#include "nsThreadSyncDispatch.h"
#include "nsThreadUtils.h"
#include "nsXPCOMPrivate.h"  // for gXPCOMThreadsShutDown
#include "ThreadDelay.h"

#ifdef MOZ_TASK_TRACER
#  include "GeckoTaskTracer.h"
#  include "TracedTaskCommon.h"
using namespace mozilla::tasktracer;
#endif

using namespace mozilla;

namespace {

class DelayedRunnable : public Runnable, public nsITimerCallback {
 public:
  DelayedRunnable(already_AddRefed<nsIEventTarget> aTarget,
                  already_AddRefed<nsIRunnable> aRunnable, uint32_t aDelay)
      : mozilla::Runnable("DelayedRunnable"),
        mTarget(aTarget),
        mWrappedRunnable(aRunnable),
        mDelayedFrom(TimeStamp::NowLoRes()),
        mDelay(aDelay) {}

  NS_DECL_ISUPPORTS_INHERITED

  nsresult Init() {
    return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay,
                                   nsITimer::TYPE_ONE_SHOT, mTarget);
  }

  nsresult DoRun() {
    nsCOMPtr<nsIRunnable> r = std::move(mWrappedRunnable);
    return r->Run();
  }

  NS_IMETHOD Run() override {
    // Already ran?
    if (!mWrappedRunnable) {
      return NS_OK;
    }

    // Are we too early?
    if ((TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < mDelay) {
      return NS_OK;  // Let the nsITimer run us.
    }

    mTimer->Cancel();
    return DoRun();
  }

  NS_IMETHOD Notify(nsITimer* aTimer) override {
    // If we already ran, the timer should have been canceled.
    MOZ_ASSERT(mWrappedRunnable);
    MOZ_ASSERT(aTimer == mTimer);

    return DoRun();
  }

 private:
  ~DelayedRunnable() = default;

  nsCOMPtr<nsIEventTarget> mTarget;
  nsCOMPtr<nsIRunnable> mWrappedRunnable;
  nsCOMPtr<nsITimer> mTimer;
  TimeStamp mDelayedFrom;
  uint32_t mDelay;
};

NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback)

}  // anonymous namespace

ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink,
                                     bool aIsMainThread)
    : mSink(aSink), mIsMainThread(aIsMainThread) {
  mThread = PR_GetCurrentThread();
}

void ThreadEventTarget::SetCurrentThread() { mThread = PR_GetCurrentThread(); }

void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; }

NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget)

NS_IMETHODIMP
ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) {
  return Dispatch(do_AddRef(aRunnable), aFlags);
}

NS_IMETHODIMP
ThreadEventTarget::Dispatch(already_AddRefed<nsIRunnable> aEvent,
                            uint32_t aFlags) {
  // We want to leak the reference when we fail to dispatch it, so that
  // we won't release the event in a wrong thread.
  LeakRefPtr<nsIRunnable> event(std::move(aEvent));
  if (NS_WARN_IF(!event)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (gXPCOMThreadsShutDown && !mIsMainThread) {
    NS_ASSERTION(false, "Failed Dispatch after xpcom-shutdown-threads");
    return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
  }

#ifdef MOZ_TASK_TRACER
  nsCOMPtr<nsIRunnable> tracedRunnable = CreateTracedRunnable(event.take());
  (static_cast<TracedRunnable*>(tracedRunnable.get()))->DispatchTask();
  // XXX tracedRunnable will always leaked when we fail to disptch.
  event = tracedRunnable.forget();
#endif

  if (aFlags & DISPATCH_SYNC) {
    nsCOMPtr<nsIEventTarget> current = GetCurrentThreadEventTarget();
    if (NS_WARN_IF(!current)) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    // XXX we should be able to do something better here... we should
    //     be able to monitor the slot occupied by this event and use
    //     that to tell us when the event has been processed.

    RefPtr<nsThreadSyncDispatch> wrapper =
        new nsThreadSyncDispatch(current.forget(), event.take());
    bool success = mSink->PutEvent(do_AddRef(wrapper),
                                   EventQueuePriority::Normal);  // hold a ref
    if (!success) {
      // PutEvent leaked the wrapper runnable object on failure, so we
      // explicitly release this object once for that. Note that this
      // object will be released again soon because it exits the scope.
      wrapper.get()->Release();
      return NS_ERROR_UNEXPECTED;
    }

    // Allows waiting; ensure no locks are held that would deadlock us!
    SpinEventLoopUntil(
        [&, wrapper]() -> bool { return !wrapper->IsPending(); });

    return NS_OK;
  }

  NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END,
               "unexpected dispatch flags");
  if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) {
    return NS_ERROR_UNEXPECTED;
  }
  // Delay to encourage the receiving task to run before we do work.
  DelayForChaosMode(ChaosFeature::TaskDispatching, 1000);
  return NS_OK;
}

NS_IMETHODIMP
ThreadEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
                                   uint32_t aDelayMs) {
  NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);

  RefPtr<DelayedRunnable> r =
      new DelayedRunnable(do_AddRef(this), std::move(aEvent), aDelayMs);
  nsresult rv = r->Init();
  NS_ENSURE_SUCCESS(rv, rv);

  return Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

NS_IMETHODIMP
ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
  *aIsOnCurrentThread = IsOnCurrentThread();
  return NS_OK;
}

NS_IMETHODIMP_(bool)
ThreadEventTarget::IsOnCurrentThreadInfallible() {
  // This method is only going to be called if `mThread` is null, which
  // only happens when the thread has exited the event loop.  Therefore, when
  // we are called, we can never be on this thread.
  return false;
}