Bug 1104964 - Make MediaDecoderReader own the task queue. r=cpearce
authorBobby Holley <bobbyholley@gmail.com>
Mon, 01 Dec 2014 21:51:03 -0800
changeset 218214 786ff9a62a39cba5c48bbd19a67f1af451618c6d
parent 218213 cebbf49492d3eb99c4476dd611b268caf53c4980
child 218215 a98f249001db50271291515bcc36129a114cafc3
push id52478
push userbobbyholley@gmail.com
push dateTue, 02 Dec 2014 05:51:12 +0000
treeherdermozilla-inbound@786ff9a62a39 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1104964
milestone37.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 1104964 - Make MediaDecoderReader own the task queue. r=cpearce
dom/media/MediaDecoderReader.cpp
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/gtest/TestMP4Reader.cpp
dom/media/mediasource/MediaSourceReader.cpp
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -58,16 +58,17 @@ public:
   size_t mSize;
 };
 
 MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
   : mAudioCompactor(mAudioQueue)
   , mDecoder(aDecoder)
   , mIgnoreAudioOutputFormat(false)
   , mStartTime(-1)
+  , mTaskQueueIsBorrowed(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
 }
 
 MediaDecoderReader::~MediaDecoderReader()
@@ -242,38 +243,52 @@ MediaDecoderReader::RequestAudioData()
 }
 
 void
 MediaDecoderReader::SetCallback(RequestSampleCallback* aCallback)
 {
   mSampleDecodedCallback = aCallback;
 }
 
-void
-MediaDecoderReader::SetTaskQueue(MediaTaskQueue* aTaskQueue)
+MediaTaskQueue*
+MediaDecoderReader::EnsureTaskQueue()
 {
-  mTaskQueue = aTaskQueue;
+  if (!mTaskQueue) {
+    MOZ_ASSERT(!mTaskQueueIsBorrowed);
+    RefPtr<SharedThreadPool> decodePool(GetMediaDecodeThreadPool());
+    NS_ENSURE_TRUE(decodePool, nullptr);
+
+    mTaskQueue = new MediaTaskQueue(decodePool.forget());
+  }
+
+  return mTaskQueue;
 }
 
 void
 MediaDecoderReader::BreakCycles()
 {
   if (mSampleDecodedCallback) {
     mSampleDecodedCallback->BreakCycles();
     mSampleDecodedCallback = nullptr;
   }
   mTaskQueue = nullptr;
 }
 
 void
 MediaDecoderReader::Shutdown()
 {
-  MOZ_ASSERT(mDecoder->OnDecodeThread());
+  MOZ_ASSERT(OnDecodeThread());
   mShutdown = true;
   ReleaseMediaResources();
+  if (mTaskQueue && !mTaskQueueIsBorrowed) {
+    // We may be running in the task queue ourselves, so we don't block this
+    // thread on task queue draining, since that would deadlock.
+    mTaskQueue->BeginShutdown();
+  }
+  mTaskQueue = nullptr;
 }
 
 AudioDecodeRendezvous::AudioDecodeRendezvous()
   : mMonitor("AudioDecodeRendezvous")
   , mHaveResult(false)
 {
 }
 
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -56,17 +56,29 @@ public:
 
   // Destroys the decoding state. The reader cannot be made usable again.
   // This is different from ReleaseMediaResources() as it is irreversable,
   // whereas ReleaseMediaResources() is.  Must be called on the decode
   // thread.
   virtual void Shutdown();
 
   virtual void SetCallback(RequestSampleCallback* aDecodedSampleCallback);
-  virtual void SetTaskQueue(MediaTaskQueue* aTaskQueue);
+  MediaTaskQueue* EnsureTaskQueue();
+
+  virtual bool OnDecodeThread()
+  {
+    return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
+  }
+
+  void SetBorrowedTaskQueue(MediaTaskQueue* aTaskQueue)
+  {
+    MOZ_ASSERT(!mTaskQueue && aTaskQueue);
+    mTaskQueue = aTaskQueue;
+    mTaskQueueIsBorrowed = true;
+  }
 
   // Resets all state related to decoding, emptying all buffers etc.
   // Cancels all pending Request*Data() request callbacks, and flushes the
   // decode pipeline. The decoder must not call any of the callbacks for
   // outstanding Request*Data() calls after this is called. Calls to
   // Request*Data() made after this should be processed as usual.
   // Normally this call preceedes a Seek() call, or shutdown.
   // The first samples of every stream produced after a ResetDecode() call
@@ -256,16 +268,17 @@ protected:
   // and then set to a value >= by MediaDecoderStateMachine::SetStartTime(),
   // after which point it never changes.
   int64_t mStartTime;
 private:
 
   nsRefPtr<RequestSampleCallback> mSampleDecodedCallback;
 
   nsRefPtr<MediaTaskQueue> mTaskQueue;
+  bool mTaskQueueIsBorrowed;
 
   // Flags whether a the next audio/video sample comes after a "gap" or
   // "discontinuity" in the stream. For example after a seek.
   bool mAudioDiscontinuity;
   bool mVideoDiscontinuity;
   bool mShutdown;
 };
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -239,17 +239,16 @@ MediaDecoderStateMachine::MediaDecoderSt
 
 MediaDecoderStateMachine::~MediaDecoderStateMachine()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(MediaDecoderStateMachine);
   NS_ASSERTION(!mPendingWakeDecoder.get(),
                "WakeDecoder should have been revoked already");
 
-  MOZ_ASSERT(!mDecodeTaskQueue, "Should be released in SHUTDOWN");
   mReader = nullptr;
 
 #ifdef XP_WIN
   timeEndPeriod(1);
 #endif
 }
 
 bool MediaDecoderStateMachine::HasFutureAudio() {
@@ -1023,17 +1022,17 @@ MediaDecoderStateMachine::CheckIfSeekCom
 
   SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
              audioSeekComplete, videoSeekComplete);
 
   if (audioSeekComplete && videoSeekComplete) {
     mDecodeToSeekTarget = false;
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
-    nsresult rv = mDecodeTaskQueue->Dispatch(task);
+    nsresult rv = DecodeTaskQueue()->Dispatch(task);
     if (NS_FAILED(rv)) {
       DecodeError();
     }
   }
 }
 
 bool
 MediaDecoderStateMachine::IsAudioDecoding()
@@ -1076,36 +1075,33 @@ bool MediaDecoderStateMachine::IsPlaying
   AssertCurrentThreadInMonitor();
   return !mPlayStartTime.IsNull();
 }
 
 nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  RefPtr<SharedThreadPool> decodePool(GetMediaDecodeThreadPool());
-  NS_ENSURE_TRUE(decodePool, NS_ERROR_FAILURE);
-
-  mDecodeTaskQueue = new MediaTaskQueue(decodePool.forget());
-  NS_ENSURE_TRUE(mDecodeTaskQueue, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!mReader->EnsureTaskQueue())) {
+    return NS_ERROR_FAILURE;
+  }
 
   MediaDecoderReader* cloneReader = nullptr;
   if (aCloneDonor) {
     cloneReader = aCloneDonor->mReader;
   }
 
   nsresult rv = mScheduler->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Note: This creates a cycle, broken in shutdown.
   mMediaDecodedListener =
     new MediaDataDecodedListener<MediaDecoderStateMachine>(this,
-                                                           mDecodeTaskQueue);
+                                                           DecodeTaskQueue());
   mReader->SetCallback(mMediaDecodedListener);
-  mReader->SetTaskQueue(mDecodeTaskQueue);
 
   rv = mReader->Init(cloneReader);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::StopPlayback()
@@ -1434,17 +1430,17 @@ void MediaDecoderStateMachine::StartWait
 
 void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
 {
   AssertCurrentThreadInMonitor();
   DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this,
       &MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged));
-  mDecodeTaskQueue->Dispatch(task);
+  DecodeTaskQueue()->Dispatch(task);
 }
 
 void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged()
 {
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mState != DECODER_STATE_WAIT_FOR_RESOURCES) {
     return;
@@ -1653,30 +1649,30 @@ void MediaDecoderStateMachine::StopAudio
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
-  nsresult rv = mDecodeTaskQueue->Dispatch(task);
+  nsresult rv = DecodeTaskQueue()->Dispatch(task);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
-  nsresult rv = mDecodeTaskQueue->Dispatch(task);
+  nsresult rv = DecodeTaskQueue()->Dispatch(task);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::SetReaderIdle()
 {
 #ifdef PR_LOGGING
@@ -1744,17 +1740,17 @@ MediaDecoderStateMachine::DispatchDecode
   if (needToDecodeVideo) {
     EnsureVideoDecodeTaskQueued();
   }
 
 
   if (needIdle) {
     RefPtr<nsIRunnable> event = NS_NewRunnableMethod(
         this, &MediaDecoderStateMachine::SetReaderIdle);
-    nsresult rv = mDecodeTaskQueue->Dispatch(event.forget());
+    nsresult rv = DecodeTaskQueue()->Dispatch(event.forget());
     if (NS_FAILED(rv) && mState != DECODER_STATE_SHUTDOWN) {
       DECODER_WARN("Failed to dispatch event to set decoder idle state");
     }
   }
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeSeekTask()
@@ -1770,17 +1766,17 @@ MediaDecoderStateMachine::EnqueueDecodeS
   }
   mCurrentSeekTarget = mSeekTarget;
   mSeekTarget.Reset();
   mDropAudioUntilNextDiscontinuity = HasAudio();
   mDropVideoUntilNextDiscontinuity = HasVideo();
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek));
-  nsresult rv = mDecodeTaskQueue->Dispatch(task);
+  nsresult rv = DecodeTaskQueue()->Dispatch(task);
   if (NS_FAILED(rv)) {
     DECODER_WARN("Dispatch DecodeSeek task failed.");
     mCurrentSeekTarget.Reset();
     DecodeError();
   }
   return rv;
 }
 
@@ -1812,17 +1808,17 @@ MediaDecoderStateMachine::EnsureAudioDec
     return NS_OK;
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
 
   if (IsAudioDecoding() && !mAudioRequestPending && !mWaitingForDecoderSeek) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio));
-    nsresult rv = mDecodeTaskQueue->Dispatch(task);
+    nsresult rv = DecodeTaskQueue()->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mAudioRequestPending = true;
     } else {
       DECODER_WARN("Failed to dispatch task to decode audio");
     }
   }
 
   return NS_OK;
@@ -1857,17 +1853,17 @@ MediaDecoderStateMachine::EnsureVideoDec
     return NS_OK;
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_FIRSTFRAME);
 
   if (IsVideoDecoding() && !mVideoRequestPending && !mWaitingForDecoderSeek) {
     RefPtr<nsIRunnable> task(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo));
-    nsresult rv = mDecodeTaskQueue->Dispatch(task);
+    nsresult rv = DecodeTaskQueue()->Dispatch(task);
     if (NS_SUCCEEDED(rv)) {
       mVideoRequestPending = true;
     } else {
       DECODER_WARN("Failed to dispatch task to decode video");
     }
   }
 
   return NS_OK;
@@ -2097,22 +2093,22 @@ MediaDecoderStateMachine::DecodeFirstFra
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
   DECODER_LOG("DecodeFirstFrame started");
 
   if (HasAudio()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
-    AudioQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
+    AudioQueue().AddPopListener(decodeTask, DecodeTaskQueue());
   }
   if (HasVideo()) {
     RefPtr<nsIRunnable> decodeTask(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
-    VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
+    VideoQueue().AddPopListener(decodeTask, DecodeTaskQueue());
   }
 
   if (mScheduler->IsRealTime()) {
     SetStartTime(0);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else if (mDecodingFrozenAtStateMetadata) {
     SetStartTime(mStartTime);
@@ -2266,17 +2262,17 @@ void MediaDecoderStateMachine::DecodeSee
     // May have shutdown while we released the monitor.
     return;
   }
 
   mDecodeToSeekTarget = false;
 
   if (!currentTimeChanged) {
     DECODER_LOG("Seek !currentTimeChanged...");
-    nsresult rv = mDecodeTaskQueue->Dispatch(
+    nsresult rv = DecodeTaskQueue()->Dispatch(
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
     if (NS_FAILED(rv)) {
       DecodeError();
     }
   } else {
     // The seek target is different than the current playback position,
     // we'll need to seek the playback position, so shutdown our decode
     // thread and audio sink.
@@ -2474,27 +2470,26 @@ nsresult MediaDecoderStateMachine::RunSt
     case DECODER_STATE_SHUTDOWN: {
       if (IsPlaying()) {
         StopPlayback();
       }
 
       StopAudioThread();
       FlushDecoding();
 
-      // Put a task in the decode queue to shutdown the reader.
-      RefPtr<nsIRunnable> task;
-      task = NS_NewRunnableMethod(mReader, &MediaDecoderReader::Shutdown);
-      mDecodeTaskQueue->Dispatch(task);
-
+      // Put a task in the decode queue to shutdown the reader and wait for
+      // the queue to spin down.
       {
-        // Wait for the thread decoding to exit.
+        RefPtr<nsIRunnable> task;
+        task = NS_NewRunnableMethod(mReader, &MediaDecoderReader::Shutdown);
+        nsRefPtr<MediaTaskQueue> queue = DecodeTaskQueue();
+        DebugOnly<nsresult> rv = queue->Dispatch(task);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-        mDecodeTaskQueue->BeginShutdown();
-        mDecodeTaskQueue->AwaitShutdownAndIdle();
-        mDecodeTaskQueue = nullptr;
+        queue->AwaitShutdownAndIdle();
       }
 
       // The reader's listeners hold references to the state machine,
       // creating a cycle which keeps the state machine and its shared
       // thread pools alive. So break it here.
       AudioQueue().ClearListeners();
       VideoQueue().ClearListeners();
 
@@ -2531,17 +2526,17 @@ nsresult MediaDecoderStateMachine::RunSt
       StopAudioThread();
       FlushDecoding();
       // Now that those threads are stopped, there's no possibility of
       // mPendingWakeDecoder being needed again. Revoke it.
       mPendingWakeDecoder = nullptr;
       {
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         // Wait for the thread decoding, if any, to exit.
-        mDecodeTaskQueue->AwaitIdle();
+        DecodeTaskQueue()->AwaitIdle();
         mReader->ReleaseMediaResources();
       }
       return NS_OK;
     }
 
     case DECODER_STATE_WAIT_FOR_RESOURCES: {
       return NS_OK;
     }
@@ -2698,17 +2693,17 @@ MediaDecoderStateMachine::FlushDecoding(
     // decoding operations and run any pending callbacks. This is
     // important, as we don't want any pending tasks posted to the task
     // queue by the reader to deliver any samples after we've posted the
     // reader Shutdown() task below, as the sample-delivery tasks will
     // keep video frames alive until after we've called Reader::Shutdown(),
     // and shutdown on B2G will fail as there are outstanding video frames
     // alive.
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    mDecodeTaskQueue->FlushAndDispatch(task);
+    DecodeTaskQueue()->FlushAndDispatch(task);
   }
 
   // We must reset playback so that all references to frames queued
   // in the state machine are dropped, else subsequent calls to Shutdown()
   // or ReleaseMediaResources() can fail on B2G.
   ResetPlayback();
 }
 
@@ -3197,17 +3192,17 @@ void MediaDecoderStateMachine::ScheduleS
 }
 
 nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
   return mScheduler->Schedule(aUsecs);
 }
 
 bool MediaDecoderStateMachine::OnDecodeThread() const
 {
-  return mDecodeTaskQueue->IsCurrentThreadIn();
+  return !DecodeTaskQueue() || DecodeTaskQueue()->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::OnStateMachineThread() const
 {
   return mScheduler->OnStateMachineThread();
 }
 
 nsIEventTarget* MediaDecoderStateMachine::GetStateMachineThread() const
@@ -3324,17 +3319,17 @@ void MediaDecoderStateMachine::OnAudioSi
   if (HasVideo()) {
     return;
   }
 
   // Otherwise notify media decoder/element about this error for it makes
   // no sense to play an audio-only file without sound output.
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AcquireMonitorAndInvokeDecodeError));
-  nsresult rv = mDecodeTaskQueue->Dispatch(task);
+  nsresult rv = DecodeTaskQueue()->Dispatch(task);
   if (NS_FAILED(rv)) {
     DECODER_WARN("Failed to dispatch AcquireMonitorAndInvokeDecodeError");
   }
 }
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -349,17 +349,17 @@ public:
   void QueueMetadata(int64_t aPublishTime,
                      nsAutoPtr<MediaInfo> aInfo,
                      nsAutoPtr<MetadataTags> aTags);
 
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying() const;
 
-  // Dispatch DoNotifyWaitingForResourcesStatusChanged task to mDecodeTaskQueue.
+  // Dispatch DoNotifyWaitingForResourcesStatusChanged task to the task queue.
   // Called when the reader may have acquired the hardware resources required
   // to begin decoding. The decoder monitor must be held while calling this.
   void NotifyWaitingForResourcesStatusChanged();
 
   // Notifies the state machine that should minimize the number of samples
   // decoded we preroll, until playback starts. The first time playback starts
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
@@ -726,17 +726,17 @@ protected:
   // NotifyAll on the monitor must be called when the state is changed so
   // that interested threads can wake up and alter behaviour if appropriate
   // Accessed on state machine, audio, main, and AV thread.
   State mState;
 
   // The task queue in which we run decode tasks. This is referred to as
   // the "decode thread", though in practise tasks can run on a different
   // thread every time they're called.
-  RefPtr<MediaTaskQueue> mDecodeTaskQueue;
+  MediaTaskQueue* DecodeTaskQueue() const { return mReader->GetTaskQueue(); }
 
   // The time that playback started from the system clock. This is used for
   // timing the presentation of video frames when there's no audio.
   // Accessed only via the state machine thread.  Must be set via SetPlayStartTime.
   TimeStamp mPlayStartTime;
 
   // When we start writing decoded data to a new DecodedDataStream, or we
   // restart writing due to PlaybackStarted(), we record where we are in the
--- a/dom/media/gtest/TestMP4Reader.cpp
+++ b/dom/media/gtest/TestMP4Reader.cpp
@@ -31,18 +31,17 @@ public:
   {
     EXPECT_EQ(NS_OK, Preferences::SetBool(
                        "media.fragmented-mp4.use-blank-decoder", true));
 
     EXPECT_EQ(NS_OK, resource->Open(nullptr));
     decoder->SetResource(resource);
 
     reader->Init(nullptr);
-    reader->SetTaskQueue(
-      new MediaTaskQueue(SharedThreadPool::Get(NS_LITERAL_CSTRING("TestMP4Reader"))));
+    reader->EnsureTaskQueue();
     {
       // This needs to be done before invoking GetBuffered. This is normally
       // done by MediaDecoderStateMachine.
       ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
       reader->SetStartTime(0);
     }
   }
 
@@ -52,21 +51,21 @@ public:
                                NS_NewRunnableMethod(this, &TestBinding::ReadMetadata));
     EXPECT_EQ(NS_OK, rv);
     thread->Shutdown();
   }
 
 private:
   virtual ~TestBinding()
   {
-    reader->GetTaskQueue()->Dispatch(NS_NewRunnableMethod(reader,
-                                                          &MP4Reader::Shutdown));
-    reader->GetTaskQueue()->BeginShutdown();
-    reader->GetTaskQueue()->AwaitShutdownAndIdle();
-
+    {
+      nsRefPtr<MediaTaskQueue> queue = reader->GetTaskQueue();
+      queue->Dispatch(NS_NewRunnableMethod(reader, &MP4Reader::Shutdown));
+      queue->AwaitShutdownAndIdle();
+    }
     decoder = nullptr;
     resource = nullptr;
     reader = nullptr;
     SharedThreadPool::SpinUntilShutdown();
   }
 
   void ReadMetadata()
   {
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -362,23 +362,32 @@ MediaSourceReader::CreateSubDecoder(cons
   // MSE uses a start time of 0 everywhere. Set that immediately on the
   // subreader to make sure that it's always in a state where we can invoke
   // GetBuffered on it.
   {
     ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor());
     reader->SetStartTime(0);
   }
 
+  // This part is icky. It would be nicer to just give each subreader its own
+  // task queue. Unfortunately though, Request{Audio,Video}Data implementations
+  // currently assert that they're on "the decode thread", and so having
+  // separate task queues makes MediaSource stuff unnecessarily cumbersome. We
+  // should remove the need for these assertions (which probably involves making
+  // all Request*Data implementations fully async), and then get rid of the
+  // borrowing.
+  reader->SetBorrowedTaskQueue(GetTaskQueue());
+
   // Set a callback on the subreader that forwards calls to this reader.
   // This reader will then forward them onto the state machine via this
   // reader's callback.
   RefPtr<MediaDataDecodedListener<MediaSourceReader>> callback =
-    new MediaDataDecodedListener<MediaSourceReader>(this, GetTaskQueue());
+    new MediaDataDecodedListener<MediaSourceReader>(this, reader->GetTaskQueue());
   reader->SetCallback(callback);
-  reader->SetTaskQueue(GetTaskQueue());
+
 #ifdef MOZ_FMP4
   reader->SetSharedDecoderManager(mSharedDecoderManager);
 #endif
   reader->Init(nullptr);
 
   MSE_DEBUG("MediaSourceReader(%p)::CreateSubDecoder subdecoder %p subreader %p",
             this, decoder.get(), reader.get());
   decoder->SetReader(reader);