Bug 1151656 - Use TailDispatch for the MDSM's task queue. r=mattwoodrow
authorBobby Holley <bobbyholley@gmail.com>
Tue, 07 Apr 2015 12:20:43 -0700
changeset 268149 8c22476cabcae21cb93d8680f3663009f65843a6
parent 268148 8b257d9772718afe34a7fd8896e48cd28b298182
child 268150 a3f6306dd05e64ea03f427652680151f89357e99
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1151656
milestone40.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 1151656 - Use TailDispatch for the MDSM's task queue. r=mattwoodrow
dom/media/AbstractThread.cpp
dom/media/AbstractThread.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaQueue.h
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
--- a/dom/media/AbstractThread.cpp
+++ b/dom/media/AbstractThread.cpp
@@ -29,16 +29,31 @@ template<>
 bool
 AbstractThreadImpl<nsIThread>::IsCurrentThreadIn()
 {
   bool in = NS_GetCurrentThread() == mTarget;
   MOZ_ASSERT_IF(in, MediaTaskQueue::GetCurrentQueue() == nullptr);
   return in;
 }
 
+void
+AbstractThread::MaybeTailDispatch(already_AddRefed<nsIRunnable> aRunnable,
+                                  bool aAssertDispatchSuccess)
+{
+  MediaTaskQueue* currentQueue = MediaTaskQueue::GetCurrentQueue();
+  if (currentQueue && currentQueue->RequiresTailDispatch()) {
+    currentQueue->TailDispatcher().AddTask(this, Move(aRunnable), aAssertDispatchSuccess);
+  } else {
+    nsresult rv = Dispatch(Move(aRunnable));
+    MOZ_DIAGNOSTIC_ASSERT(!aAssertDispatchSuccess || NS_SUCCEEDED(rv));
+    unused << rv;
+  }
+}
+
+
 AbstractThread*
 AbstractThread::MainThread()
 {
   MOZ_ASSERT(sMainThread);
   return sMainThread;
 }
 
 void
--- a/dom/media/AbstractThread.h
+++ b/dom/media/AbstractThread.h
@@ -30,16 +30,21 @@ namespace mozilla {
  */
 class AbstractThread
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractThread);
   virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable) = 0;
   virtual bool IsCurrentThreadIn() = 0;
 
+  // Convenience method for dispatching a runnable when we may be running on
+  // a thread that requires runnables to be dispatched with tail dispatch.
+  void MaybeTailDispatch(already_AddRefed<nsIRunnable> aRunnable,
+                         bool aAssertDispatchSuccess = true);
+
   template<typename TargetType> static AbstractThread* Create(TargetType* aTarget);
 
   // Convenience method for getting an AbstractThread for the main thread.
   static AbstractThread* MainThread();
 
   // Must be called exactly once during startup.
   static void InitStatics();
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -42,16 +42,18 @@
 #include <algorithm>
 
 namespace mozilla {
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
+#define NS_DispatchToMainThread(...) CompileError_UseTailDispatchInstead
+
 // avoid redefined macro in unified build
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(x, ...) \
   PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, ("Decoder=%p " x, mDecoder.get(), ##__VA_ARGS__))
@@ -247,17 +249,17 @@ MediaDecoderStateMachine::MediaDecoderSt
   mSentPlaybackEndedEvent(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
   // Set up our task queue.
   RefPtr<SharedThreadPool> pool(GetMediaThreadPool());
   MOZ_DIAGNOSTIC_ASSERT(pool);
-  mTaskQueue = new MediaTaskQueue(pool.forget());
+  mTaskQueue = new MediaTaskQueue(pool.forget(), /* aAssertTailDispatch = */ true);
 
   static bool sPrefCacheInit = false;
   if (!sPrefCacheInit) {
     sPrefCacheInit = true;
     Preferences::AddUintVarCache(&sVideoQueueDefaultSize,
                                  "media.video-queue.default-size",
                                  MAX_VIDEO_QUEUE_SIZE);
     Preferences::AddUintVarCache(&sVideoQueueHWAccelSize,
@@ -857,20 +859,22 @@ MediaDecoderStateMachine::OnNotDecoded(M
   }
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
     MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
     WaitRequestRef(aType).Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
-                                               &MediaDecoderReader::WaitForData, aType)
+                                               &MediaDecoderReader::WaitForData, aType,
+                                               TailDispatcher())
       ->RefableThen(TaskQueue(), __func__, this,
                     &MediaDecoderStateMachine::OnWaitForDataResolved,
-                    &MediaDecoderStateMachine::OnWaitForDataRejected));
+                    &MediaDecoderStateMachine::OnWaitForDataRejected,
+                    TailDispatcher()));
     return;
   }
 
   if (aReason == MediaDecoderReader::CANCELED) {
     DispatchDecodeTasksIfNeeded();
     return;
   }
 
@@ -1078,22 +1082,19 @@ MediaDecoderStateMachine::CheckIfSeekCom
     }
   }
 
   SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
              audioSeekComplete, videoSeekComplete);
 
   if (audioSeekComplete && videoSeekComplete) {
     mDecodeToSeekTarget = false;
-    RefPtr<nsIRunnable> task(
+    nsCOMPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
-    nsresult rv = TaskQueue()->Dispatch(task);
-    if (NS_FAILED(rv)) {
-      DecodeError();
-    }
+    TailDispatch(TaskQueue(), task.forget());
   }
 }
 
 bool
 MediaDecoderStateMachine::IsAudioDecoding()
 {
   AssertCurrentThreadInMonitor();
   return HasAudio() && !AudioQueue().IsFinished();
@@ -1224,34 +1225,34 @@ void MediaDecoderStateMachine::UpdatePla
   NS_ASSERTION(mCurrentFrameTime >= 0, "CurrentTime should be positive!");
   if (aTime > mEndTime) {
     NS_ASSERTION(mCurrentFrameTime > GetDuration(),
                  "CurrentTime must be after duration if aTime > endTime!");
     DECODER_LOG("Setting new end time to %lld", aTime);
     mEndTime = aTime;
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
-    NS_DispatchToMainThread(event);
+    TailDispatch(AbstractThread::MainThread(), event.forget());
   }
 }
 
 void MediaDecoderStateMachine::UpdatePlaybackPosition(int64_t aTime)
 {
   MOZ_ASSERT(OnTaskQueue());
   UpdatePlaybackPositionInternal(aTime);
 
   bool fragmentEnded = mFragmentEndTime >= 0 && GetMediaTime() >= mFragmentEndTime;
   if (!mPositionChangeQueued || fragmentEnded) {
     mPositionChangeQueued = true;
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
         mDecoder,
         &MediaDecoder::PlaybackPositionChanged,
         MediaDecoderEventVisibility::Observable);
-    NS_DispatchToMainThread(event);
+    TailDispatch(AbstractThread::MainThread(), event.forget());
   }
 
   mMetadataManager.DispatchMetadataIfNeeded(mDecoder, aTime);
 
   if (fragmentEnded) {
     StopPlayback();
   }
 }
@@ -1404,31 +1405,31 @@ void MediaDecoderStateMachine::SetDurati
     // resource.
     if (NS_IsMainThread()) {
       // Seek synchronously.
       mDecoder->Seek(double(mEndTime) / USECS_PER_S, SeekTarget::Accurate);
     } else {
       // Queue seek to new end position.
       nsCOMPtr<nsIRunnable> task =
         new SeekRunnable(mDecoder, double(mEndTime) / USECS_PER_S);
-      NS_DispatchToMainThread(task);
+      AbstractThread::MainThread()->MaybeTailDispatch(task.forget());
     }
   }
 }
 
 void MediaDecoderStateMachine::UpdateEstimatedDuration(int64_t aDuration)
 {
   AssertCurrentThreadInMonitor();
   int64_t duration = GetDuration();
   if (aDuration != duration &&
       mozilla::Abs(aDuration - duration) > ESTIMATED_DURATION_FUZZ_FACTOR_USECS) {
     SetDuration(aDuration);
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::DurationChanged);
-    NS_DispatchToMainThread(event);
+    AbstractThread::MainThread()->MaybeTailDispatch(event.forget());
   }
 }
 
 void MediaDecoderStateMachine::SetMediaEndTime(int64_t aEndTime)
 {
   MOZ_ASSERT(OnDecodeTaskQueue());
   AssertCurrentThreadInMonitor();
 
@@ -1481,34 +1482,33 @@ void MediaDecoderStateMachine::SetDorman
     } else {
       mQueuedSeek.mTarget = SeekTarget(mCurrentFrameTime,
                                        SeekTarget::Accurate,
                                        MediaDecoderEventVisibility::Suppressed);
       // XXXbholley - Nobody is listening to this promise. Do we need to pass it
       // back to MediaDecoder when we come out of dormant?
       nsRefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
     }
-    mPendingSeek.RejectIfExists(__func__);
-    mCurrentSeek.RejectIfExists(__func__);
+    mPendingSeek.RejectIfExists(__func__, TailDispatcher());
+    mCurrentSeek.RejectIfExists(__func__, TailDispatcher());
     SetState(DECODER_STATE_DORMANT);
     if (IsPlaying()) {
       StopPlayback();
     }
 
     Reset();
 
     // Note that we do not wait for the decode task queue to go idle before
     // queuing the ReleaseMediaResources task - instead, we disconnect promises,
     // reset state, and put a ResetDecode in the decode task queue. Any tasks
     // that run after ResetDecode are supposed to run with a clean slate. We rely
     // on that in other places (i.e. seeking), so it seems reasonable to rely on
     // it here as well.
-    DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(
-    NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources));
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources);
+    TailDispatch(DecodeTaskQueue(), r.forget());
     // There's now no possibility of mPendingWakeDecoder being needed again. Revoke it.
     mPendingWakeDecoder = nullptr;
     mDecoder->GetReentrantMonitor().NotifyAll();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
     mDecodingFrozenAtStateDecoding = true;
     ScheduleStateMachine();
     mCurrentFrameTime = 0;
     SetState(DECODER_STATE_DECODING_NONE);
@@ -1667,22 +1667,22 @@ MediaDecoderStateMachine::Seek(SeekTarge
     return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
   }
 
   NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA,
                "We should have got duration already");
 
   if (mState < DECODER_STATE_DECODING) {
     DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
-    mQueuedSeek.RejectIfExists(__func__);
+    mQueuedSeek.RejectIfExists(__func__, TailDispatcher());
     mQueuedSeek.mTarget = aTarget;
     return mQueuedSeek.mPromise.Ensure(__func__);
   }
-  mQueuedSeek.RejectIfExists(__func__);
-  mPendingSeek.RejectIfExists(__func__);
+  mQueuedSeek.RejectIfExists(__func__, TailDispatcher());
+  mPendingSeek.RejectIfExists(__func__, TailDispatcher());
   mPendingSeek.mTarget = aTarget;
 
   DECODER_LOG("Changed state to SEEKING (to %lld)", mPendingSeek.mTarget.mTime);
   SetState(DECODER_STATE_SEEKING);
   ScheduleStateMachine();
 
   return mPendingSeek.mPromise.Ensure(__func__);
 }
@@ -1717,20 +1717,19 @@ void MediaDecoderStateMachine::StopAudio
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
 
-  RefPtr<nsIRunnable> task(
+  nsCOMPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
-  nsresult rv = TaskQueue()->Dispatch(task);
-  NS_ENSURE_SUCCESS(rv, rv);
+  TailDispatch(TaskQueue(), task.forget());
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
   MOZ_ASSERT(OnDecodeTaskQueue());
   DECODER_LOG("Invoking SetReaderIdle()");
@@ -1792,32 +1791,29 @@ MediaDecoderStateMachine::DispatchDecode
   if (needToDecodeVideo) {
     EnsureVideoDecodeTaskQueued();
   }
 
   if (needIdle) {
     DECODER_LOG("Dispatching SetReaderIdle() audioQueue=%lld videoQueue=%lld",
                 GetDecodedAudioDuration(),
                 VideoQueue().Duration());
-    RefPtr<nsIRunnable> event = NS_NewRunnableMethod(
+    nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(
         this, &MediaDecoderStateMachine::SetReaderIdle);
-    nsresult rv = DecodeTaskQueue()->Dispatch(event.forget());
-    if (NS_FAILED(rv) && !IsShutdown()) {
-      DECODER_WARN("Failed to dispatch event to set decoder idle state");
-    }
+    TailDispatch(DecodeTaskQueue(), task.forget());
   }
 }
 
 void
 MediaDecoderStateMachine::InitiateSeek()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
-  mCurrentSeek.RejectIfExists(__func__);
+  mCurrentSeek.RejectIfExists(__func__, TailDispatcher());
   mCurrentSeek.Steal(mPendingSeek);
 
   // Bound the seek time to be inside the media range.
   int64_t end = GetEndTime();
   NS_ASSERTION(mStartTime != -1, "Should know start time by now");
   NS_ASSERTION(end != -1, "Should know end time by now");
   int64_t seekTime = mCurrentSeek.mTarget.mTime + mStartTime;
   seekTime = std::min(seekTime, end);
@@ -1831,17 +1827,17 @@ MediaDecoderStateMachine::InitiateSeek()
     // for audio thread since it is until then we know which position we seek to
     // as far as fast-seek is concerned. It also fix the problem where stream
     // clock seems to go backwards during seeking.
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethodWithArgs<int64_t, MediaStreamGraph*>(mDecoder,
                                                                &MediaDecoder::RecreateDecodedStream,
                                                                seekTime - mStartTime,
                                                                nullptr);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    TailDispatch(AbstractThread::MainThread(), event.forget());
   }
 
   mDropAudioUntilNextDiscontinuity = HasAudio();
   mDropVideoUntilNextDiscontinuity = HasVideo();
 
   mDecoder->StopProgressUpdates();
   mCurrentTimeBeforeSeek = GetMediaTime();
 
@@ -1854,28 +1850,29 @@ MediaDecoderStateMachine::InitiateSeek()
   // SeekingStarted will do a UpdateReadyStateForData which will
   // inform the element and its users that we have no frames
   // to display
   nsCOMPtr<nsIRunnable> startEvent =
       NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
         mDecoder,
         &MediaDecoder::SeekingStarted,
         mCurrentSeek.mTarget.mEventVisibility);
-  NS_DispatchToMainThread(startEvent, NS_DISPATCH_NORMAL);
+  TailDispatch(AbstractThread::MainThread(), startEvent.forget());
 
   // Reset our state machine and decoding pipeline before seeking.
   Reset();
 
   // Do the seek.
   mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                     &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
-                                    GetEndTime())
+                                    GetEndTime(), TailDispatcher())
     ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnSeekCompleted,
-                  &MediaDecoderStateMachine::OnSeekFailed));
+                  &MediaDecoderStateMachine::OnSeekFailed,
+                  TailDispatcher()));
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
@@ -1910,20 +1907,22 @@ MediaDecoderStateMachine::EnsureAudioDec
       mAudioWaitRequest.Exists() || mSeekRequest.Exists()) {
     return NS_OK;
   }
 
   SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o",
              AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
 
   mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
-                                         __func__, &MediaDecoderReader::RequestAudioData)
+                                         __func__, &MediaDecoderReader::RequestAudioData,
+                                         TailDispatcher())
     ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnAudioDecoded,
-                  &MediaDecoderStateMachine::OnAudioNotDecoded));
+                  &MediaDecoderStateMachine::OnAudioNotDecoded,
+                  TailDispatcher()));
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1970,20 +1969,21 @@ MediaDecoderStateMachine::EnsureVideoDec
   mVideoDecodeStartTime = TimeStamp::Now();
 
   SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
              VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
              currentTime);
 
   mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                          &MediaDecoderReader::RequestVideoData,
-                                         skipToNextKeyFrame, currentTime)
+                                         skipToNextKeyFrame, currentTime, TailDispatcher())
     ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnVideoDecoded,
-                  &MediaDecoderStateMachine::OnVideoNotDecoded));
+                  &MediaDecoderStateMachine::OnVideoNotDecoded,
+                  TailDispatcher()));
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
 {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
@@ -2108,17 +2108,17 @@ MediaDecoderStateMachine::DecodeError()
   // XXXbholley - Is anybody actually waiting on this monitor, or is it just
   // a leftover from when we used to do sync dispatch for the below?
   mDecoder->GetReentrantMonitor().NotifyAll();
 
   // MediaDecoder::DecodeError notifies the owner, and then shuts down the state
   // machine.
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  TailDispatch(AbstractThread::MainThread(), event.forget());
 }
 
 void
 MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
@@ -2179,32 +2179,32 @@ MediaDecoderStateMachine::EnqueueLoadedM
   MOZ_ASSERT(OnTaskQueue());
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
   MediaDecoderEventVisibility visibility = mSentLoadedMetadataEvent?
                                     MediaDecoderEventVisibility::Suppressed :
                                     MediaDecoderEventVisibility::Observable;
   nsCOMPtr<nsIRunnable> metadataLoadedEvent =
     new MetadataEventRunner(mDecoder, info, mMetadataTags, visibility);
-  NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
+  TailDispatch(AbstractThread::MainThread(), metadataLoadedEvent.forget());
   mSentLoadedMetadataEvent = true;
 }
 
 void
 MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent()
 {
   MOZ_ASSERT(OnTaskQueue());
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
   MediaDecoderEventVisibility visibility = mSentFirstFrameLoadedEvent?
                                     MediaDecoderEventVisibility::Suppressed :
                                     MediaDecoderEventVisibility::Observable;
   nsCOMPtr<nsIRunnable> event =
     new FirstFrameLoadedEventRunner(mDecoder, info, visibility);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  TailDispatch(AbstractThread::MainThread(), event.forget());
   mSentFirstFrameLoadedEvent = true;
 }
 
 void
 MediaDecoderStateMachine::CallDecodeFirstFrame()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@@ -2245,28 +2245,32 @@ MediaDecoderStateMachine::DecodeFirstFra
     // the first samples in order to determine the media start time,
     // we have the start time from last time we loaded.
     SetStartTime(mStartTime);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
-                                             __func__, &MediaDecoderReader::RequestAudioData)
+                                             __func__, &MediaDecoderReader::RequestAudioData,
+                                             TailDispatcher())
         ->RefableThen(TaskQueue(), __func__, this,
                       &MediaDecoderStateMachine::OnAudioDecoded,
-                      &MediaDecoderStateMachine::OnAudioNotDecoded));
+                      &MediaDecoderStateMachine::OnAudioNotDecoded,
+                      TailDispatcher()));
     }
     if (HasVideo()) {
       mVideoDecodeStartTime = TimeStamp::Now();
       mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
-                                             __func__, &MediaDecoderReader::RequestVideoData, false, int64_t(0))
+                                             __func__, &MediaDecoderReader::RequestVideoData, false,
+                                             int64_t(0), TailDispatcher())
         ->RefableThen(TaskQueue(), __func__, this,
                       &MediaDecoderStateMachine::OnVideoDecoded,
-                      &MediaDecoderStateMachine::OnVideoNotDecoded));
+                      &MediaDecoderStateMachine::OnVideoNotDecoded,
+                      TailDispatcher()));
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::FinishDecodeFirstFrame()
@@ -2438,25 +2442,25 @@ MediaDecoderStateMachine::SeekCompleted(
   // Try to decode another frame to detect if we're at the end...
   DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
 
   // Reset quick buffering status. This ensures that if we began the
   // seek while quick-buffering, we won't bypass quick buffering mode
   // if we need to buffer after the seek.
   mQuickBuffering = false;
 
-  mCurrentSeek.Resolve(mState == DECODER_STATE_COMPLETED, __func__);
+  mCurrentSeek.Resolve(mState == DECODER_STATE_COMPLETED, __func__, TailDispatcher());
   ScheduleStateMachine();
 
   if (video) {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     RenderVideoFrame(video, TimeStamp::Now());
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+    TailDispatch(AbstractThread::MainThread(), event.forget());
   }
 }
 
 class DecoderDisposer
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecoderDisposer)
   DecoderDisposer(MediaDecoder* aDecoder, MediaDecoderStateMachine* aStateMachine)
@@ -2519,17 +2523,18 @@ MediaDecoderStateMachine::FinishShutdown
   // finished and released its monitor/references. That event then will
   // dispatch an event to the main thread to release the decoder and
   // state machine.
   DECODER_LOG("Shutting down state machine task queue");
   RefPtr<DecoderDisposer> disposer = new DecoderDisposer(mDecoder, this);
   TaskQueue()->BeginShutdown()->Then(AbstractThread::MainThread(), __func__,
                                      disposer.get(),
                                      &DecoderDisposer::OnTaskQueueShutdown,
-                                     &DecoderDisposer::OnTaskQueueShutdown);
+                                     &DecoderDisposer::OnTaskQueueShutdown,
+                                     TailDispatcher());
 }
 
 nsresult MediaDecoderStateMachine::RunStateMachine()
 {
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mDelayedScheduler.Reset(); // Must happen on state machine task queue.
@@ -2545,32 +2550,31 @@ nsresult MediaDecoderStateMachine::RunSt
 
   switch (mState) {
     case DECODER_STATE_ERROR: {
       // Just wait for MediaDecoder::DecodeError to shut us down.
       return NS_OK;
     }
 
     case DECODER_STATE_SHUTDOWN: {
-      mQueuedSeek.RejectIfExists(__func__);
-      mPendingSeek.RejectIfExists(__func__);
-      mCurrentSeek.RejectIfExists(__func__);
+      mQueuedSeek.RejectIfExists(__func__, TailDispatcher());
+      mPendingSeek.RejectIfExists(__func__, TailDispatcher());
+      mCurrentSeek.RejectIfExists(__func__, TailDispatcher());
 
       if (IsPlaying()) {
         StopPlayback();
       }
 
       Reset();
 
       // Put a task in the decode queue to shutdown the reader.
       // the queue to spin down.
-      RefPtr<nsIRunnable> task;
-      task = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ShutdownReader);
-      DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
+      nsCOMPtr<nsIRunnable> task
+        = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ShutdownReader);
+      TailDispatch(DecodeTaskQueue(), task.forget());
 
       DECODER_LOG("Shutdown started");
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
       return NS_OK;
     }
@@ -2585,20 +2589,21 @@ nsresult MediaDecoderStateMachine::RunSt
       ScheduleStateMachine();
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_METADATA: {
       if (!mMetadataRequest.Exists()) {
         DECODER_LOG("Dispatching CallReadMetadata");
         mMetadataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
-                                              &MediaDecoderReader::CallReadMetadata)
+                                              &MediaDecoderReader::CallReadMetadata, TailDispatcher())
           ->RefableThen(TaskQueue(), __func__, this,
                         &MediaDecoderStateMachine::OnMetadataRead,
-                        &MediaDecoderStateMachine::OnMetadataNotRead));
+                        &MediaDecoderStateMachine::OnMetadataNotRead,
+                        TailDispatcher()));
 
       }
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_FIRSTFRAME: {
       // DECODER_STATE_DECODING_FIRSTFRAME will be started by OnMetadataRead.
       return NS_OK;
@@ -2710,17 +2715,17 @@ nsresult MediaDecoderStateMachine::RunSt
           !mSentPlaybackEndedEvent)
       {
         int64_t clockTime = std::max(mAudioEndTime, mVideoFrameEndTime);
         clockTime = std::max(int64_t(0), std::max(clockTime, mEndTime));
         UpdatePlaybackPosition(clockTime);
 
         nsCOMPtr<nsIRunnable> event =
           NS_NewRunnableMethod(mDecoder, &MediaDecoder::PlaybackEnded);
-        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+        TailDispatch(AbstractThread::MainThread(), event.forget());
 
         mSentPlaybackEndedEvent = true;
       }
       return NS_OK;
     }
   }
 
   return NS_OK;
@@ -2762,19 +2767,19 @@ MediaDecoderStateMachine::Reset()
 
   mMetadataRequest.DisconnectIfExists();
   mAudioDataRequest.DisconnectIfExists();
   mAudioWaitRequest.DisconnectIfExists();
   mVideoDataRequest.DisconnectIfExists();
   mVideoWaitRequest.DisconnectIfExists();
   mSeekRequest.DisconnectIfExists();
 
-  RefPtr<nsRunnable> resetTask =
+  nsCOMPtr<nsIRunnable> resetTask =
     NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
-  DecodeTaskQueue()->Dispatch(resetTask);
+  TailDispatch(DecodeTaskQueue(), resetTask.forget());
 }
 
 void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData,
                                                 TimeStamp aTarget)
 {
   MOZ_ASSERT(OnTaskQueue());
   mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
 
@@ -2794,17 +2799,19 @@ void MediaDecoderStateMachine::RenderVid
       // If more than 10% of the last 30 frames have been corrupted, then try disabling
       // hardware acceleration. We use 10 as the corrupt value because RollingMean<>
       // only supports integer types.
       mCorruptFrames.insert(10);
       if (!mDisabledHardwareAcceleration &&
           mReader->VideoIsHardwareAccelerated() &&
           frameStats.GetPresentedFrames() > 30 &&
           mCorruptFrames.mean() >= 1 /* 10% */) {
-        DecodeTaskQueue()->Dispatch(NS_NewRunnableMethod(mReader, &MediaDecoderReader::DisableHardwareAcceleration));
+        nsCOMPtr<nsIRunnable> task =
+          NS_NewRunnableMethod(mReader, &MediaDecoderReader::DisableHardwareAcceleration);
+        TailDispatch(DecodeTaskQueue(), task.forget());
         mDisabledHardwareAcceleration = true;
       }
     } else {
       mCorruptFrames.insert(0);
     }
     container->SetCurrentFrame(aData->mDisplay, aData->mImage, aTarget);
     MOZ_ASSERT(container->GetFrameDelay() >= 0 || IsRealTime());
   }
@@ -3197,17 +3204,17 @@ void MediaDecoderStateMachine::UpdateRea
    * HTMLMediaElement::UpdateReadyStateForData. It doesn't use the value of
    * GetNextFrameStatus we computed here, because what we're computing here
    * could be stale by the time MediaDecoder::UpdateReadyStateForData runs.
    * We only compute GetNextFrameStatus here to avoid posting runnables to the main
    * thread unnecessarily.
    */
   nsCOMPtr<nsIRunnable> event;
   event = NS_NewRunnableMethod(mDecoder, &MediaDecoder::UpdateReadyStateForData);
-  NS_DispatchToMainThread(event);
+  AbstractThread::MainThread()->MaybeTailDispatch(event.forget());
 }
 
 bool MediaDecoderStateMachine::JustExitedQuickBuffering()
 {
   return !mDecodeStartTime.IsNull() &&
     mQuickBuffering &&
     (TimeStamp::Now() - mDecodeStartTime) < TimeDuration::FromMicroseconds(QUICK_BUFFER_THRESHOLD_USECS);
 }
@@ -3288,21 +3295,19 @@ MediaDecoderStateMachine::ScheduleStateM
     return;
   }
 
   if (mDispatchedStateMachine) {
     return;
   }
   mDispatchedStateMachine = true;
 
-  RefPtr<nsIRunnable> task =
+  nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::RunStateMachine);
-  nsresult rv = TaskQueue()->Dispatch(task);
-  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
-  (void) rv;
+  TaskQueue()->MaybeTailDispatch(task.forget());
 }
 
 void
 MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds)
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(OnTaskQueue());          // mDelayedScheduler.Ensure() may Disconnect()
                                       // the promise, which must happen on the state
@@ -3471,8 +3476,10 @@ uint32_t MediaDecoderStateMachine::GetAm
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 #undef DECODER_WARN
 #undef DECODER_WARN_HELPER
+
+#undef NS_DispatchToMainThread
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -326,16 +326,32 @@ public:
     return 0;
   }
 
   void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
   // Returns the state machine task queue.
   MediaTaskQueue* TaskQueue() const { return mTaskQueue; }
 
+  // Returns the tail dispatcher associated with TaskQueue(), which will fire
+  // its tasks when the current task completes. May only be called when running
+  // in TaskQueue().
+  TaskDispatcher& TailDispatcher()
+  {
+    MOZ_ASSERT(OnTaskQueue());
+    return TaskQueue()->TailDispatcher();
+  }
+
+  // Convenience method to perform a tail dispatch.
+  void TailDispatch(AbstractThread* aThread,
+                    already_AddRefed<nsIRunnable> aTask)
+  {
+    TailDispatcher().AddTask(aThread, Move(aTask));
+  }
+
   // Calls ScheduleStateMachine() after taking the decoder lock. Also
   // notifies the decoder thread in case it's waiting on the decoder lock.
   void ScheduleStateMachineWithLockAndWakeDecoder();
 
   // Schedules the shared state machine thread to run the state machine.
   void ScheduleStateMachine();
 
   // Invokes ScheduleStateMachine to run in |aMicroseconds| microseconds,
@@ -806,17 +822,17 @@ public:
       if (IsScheduled() && mTarget <= aTarget) {
         return;
       }
       Reset();
       mTarget = aTarget;
       mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->RefableThen(
         mSelf->TaskQueue(), __func__, mSelf,
         &MediaDecoderStateMachine::OnDelayedSchedule,
-        &MediaDecoderStateMachine::NotReached));
+        &MediaDecoderStateMachine::NotReached, mSelf->TailDispatcher()));
     }
 
     void CompleteRequest()
     {
       mRequest.Complete();
       mTarget = TimeStamp();
     }
 
@@ -896,27 +912,27 @@ public:
     }
 
     bool Exists()
     {
       MOZ_ASSERT(mTarget.IsValid() == !mPromise.IsEmpty());
       return mTarget.IsValid();
     }
 
-    void Resolve(bool aAtEnd, const char* aCallSite)
+    void Resolve(bool aAtEnd, const char* aCallSite, TaskDispatcher& aDispatcher)
     {
       mTarget.Reset();
       MediaDecoder::SeekResolveValue val(aAtEnd, mTarget.mEventVisibility);
-      mPromise.Resolve(val, aCallSite);
+      mPromise.Resolve(val, aCallSite, aDispatcher);
     }
 
-    void RejectIfExists(const char* aCallSite)
+    void RejectIfExists(const char* aCallSite, TaskDispatcher& aDispatcher)
     {
       mTarget.Reset();
-      mPromise.RejectIfExists(true, aCallSite);
+      mPromise.RejectIfExists(true, aCallSite, aDispatcher);
     }
 
     ~SeekJob()
     {
       MOZ_DIAGNOSTIC_ASSERT(!mTarget.IsValid());
       MOZ_DIAGNOSTIC_ASSERT(mPromise.IsEmpty());
     }
 
--- a/dom/media/MediaQueue.h
+++ b/dom/media/MediaQueue.h
@@ -171,26 +171,27 @@ private:
       , mTarget(aTarget)
     {
     }
     Listener(const Listener& aOther)
       : mRunnable(aOther.mRunnable)
       , mTarget(aOther.mTarget)
     {
     }
-    RefPtr<nsIRunnable> mRunnable;
+    nsCOMPtr<nsIRunnable> mRunnable;
     RefPtr<MediaTaskQueue> mTarget;
   };
 
   nsTArray<Listener> mPopListeners;
 
   void NotifyPopListeners() {
     for (uint32_t i = 0; i < mPopListeners.Length(); i++) {
       Listener& l = mPopListeners[i];
-      l.mTarget->Dispatch(l.mRunnable);
+      nsCOMPtr<nsIRunnable> r = l.mRunnable;
+      l.mTarget->MaybeTailDispatch(r.forget());
     }
   }
 
   // True when we've decoded the last frame of data in the
   // bitstream for which we're queueing frame data.
   bool mEndOfStream;
 };
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -273,17 +273,21 @@ MediaStreamGraphImpl::UpdateBufferSuffic
       data->mHaveEnough = track->GetEnd() >= desiredEnd;
       if (!data->mHaveEnough) {
         runnables.MoveElementsFrom(data->mDispatchWhenNotEnough);
       }
     }
   }
 
   for (uint32_t i = 0; i < runnables.Length(); ++i) {
-    runnables[i].mTarget->Dispatch(runnables[i].mRunnable);
+    // This dispatch was observed to fail in test_video_dimensions.html on
+    // win8 64 debug when invoked from noop_resampler::fill on the cubeb audio
+    // thread.
+    nsCOMPtr<nsIRunnable> r = runnables[i].mRunnable;
+    runnables[i].mTarget->MaybeTailDispatch(r.forget(), /* aAssertDispatchSuccess = */ false);
   }
 }
 
 StreamTime
 MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
                                             GraphTime aTime)
 {
   MOZ_ASSERT(aTime <= CurrentDriver()->StateComputedTime(),
@@ -2496,26 +2500,28 @@ SourceMediaStream::GetEndOfAppendedData(
 
 void
 SourceMediaStream::DispatchWhenNotEnoughBuffered(TrackID aID,
     MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable)
 {
   MutexAutoLock lock(mMutex);
   TrackData* data = FindDataForTrack(aID);
   if (!data) {
-    aSignalQueue->Dispatch(aSignalRunnable);
+    nsCOMPtr<nsIRunnable> r = aSignalRunnable;
+    aSignalQueue->MaybeTailDispatch(r.forget());
     return;
   }
 
   if (data->mHaveEnough) {
     if (data->mDispatchWhenNotEnough.IsEmpty()) {
       data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalQueue, aSignalRunnable);
     }
   } else {
-    aSignalQueue->Dispatch(aSignalRunnable);
+    nsCOMPtr<nsIRunnable> r = aSignalRunnable;
+    aSignalQueue->MaybeTailDispatch(r.forget());
   }
 }
 
 void
 SourceMediaStream::EndTrack(TrackID aID)
 {
   MutexAutoLock lock(mMutex);
   TrackData *track = FindDataForTrack(aID);
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -850,17 +850,17 @@ protected:
   struct ThreadAndRunnable {
     void Init(MediaTaskQueue* aTarget, nsIRunnable* aRunnable)
     {
       mTarget = aTarget;
       mRunnable = aRunnable;
     }
 
     nsRefPtr<MediaTaskQueue> mTarget;
-    RefPtr<nsIRunnable> mRunnable;
+    nsCOMPtr<nsIRunnable> mRunnable;
   };
   enum TrackCommands {
     TRACK_CREATE = MediaStreamListener::TRACK_EVENT_CREATED,
     TRACK_END = MediaStreamListener::TRACK_EVENT_ENDED
   };
   /**
    * Data for each track that hasn't ended.
    */