Global Metrics

path: .metrics.cyclomatic.average
old: 1.0
new: 1.605263157894737

path: .metrics.cyclomatic.sum
old: 11.0
new: 61.0

path: .metrics.mi.mi_original
old: 57.150268739155294
new: 13.2847781231809

path: .metrics.mi.mi_visual_studio
old: 33.4212097889797
new: 7.768876095427427

path: .metrics.mi.mi_sei
old: 31.27719695259034
new: -21.03817613960489

path: .metrics.nexits.sum
old: 4.0
new: 30.0

path: .metrics.nexits.average
old: 0.5714285714285714
new: 0.8571428571428571

path: .metrics.nargs.sum
old: 0.0
new: 29.0

path: .metrics.nargs.average
old: 0.0
new: 0.8285714285714286

path: .metrics.cognitive.average
old: 0.0
new: 0.7142857142857143

path: .metrics.cognitive.sum
old: 0.0
new: 25.0

path: .metrics.halstead.estimated_program_length
old: 440.5686351804948
new: 1435.071397551833

path: .metrics.halstead.purity_ratio
old: 1.7764864321794145
new: 1.195892831293194

path: .metrics.halstead.N1
old: 138.0
new: 700.0

path: .metrics.halstead.level
old: 0.0715909090909091
new: 0.02707692307692308

path: .metrics.halstead.length
old: 248.0
new: 1200.0

path: .metrics.halstead.N2
old: 110.0
new: 500.0

path: .metrics.halstead.vocabulary
old: 79.0
new: 202.0

path: .metrics.halstead.difficulty
old: 13.968253968253968
new: 36.93181818181818

path: .metrics.halstead.effort
old: 21837.09699178049
new: 339398.00889468176

path: .metrics.halstead.n2
old: 63.0
new: 176.0

path: .metrics.halstead.bugs
old: 0.2604206072913231
new: 1.6218783331583877

path: .metrics.halstead.n1
old: 16.0
new: 26.0

path: .metrics.halstead.time
old: 1213.1720550989162
new: 18855.44493859343

path: .metrics.halstead.volume
old: 1563.3376255479216
new: 9189.853779302151

path: .metrics.loc.lloc
old: 6.0
new: 94.0

path: .metrics.loc.cloc
old: 9.0
new: 62.0

path: .metrics.loc.blank
old: 26.0
new: 45.0

path: .metrics.loc.ploc
old: 56.0
new: 273.0

path: .metrics.loc.sloc
old: 91.0
new: 380.0

path: .metrics.nom.functions
old: 7.0
new: 34.0

path: .metrics.nom.total
old: 7.0
new: 35.0

path: .metrics.nom.closures
old: 0.0
new: 1.0

Spaces Data

Minimal test - lines (23, 380)

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 30.0

path: .spaces[0].metrics.nexits.average
old: null
new: 0.8571428571428571

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.6216216216216215

path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 60.0

path: .spaces[0].metrics.halstead.length
old: 1.0
new: 1185.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: null
new: 1302.4890500481215

path: .spaces[0].metrics.halstead.N2
old: 1.0
new: 485.0

path: .spaces[0].metrics.halstead.difficulty
old: 0.0
new: 39.161490683229815

path: .spaces[0].metrics.halstead.effort
old: 0.0
new: 350223.9499376769

path: .spaces[0].metrics.halstead.vocabulary
old: 1.0
new: 187.0

path: .spaces[0].metrics.halstead.volume
old: 0.0
new: 8943.069934966848

path: .spaces[0].metrics.halstead.level
old: null
new: 0.025535289452815225

path: .spaces[0].metrics.halstead.purity_ratio
old: null
new: 1.0991468776777398

path: .spaces[0].metrics.halstead.time
old: 0.0
new: 19456.886107648716

path: .spaces[0].metrics.halstead.bugs
old: 0.0
new: 1.6561867525592655

path: .spaces[0].metrics.halstead.N1
old: 0.0
new: 700.0

path: .spaces[0].metrics.halstead.n2
old: 1.0
new: 161.0

path: .spaces[0].metrics.halstead.n1
old: 0.0
new: 26.0

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 1.0

path: .spaces[0].metrics.nom.total
old: 0.0
new: 35.0

path: .spaces[0].metrics.nom.functions
old: 0.0
new: 34.0

path: .spaces[0].metrics.cognitive.average
old: null
new: 0.7142857142857143

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 25.0

path: .spaces[0].metrics.loc.blank
old: 0.0
new: 46.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 94.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 54.0

path: .spaces[0].metrics.loc.ploc
old: 1.0
new: 258.0

path: .spaces[0].metrics.loc.sloc
old: 1.0
new: 358.0

path: .spaces[0].metrics.nargs.average
old: null
new: 0.8285714285714286

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 29.0

path: .spaces[0].metrics.mi.mi_original
old: null
new: 14.622467767856506

path: .spaces[0].metrics.mi.mi_sei
old: null
new: -20.19474872024005

path: .spaces[0].metrics.mi.mi_visual_studio
old: null
new: 8.551150741436553

Code

namespace mozilla {

LazyLogModule gMozPromiseLog("MozPromise");
LazyLogModule gStateWatchingLog("StateWatching");

StaticRefPtr sMainThread;
MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS;

class XPCOMThreadWrapper final : public AbstractThread,
                                 public nsIThreadObserver,
                                 public nsIDirectTaskDispatcher {
 public:
  XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch,
                     bool aOnThread)
      : AbstractThread(aRequireTailDispatch),
        mThread(aThread),
        mDirectTaskDispatcher(do_QueryInterface(aThread)),
        mOnThread(aOnThread) {
    MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher);
    MOZ_DIAGNOSTIC_ASSERT(!aOnThread || IsCurrentThreadIn());
    if (aOnThread) {
      MOZ_ASSERT(!sCurrentThreadTLS.get(),
                 "There can only be a single XPCOMThreadWrapper available on a "
                 "thread");
      // Set the default current thread so that GetCurrent() never returns
      // nullptr.
      sCurrentThreadTLS.set(this);
    }
  }

  NS_DECL_ISUPPORTS_INHERITED

  nsresult Dispatch(already_AddRefed aRunnable,
                    DispatchReason aReason = NormalDispatch) override {
    nsCOMPtr r = aRunnable;
    AbstractThread* currentThread;
    if (aReason != TailDispatch && (currentThread = GetCurrent()) &&
        RequiresTailDispatch(currentThread) &&
        currentThread->IsTailDispatcherAvailable()) {
      return currentThread->TailDispatcher().AddTask(this, r.forget());
    }

    // At a certain point during shutdown, we stop processing events from the
    // main thread event queue (this happens long after all _other_ XPCOM
    // threads have been shut down). However, various bits of subsequent
    // teardown logic (the media shutdown blocker and the final shutdown cycle
    // collection) can trigger state watching and state mirroring notifications
    // that result in dispatch to the main thread. This causes shutdown leaks,
    // because the |Runner| wrapper below creates a guaranteed cycle
    // (Thread->EventQueue->Runnable->Thread) until the event is processed. So
    // if we put the event into a queue that will never be processed, we'll wind
    // up with a leak.
    //
    // We opt to just release the runnable in that case. Ordinarily, this
    // approach could cause problems for runnables that are only safe to be
    // released on the target thread (and not the dispatching thread). This is
    // why XPCOM thread dispatch explicitly leaks the runnable when dispatch
    // fails, rather than releasing it. But given that this condition only
    // applies very late in shutdown when only one thread remains operational,
    // that concern is unlikely to apply.
    if (gXPCOMMainThreadEventsAreDoomed) {
      return NS_ERROR_FAILURE;
    }

    RefPtr runner = new Runner(this, r.forget());
    return mThread->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
  }

  // Prevent a GCC warning about the other overload of Dispatch being hidden.
  using AbstractThread::Dispatch;

  bool IsCurrentThreadIn() const override {
    return mThread->IsOnCurrentThread();
  }

  TaskDispatcher& TailDispatcher() override {
    MOZ_ASSERT(IsCurrentThreadIn());
    MOZ_ASSERT(IsTailDispatcherAvailable());
    if (!mTailDispatcher.isSome()) {
      mTailDispatcher.emplace(mDirectTaskDispatcher,
                              /* aIsTailDispatcher = */ true);
      mThread->AddObserver(this);
    }

    return mTailDispatcher.ref();
  }

  bool IsTailDispatcherAvailable() override {
    // Our tail dispatching implementation relies on nsIThreadObserver
    // callbacks. If we're not doing event processing, it won't work.
    bool inEventLoop =
        static_cast(mThread.get())->RecursionDepth() > 0;
    return inEventLoop;
  }

  bool MightHaveTailTasks() override { return mTailDispatcher.isSome(); }

  nsIEventTarget* AsEventTarget() override { return mThread; }

  //-----------------------------------------------------------------------------
  // nsIThreadObserver
  //-----------------------------------------------------------------------------
  NS_IMETHOD OnDispatchedEvent() override { return NS_OK; }

  NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
                                   bool eventWasProcessed) override {
    // This is the primary case.
    MaybeFireTailDispatcher();
    return NS_OK;
  }

  NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* thread,
                                bool mayWait) override {
    // In general, the tail dispatcher is handled at the end of the current in
    // AfterProcessNextEvent() above. However, if start spinning a nested event
    // loop, it's generally better to fire the tail dispatcher before the first
    // nested event, rather than after it. This check handles that case.
    MaybeFireTailDispatcher();
    return NS_OK;
  }

  //-----------------------------------------------------------------------------
  // nsIDirectTaskDispatcher
  //-----------------------------------------------------------------------------
  // Forward calls to nsIDirectTaskDispatcher to the underlying nsThread object.
  // We can't use the generated NS_FORWARD_NSIDIRECTTASKDISPATCHER macro
  // as already_AddRefed type must be moved.
  NS_IMETHOD DispatchDirectTask(already_AddRefed aEvent) override {
    return mDirectTaskDispatcher->DispatchDirectTask(std::move(aEvent));
  }
  NS_IMETHOD DrainDirectTasks() override {
    return mDirectTaskDispatcher->DrainDirectTasks();
  }
  NS_IMETHOD HaveDirectTasks(bool* aResult) override {
    return mDirectTaskDispatcher->HaveDirectTasks(aResult);
  }

 private:
  const RefPtr mThread;
  const nsCOMPtr mDirectTaskDispatcher;
  Maybe mTailDispatcher;
  const bool mOnThread;

  ~XPCOMThreadWrapper() {
    if (mOnThread) {
      MOZ_DIAGNOSTIC_ASSERT(IsCurrentThreadIn(),
                            "Must be destroyed on the thread it was created");
      sCurrentThreadTLS.set(nullptr);
    }
  }

  void MaybeFireTailDispatcher() {
    if (mTailDispatcher.isSome()) {
      mTailDispatcher.ref().DrainDirectTasks();
      mThread->RemoveObserver(this);
      mTailDispatcher.reset();
    }
  }

  class Runner : public Runnable {
   public:
    explicit Runner(XPCOMThreadWrapper* aThread,
                    already_AddRefed aRunnable)
        : Runnable("XPCOMThreadWrapper::Runner"),
          mThread(aThread),
          mRunnable(aRunnable) {}

    NS_IMETHOD Run() override {
      MOZ_ASSERT(mThread == AbstractThread::GetCurrent());
      MOZ_ASSERT(mThread->IsCurrentThreadIn());
      SerialEventTargetGuard guard(mThread);
      return mRunnable->Run();
    }

#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
    NS_IMETHOD GetName(nsACString& aName) override {
      aName.AssignLiteral("AbstractThread::Runner");
      if (nsCOMPtr named = do_QueryInterface(mRunnable)) {
        nsAutoCString name;
        named->GetName(name);
        if (!name.IsEmpty()) {
          aName.AppendLiteral(" for ");
          aName.Append(name);
        }
      }
      return NS_OK;
    }
#endif

   private:
    const RefPtr mThread;
    const RefPtr mRunnable;
  };
};
NS_IMPL_ISUPPORTS_INHERITED(XPCOMThreadWrapper, AbstractThread,
                            nsIThreadObserver, nsIDirectTaskDispatcher);

NS_IMPL_ISUPPORTS(AbstractThread, nsIEventTarget, nsISerialEventTarget)

NS_IMETHODIMP_(bool)
AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); }

NS_IMETHODIMP
AbstractThread::IsOnCurrentThread(bool* aResult) {
  *aResult = IsCurrentThreadIn();
  return NS_OK;
}

NS_IMETHODIMP
AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
  nsCOMPtr event(aEvent);
  return Dispatch(event.forget(), aFlags);
}

NS_IMETHODIMP
AbstractThread::Dispatch(already_AddRefed aEvent,
                         uint32_t aFlags) {
  return Dispatch(std::move(aEvent), NormalDispatch);
}

NS_IMETHODIMP
AbstractThread::DelayedDispatch(already_AddRefed aEvent,
                                uint32_t aDelayMs) {
  nsCOMPtr event = aEvent;
  NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED);

  RefPtr r =
      new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs);
  nsresult rv = r->Init();
  NS_ENSURE_SUCCESS(rv, rv);

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

nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) {
  if (MightHaveTailTasks()) {
    return TailDispatcher().DispatchTasksFor(aThread);
  }

  return NS_OK;
}

bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) {
  if (!MightHaveTailTasks()) {
    return false;
  }
  return TailDispatcher().HasTasksFor(aThread);
}

bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const {
  MOZ_ASSERT(aThread);
  // We require tail dispatch if both the source and destination
  // threads support it.
  return SupportsTailDispatch() && aThread->SupportsTailDispatch();
}

bool AbstractThread::RequiresTailDispatchFromCurrentThread() const {
  AbstractThread* current = GetCurrent();
  return current && RequiresTailDispatch(current);
}

AbstractThread* AbstractThread::MainThread() {
  MOZ_ASSERT(sMainThread);
  return sMainThread;
}

void AbstractThread::InitTLS() {
  if (!sCurrentThreadTLS.init()) {
    MOZ_CRASH();
  }
}

void AbstractThread::InitMainThread() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!sMainThread);
  nsCOMPtr mainThread =
      do_QueryInterface(nsThreadManager::get().GetMainThreadWeak());
  MOZ_DIAGNOSTIC_ASSERT(mainThread);

  if (!sCurrentThreadTLS.init()) {
    MOZ_CRASH();
  }
  sMainThread = new XPCOMThreadWrapper(mainThread.get(),
                                       /* aRequireTailDispatch = */ true,
                                       true /* onThread */);
}

void AbstractThread::ShutdownMainThread() {
  MOZ_ASSERT(NS_IsMainThread());
  sMainThread = nullptr;
}

void AbstractThread::DispatchStateChange(
    already_AddRefed aRunnable) {
  AbstractThread* currentThread = GetCurrent();
  MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
  if (currentThread->IsTailDispatcherAvailable()) {
    currentThread->TailDispatcher().AddStateChangeTask(this,
                                                       std::move(aRunnable));
  } else {
    // If the tail dispatcher isn't available, we just avoid sending state
    // updates.
    //
    // This happens, specifically (1) During async shutdown (via the media
    // shutdown blocker), and (2) During the final shutdown cycle collection.
    // Both of these trigger changes to various watched and mirrored state.
    nsCOMPtr neverDispatched = aRunnable;
  }
}

/* static */
void AbstractThread::DispatchDirectTask(
    already_AddRefed aRunnable) {
  AbstractThread* currentThread = GetCurrent();
  MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist");
  if (currentThread->IsTailDispatcherAvailable()) {
    currentThread->TailDispatcher().AddDirectTask(std::move(aRunnable));
  } else {
    // If the tail dispatcher isn't available, we post as a regular task.
    currentThread->Dispatch(std::move(aRunnable));
  }
}

/* static */
already_AddRefed AbstractThread::CreateXPCOMThreadWrapper(
    nsIThread* aThread, bool aRequireTailDispatch, bool aOnThread) {
  nsCOMPtr internalThread = do_QueryInterface(aThread);
  MOZ_ASSERT(internalThread, "Need an nsThread for AbstractThread");
  RefPtr wrapper =
      new XPCOMThreadWrapper(internalThread, aRequireTailDispatch, aOnThread);

  bool onCurrentThread = false;
  Unused << aThread->IsOnCurrentThread(&onCurrentThread);

  if (onCurrentThread) {
    if (!aOnThread) {
      MOZ_ASSERT(!sCurrentThreadTLS.get(),
                 "There can only be a single XPCOMThreadWrapper available on a "
                 "thread");
      sCurrentThreadTLS.set(wrapper);
    }
    return wrapper.forget();
  }

  // Set the thread-local sCurrentThreadTLS to point to the wrapper on the
  // target thread. This ensures that sCurrentThreadTLS is as expected by
  // AbstractThread::GetCurrent() on the target thread.
  nsCOMPtr r = NS_NewRunnableFunction(
      "AbstractThread::CreateXPCOMThreadWrapper", [wrapper]() {
        MOZ_ASSERT(!sCurrentThreadTLS.get(),
                   "There can only be a single XPCOMThreadWrapper available on "
                   "a thread");
        sCurrentThreadTLS.set(wrapper);
      });
  aThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
  return wrapper.forget();
}
}  // namespace mozilla