Bug 1416724 - part 1 - AbstractThread::Dispatch should propage errors if failing the dispatching of Runnables, r=jwwang
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 15 Nov 2017 07:58:02 +0100
changeset 391968 430e5be1f7bb6c2ebd991bc5e2a2222b83d2fdf2
parent 391967 76d469663b3c54093aad44d58dd932cb659465a6
child 391969 64c4030422704671d85e17092cb4de01dc6c0528
push id32909
push usercbrindusan@mozilla.com
push dateWed, 15 Nov 2017 22:25:14 +0000
treeherdermozilla-central@f41930a869a8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwwang
bugs1416724
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1416724 - part 1 - AbstractThread::Dispatch should propage errors if failing the dispatching of Runnables, r=jwwang
xpcom/tests/gtest/TestMozPromise.cpp
xpcom/tests/gtest/TestStateWatching.cpp
xpcom/tests/gtest/TestTaskQueue.cpp
xpcom/threads/AbstractThread.cpp
xpcom/threads/AbstractThread.h
xpcom/threads/StateMirroring.h
xpcom/threads/TaskDispatcher.h
xpcom/threads/TaskQueue.cpp
xpcom/threads/TaskQueue.h
--- a/xpcom/tests/gtest/TestMozPromise.cpp
+++ b/xpcom/tests/gtest/TestMozPromise.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 
 #include "base/message_loop.h"
 
 #include "mozilla/TaskQueue.h"
 #include "mozilla/MozPromise.h"
+#include "mozilla/Unused.h"
 
 #include "nsISupportsImpl.h"
 #include "mozilla/SharedThreadPool.h"
 #include "VideoUtils.h"
 
 using namespace mozilla;
 
 typedef MozPromise<int, double, false> TestPromise;
@@ -55,22 +56,21 @@ public:
     MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
     if (!mPromise) {
       // Canceled.
       return NS_OK;
     }
 
     if (--mIterations == 0) {
       mPromise->ResolveOrReject(mValue, __func__);
-    } else {
-      nsCOMPtr<nsIRunnable> r = this;
-      mTaskQueue->Dispatch(r.forget());
+      return NS_OK;
     }
 
-    return NS_OK;
+    nsCOMPtr<nsIRunnable> r = this;
+    return mTaskQueue->Dispatch(r.forget());
   }
 
   void Cancel() {
     mPromise = nullptr;
   }
 
 protected:
   ~DelayedResolveOrReject() {}
@@ -82,17 +82,17 @@ private:
   int mIterations;
 };
 
 template<typename FunctionType>
 void
 RunOnTaskQueue(TaskQueue* aQueue, FunctionType aFun)
 {
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("RunOnTaskQueue", aFun);
-  aQueue->Dispatch(r.forget());
+  Unused << aQueue->Dispatch(r.forget());
 }
 
 // std::function can't come soon enough. :-(
 #define DO_FAIL []() { EXPECT_TRUE(false); return TestPromise::CreateAndReject(0, __func__); }
 
 TEST(MozPromise, BasicResolve)
 {
   AutoTaskQueue atq;
@@ -157,21 +157,21 @@ TEST(MozPromise, AsyncResolve)
     RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__);
 
     // Kick off three racing tasks, and make sure we get the one that finishes earliest.
     RefPtr<DelayedResolveOrReject> a = new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(32), 10);
     RefPtr<DelayedResolveOrReject> b = new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(42), 5);
     RefPtr<DelayedResolveOrReject> c = new DelayedResolveOrReject(queue, p, RRValue::MakeReject(32.0), 7);
 
     nsCOMPtr<nsIRunnable> ref = a.get();
-    queue->Dispatch(ref.forget());
+    Unused << queue->Dispatch(ref.forget());
     ref = b.get();
-    queue->Dispatch(ref.forget());
+    Unused << queue->Dispatch(ref.forget());
     ref = c.get();
-    queue->Dispatch(ref.forget());
+    Unused << queue->Dispatch(ref.forget());
 
     p->Then(queue, __func__, [queue, a, b, c] (int aResolveValue) -> void {
       EXPECT_EQ(aResolveValue, 42);
       a->Cancel();
       b->Cancel();
       c->Cancel();
       queue->BeginShutdown();
     }, DO_FAIL);
@@ -192,17 +192,17 @@ TEST(MozPromise, CompletionPromises)
            [&invokedPass] (int aVal) {
              invokedPass = true;
              return TestPromise::CreateAndResolve(aVal, __func__);
            }, DO_FAIL)
     ->Then(queue, __func__,
       [queue] (int aVal) -> RefPtr<TestPromise> {
         RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__);
         nsCOMPtr<nsIRunnable> resolver = new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(aVal - 8), 10);
-        queue->Dispatch(resolver.forget());
+        Unused << queue->Dispatch(resolver.forget());
         return RefPtr<TestPromise>(p);
       },
       DO_FAIL)
     ->Then(queue, __func__,
       [] (int aVal) -> RefPtr<TestPromise> { return TestPromise::CreateAndReject(double(aVal - 42) + 42.0, __func__); },
       DO_FAIL)
     ->Then(queue, __func__,
       DO_FAIL,
--- a/xpcom/tests/gtest/TestStateWatching.cpp
+++ b/xpcom/tests/gtest/TestStateWatching.cpp
@@ -3,16 +3,17 @@
 /* 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 "gtest/gtest.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/StateWatching.h"
 #include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
 #include "nsISupportsImpl.h"
 #include "VideoUtils.h"
 
 namespace TestStateWatching {
 
 using namespace mozilla;
 
 struct Foo {
@@ -27,17 +28,17 @@ TEST(WatchManager, Shutdown)
 {
   RefPtr<TaskQueue> queue = new TaskQueue(
     GetMediaThreadPool(MediaThreadType::PLAYBACK));
 
   RefPtr<Foo> p = new Foo;
   WatchManager<Foo> manager(p, queue);
   Watchable<bool> notifier(false, "notifier");
 
-  queue->Dispatch(NS_NewRunnableFunction(
+  Unused << queue->Dispatch(NS_NewRunnableFunction(
     "TestStateWatching::WatchManager_Shutdown_Test::TestBody", [&]() {
       manager.Watch(notifier, &Foo::Notify);
       notifier = true;    // Trigger the call to Foo::Notify().
       manager.Shutdown(); // Shutdown() should cancel the call.
     }));
 
   queue->BeginShutdown();
   queue->AwaitShutdownAndIdle();
--- a/xpcom/tests/gtest/TestTaskQueue.cpp
+++ b/xpcom/tests/gtest/TestTaskQueue.cpp
@@ -2,16 +2,17 @@
 /* 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 "gtest/gtest.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
 #include "VideoUtils.h"
 
 namespace TestTaskQueue {
 
 using namespace mozilla;
 
 TEST(TaskQueue, EventOrder)
 {
@@ -24,48 +25,47 @@ TEST(TaskQueue, EventOrder)
 
   bool errored = false;
   int counter = 0;
   int sync = 0;
   Monitor monitor("TaskQueue::EventOrder::monitor");
 
   // We expect task1 happens before task3.
   for (int i = 0; i < 10000; ++i) {
-    tq1->Dispatch(
+    Unused << tq1->Dispatch(
       NS_NewRunnableFunction(
         "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
         [&]() {
-          tq2->Dispatch(NS_NewRunnableFunction(
+          Unused << tq2->Dispatch(NS_NewRunnableFunction(
             "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
             []() { // task0
             }));
-          tq3->Dispatch(NS_NewRunnableFunction(
+          Unused << tq3->Dispatch(NS_NewRunnableFunction(
             "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
             [&]() { // task1
               EXPECT_EQ(1, ++counter);
               errored = counter != 1;
               MonitorAutoLock mon(monitor);
               ++sync;
               mon.Notify();
             }));
-          tq2->Dispatch(NS_NewRunnableFunction(
+          Unused << tq2->Dispatch(NS_NewRunnableFunction(
             "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
             [&]() { // task2
-              tq3->Dispatch(NS_NewRunnableFunction(
+              Unused << tq3->Dispatch(NS_NewRunnableFunction(
                 "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
                 [&]() { // task3
                   EXPECT_EQ(0, --counter);
                   errored = counter != 0;
                   MonitorAutoLock mon(monitor);
                   ++sync;
                   mon.Notify();
                 }));
             }));
         }),
-      AbstractThread::AssertDispatchSuccess,
       AbstractThread::TailDispatch);
 
     // Ensure task1 and task3 are done before next loop.
     MonitorAutoLock mon(monitor);
     while (sync != 2) {
       mon.Wait();
     }
     sync = 0;
--- a/xpcom/threads/AbstractThread.cpp
+++ b/xpcom/threads/AbstractThread.cpp
@@ -42,29 +42,25 @@ public:
     //
     // If you need to use tail dispatch on other XPCOM threads, you'll need to
     // implement an nsIThreadObserver to fire the tail dispatcher at the
     // appropriate times. You will also need to modify this assertion.
     MOZ_ASSERT_IF(aRequireTailDispatch, NS_IsMainThread() && aTarget->IsOnCurrentThread());
   }
 
   virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
-                            DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
                             DispatchReason aReason = NormalDispatch) override
   {
     AbstractThread* currentThread;
     if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) {
-      currentThread->TailDispatcher().AddTask(this, Move(aRunnable), aFailureHandling);
-      return NS_OK;
+      return currentThread->TailDispatcher().AddTask(this, Move(aRunnable));
     }
 
     RefPtr<nsIRunnable> runner(new Runner(this, Move(aRunnable), false /* already drained by TaskGroupRunnable  */));
-    nsresult rv = mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
-    MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv));
-    return rv;
+    return mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
   }
 
   // Prevent a GCC warning about the other overload of Dispatch being hidden.
   using AbstractThread::Dispatch;
 
   virtual bool IsCurrentThreadIn() override
   {
     return mTarget->IsOnCurrentThread();
@@ -218,33 +214,34 @@ AbstractThread::DispatchFromScript(nsIRu
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
   return Dispatch(event.forget(), aFlags);
 }
 
 NS_IMETHODIMP
 AbstractThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
 {
-  Dispatch(Move(aEvent), DontAssertDispatchSuccess, NormalDispatch);
-  return NS_OK;
+  return Dispatch(Move(aEvent), NormalDispatch);
 }
 
 NS_IMETHODIMP
 AbstractThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
                                  uint32_t aDelayMs)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-void
+nsresult
 AbstractThread::TailDispatchTasksFor(AbstractThread* aThread)
 {
   if (MightHaveTailTasks()) {
-    TailDispatcher().DispatchTasksFor(aThread);
+    return TailDispatcher().DispatchTasksFor(aThread);
   }
+
+  return NS_OK;
 }
 
 bool
 AbstractThread::HasTailTasksFor(AbstractThread* aThread)
 {
   if (!MightHaveTailTasks()) {
     return false;
   }
--- a/xpcom/threads/AbstractThread.h
+++ b/xpcom/threads/AbstractThread.h
@@ -62,20 +62,18 @@ public:
   // |flags| parameter from Dispatch. Otherwise, a single-argument Dispatch call
   // would be ambiguous.
   NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override;
   NS_IMETHOD IsOnCurrentThread(bool *_retval) override;
   NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable> event, uint32_t flags) override;
   NS_IMETHOD DispatchFromScript(nsIRunnable *event, uint32_t flags) override;
   NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable> event, uint32_t delay) override;
 
-  enum DispatchFailureHandling { AssertDispatchSuccess, DontAssertDispatchSuccess };
   enum DispatchReason { NormalDispatch, TailDispatch };
   virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
-                            DispatchFailureHandling aHandling = AssertDispatchSuccess,
                             DispatchReason aReason = NormalDispatch) = 0;
 
   virtual bool IsCurrentThreadIn() = 0;
 
   // Returns a TaskDispatcher that will dispatch its tasks when the currently-
   // running tasks pops off the stack.
   //
   // May only be called when running within the it is invoked up, and only on
@@ -84,17 +82,17 @@ public:
 
   // Returns true if we have tail tasks scheduled, or if this isn't known.
   // Returns false if we definitely don't have any tail tasks.
   virtual bool MightHaveTailTasks() { return true; }
 
   // Helper functions for methods on the tail TasklDispatcher. These check
   // HasTailTasks to avoid allocating a TailDispatcher if it isn't
   // needed.
-  void TailDispatchTasksFor(AbstractThread* aThread);
+  nsresult TailDispatchTasksFor(AbstractThread* aThread);
   bool HasTailTasksFor(AbstractThread* aThread);
 
   // Returns true if this supports the tail dispatcher.
   bool SupportsTailDispatch() const { return mSupportsTailDispatch; }
 
   // Returns true if this thread requires all dispatches originating from
   // aThread go through the tail dispatcher.
   bool RequiresTailDispatch(AbstractThread* aThread) const;
--- a/xpcom/threads/StateMirroring.h
+++ b/xpcom/threads/StateMirroring.h
@@ -150,18 +150,17 @@ private:
 
     void DisconnectAll()
     {
       MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this);
       for (size_t i = 0; i < mMirrors.Length(); ++i) {
         mMirrors[i]->OwnerThread()->Dispatch(
           NewRunnableMethod("AbstractMirror::NotifyDisconnected",
                             mMirrors[i],
-                            &AbstractMirror<T>::NotifyDisconnected),
-          AbstractThread::DontAssertDispatchSuccess);
+                            &AbstractMirror<T>::NotifyDisconnected));
       }
       mMirrors.Clear();
     }
 
     operator const T&()
     {
       MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
       return mValue;
@@ -333,17 +332,17 @@ private:
       MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aCanonical->OwnerThread()), "Can't get coherency without tail dispatch");
 
       nsCOMPtr<nsIRunnable> r =
         NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>(
           "AbstractCanonical::AddMirror",
           aCanonical,
           &AbstractCanonical<T>::AddMirror,
           this);
-      aCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
+      aCanonical->OwnerThread()->Dispatch(r.forget());
       mCanonical = aCanonical;
     }
   public:
 
     void DisconnectIfConnected()
     {
       MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
       if (!IsConnected()) {
@@ -352,17 +351,17 @@ private:
 
       MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, mCanonical.get());
       nsCOMPtr<nsIRunnable> r =
         NewRunnableMethod<StoreRefPtrPassByPtr<AbstractMirror<T>>>(
           "AbstractCanonical::RemoveMirror",
           mCanonical,
           &AbstractCanonical<T>::RemoveMirror,
           this);
-      mCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
+      mCanonical->OwnerThread()->Dispatch(r.forget());
       mCanonical = nullptr;
     }
 
   protected:
     ~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); }
 
   private:
     T mValue;
--- a/xpcom/threads/TaskDispatcher.h
+++ b/xpcom/threads/TaskDispatcher.h
@@ -49,21 +49,20 @@ public:
   // State change tasks are dispatched asynchronously always run before regular
   // tasks. They are intended to be used to update the value held by mirrors
   // before any other dispatched tasks are run on the target thread.
   virtual void AddStateChangeTask(AbstractThread* aThread,
                                   already_AddRefed<nsIRunnable> aRunnable) = 0;
 
   // Regular tasks are dispatched asynchronously, and run after state change
   // tasks.
-  virtual void AddTask(AbstractThread* aThread,
-                       already_AddRefed<nsIRunnable> aRunnable,
-                       AbstractThread::DispatchFailureHandling aFailureHandling = AbstractThread::AssertDispatchSuccess) = 0;
+  virtual nsresult AddTask(AbstractThread* aThread,
+                           already_AddRefed<nsIRunnable> aRunnable) = 0;
 
-  virtual void DispatchTasksFor(AbstractThread* aThread) = 0;
+  virtual nsresult DispatchTasksFor(AbstractThread* aThread) = 0;
   virtual bool HasTasksFor(AbstractThread* aThread) = 0;
   virtual void DrainDirectTasks() = 0;
 };
 
 /*
  * AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires
  * its queued tasks when it is popped off the stack.
  */
@@ -117,74 +116,80 @@ public:
   void AddStateChangeTask(AbstractThread* aThread,
                           already_AddRefed<nsIRunnable> aRunnable) override
   {
     nsCOMPtr<nsIRunnable> r = aRunnable;
     MOZ_RELEASE_ASSERT(r);
     EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(r.forget());
   }
 
-  void AddTask(AbstractThread* aThread,
-               already_AddRefed<nsIRunnable> aRunnable,
-               AbstractThread::DispatchFailureHandling aFailureHandling) override
+  nsresult AddTask(AbstractThread* aThread,
+                   already_AddRefed<nsIRunnable> aRunnable) override
   {
     nsCOMPtr<nsIRunnable> r = aRunnable;
     MOZ_RELEASE_ASSERT(r);
     // To preserve the event order, we need to append a new group if the last
     // group is not targeted for |aThread|.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318226&mark=0-3#c0
     // for the details of the issue.
     if (mTaskGroups.Length() == 0 || mTaskGroups.LastElement()->mThread != aThread) {
       mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread));
     }
 
     PerThreadTaskGroup& group = *mTaskGroups.LastElement();
     group.mRegularTasks.AppendElement(r.forget());
 
-    // The task group needs to assert dispatch success if any of the runnables
-    // it's dispatching want to assert it.
-    if (aFailureHandling == AbstractThread::AssertDispatchSuccess) {
-      group.mFailureHandling = AbstractThread::AssertDispatchSuccess;
-    }
+    return NS_OK;
   }
 
   bool HasTasksFor(AbstractThread* aThread) override
   {
     return !!GetTaskGroup(aThread) ||
            (aThread == AbstractThread::GetCurrent() && HaveDirectTasks());
   }
 
-  void DispatchTasksFor(AbstractThread* aThread) override
+  nsresult DispatchTasksFor(AbstractThread* aThread) override
   {
+    nsresult rv = NS_OK;
+
     // Dispatch all groups that match |aThread|.
     for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
       if (mTaskGroups[i]->mThread == aThread) {
-        DispatchTaskGroup(Move(mTaskGroups[i]));
+        nsresult rv2 = DispatchTaskGroup(Move(mTaskGroups[i]));
+
+        if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) {
+          // We should try our best to call DispatchTaskGroup() as much as
+          // possible and return an error if any of DispatchTaskGroup() calls
+          // failed.
+          rv = rv2;
+        }
+
         mTaskGroups.RemoveElementAt(i--);
       }
     }
+
+    return rv;
   }
 
 private:
 
   struct PerThreadTaskGroup
   {
   public:
     explicit PerThreadTaskGroup(AbstractThread* aThread)
-      : mThread(aThread), mFailureHandling(AbstractThread::DontAssertDispatchSuccess)
+      : mThread(aThread)
     {
       MOZ_COUNT_CTOR(PerThreadTaskGroup);
     }
 
     ~PerThreadTaskGroup() { MOZ_COUNT_DTOR(PerThreadTaskGroup); }
 
     RefPtr<AbstractThread> mThread;
     nsTArray<nsCOMPtr<nsIRunnable>> mStateChangeTasks;
     nsTArray<nsCOMPtr<nsIRunnable>> mRegularTasks;
-    AbstractThread::DispatchFailureHandling mFailureHandling;
   };
 
   class TaskGroupRunnable : public Runnable
   {
     public:
       explicit TaskGroupRunnable(UniquePtr<PerThreadTaskGroup>&& aTasks)
         : Runnable("AutoTaskDispatcher::TaskGroupRunnable")
         , mTasks(Move(aTasks))
@@ -245,25 +250,24 @@ private:
         return mTaskGroups[i].get();
       }
     }
 
     // Not found.
     return nullptr;
   }
 
-  void DispatchTaskGroup(UniquePtr<PerThreadTaskGroup> aGroup)
+  nsresult DispatchTaskGroup(UniquePtr<PerThreadTaskGroup> aGroup)
   {
     RefPtr<AbstractThread> thread = aGroup->mThread;
 
-    AbstractThread::DispatchFailureHandling failureHandling = aGroup->mFailureHandling;
     AbstractThread::DispatchReason reason = mIsTailDispatcher ? AbstractThread::TailDispatch
                                                               : AbstractThread::NormalDispatch;
     nsCOMPtr<nsIRunnable> r = new TaskGroupRunnable(Move(aGroup));
-    thread->Dispatch(r.forget(), failureHandling, reason);
+    return thread->Dispatch(r.forget(), reason);
   }
 
   // Direct tasks. We use a Maybe<> because (a) this class is hot, (b)
   // mDirectTasks often doesn't get anything put into it, and (c) the
   // std::queue implementation in GNU libstdc++ does two largish heap
   // allocations when creating a new std::queue.
   mozilla::Maybe<std::queue<nsCOMPtr<nsIRunnable>>> mDirectTasks;
 
--- a/xpcom/threads/TaskQueue.cpp
+++ b/xpcom/threads/TaskQueue.cpp
@@ -34,17 +34,16 @@ public:
   }
 
   NS_IMETHOD
   Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override
   {
     nsCOMPtr<nsIRunnable> runnable = aEvent;
     MonitorAutoLock mon(mTaskQueue->mQueueMonitor);
     return mTaskQueue->DispatchLocked(/* passed by ref */runnable,
-                                      DontAssertDispatchSuccess,
                                       NormalDispatch);
   }
 
   NS_IMETHOD
   DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t aFlags) override
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
@@ -101,28 +100,26 @@ TaskQueue::TailDispatcher()
   MOZ_ASSERT(mTailDispatcher);
   return *mTailDispatcher;
 }
 
 // Note aRunnable is passed by ref to support conditional ownership transfer.
 // See Dispatch() in TaskQueue.h for more details.
 nsresult
 TaskQueue::DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
-                          DispatchFailureHandling aFailureHandling,
                           DispatchReason aReason)
 {
   mQueueMonitor.AssertCurrentThreadOwns();
   if (mIsShutdown) {
     return NS_ERROR_FAILURE;
   }
 
   AbstractThread* currentThread;
   if (aReason != TailDispatch && (currentThread = GetCurrent()) && RequiresTailDispatch(currentThread)) {
-    currentThread->TailDispatcher().AddTask(this, aRunnable.forget(), aFailureHandling);
-    return NS_OK;
+    return currentThread->TailDispatcher().AddTask(this, aRunnable.forget());
   }
 
   mTasks.push(aRunnable.forget());
   if (mIsRunning) {
     return NS_OK;
   }
   RefPtr<nsIRunnable> runner(new Runner(this));
   nsresult rv = mTarget->Dispatch(runner.forget(), NS_DISPATCH_NORMAL);
--- a/xpcom/threads/TaskQueue.h
+++ b/xpcom/threads/TaskQueue.h
@@ -56,30 +56,24 @@ public:
   TaskQueue(already_AddRefed<nsIEventTarget> aTarget,
             const char* aName,
             bool aSupportsTailDispatch = false);
 
   TaskDispatcher& TailDispatcher() override;
 
   TaskQueue* AsTaskQueue() override { return this; }
 
-  nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
-                    DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
-                    DispatchReason aReason = NormalDispatch) override
+  MOZ_MUST_USE nsresult
+  Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+           DispatchReason aReason = NormalDispatch) override
   {
     nsCOMPtr<nsIRunnable> r = aRunnable;
     {
       MonitorAutoLock mon(mQueueMonitor);
-      nsresult rv = DispatchLocked(/* passed by ref */r, aFailureHandling, aReason);
-#if defined(DEBUG) || !defined(RELEASE_OR_BETA) || defined(EARLY_BETA_OR_EARLIER)
-      if (NS_FAILED(rv) && aFailureHandling == AssertDispatchSuccess) {
-        MOZ_CRASH_UNSAFE_PRINTF("%s: Dispatch failed. rv=%x", mName, uint32_t(rv));
-      }
-#endif
-      return rv;
+      return DispatchLocked(/* passed by ref */r, aReason);
     }
     // If the ownership of |r| is not transferred in DispatchLocked() due to
     // dispatch failure, it will be deleted here outside the lock. We do so
     // since the destructor of the runnable might access TaskQueue and result
     // in deadlocks.
   }
 
   // Prevent a GCC warning about the other overload of Dispatch being hidden.
@@ -116,17 +110,16 @@ protected:
 
 
   // Blocks until all task finish executing. Called internally by methods
   // that need to wait until the task queue is idle.
   // mQueueMonitor must be held.
   void AwaitIdleLocked();
 
   nsresult DispatchLocked(nsCOMPtr<nsIRunnable>& aRunnable,
-                          DispatchFailureHandling aFailureHandling,
                           DispatchReason aReason = NormalDispatch);
 
   void MaybeResolveShutdown()
   {
     mQueueMonitor.AssertCurrentThreadOwns();
     if (mIsShutdown && !mIsRunning) {
       mShutdownPromise.ResolveIfExists(true, __func__);
       mTarget = nullptr;