Bug 1108707 - Make reader shutdown asynchronous. r=cpearce (relanding on a CLOSED TREE because it wasn't the source of the leaks)
authorBobby Holley <bobbyholley@gmail.com>
Tue, 09 Dec 2014 11:43:21 -0800
changeset 244739 96b86345fe30bc0e2dc12f7e32197bc86bede8a7
parent 244738 97df7d232e7f9c1fa855f7823bd5ace4a44ae195
child 244740 26b6268a5b18f897e37910e72a4875eca1cd1a53
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1108707
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 1108707 - Make reader shutdown asynchronous. r=cpearce (relanding on a CLOSED TREE because it wasn't the source of the leaks)
dom/media/MediaDecoderReader.cpp
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaTaskQueue.cpp
dom/media/MediaTaskQueue.h
dom/media/android/AndroidMediaReader.cpp
dom/media/android/AndroidMediaReader.h
dom/media/fmp4/MP4Reader.cpp
dom/media/fmp4/MP4Reader.h
dom/media/mediasource/MediaSourceDecoder.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/mediasource/TrackBuffer.cpp
dom/media/mediasource/TrackBuffer.h
dom/media/mediasource/moz.build
dom/media/omx/MediaCodecReader.cpp
dom/media/omx/MediaCodecReader.h
dom/media/omx/MediaOmxReader.cpp
dom/media/omx/MediaOmxReader.h
dom/media/webm/WebMReader.cpp
dom/media/webm/WebMReader.h
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -278,28 +278,38 @@ MediaDecoderReader::BreakCycles()
 {
   if (mSampleDecodedCallback) {
     mSampleDecodedCallback->BreakCycles();
     mSampleDecodedCallback = nullptr;
   }
   mTaskQueue = nullptr;
 }
 
-void
+nsRefPtr<ShutdownPromise>
 MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnDecodeThread());
   mShutdown = true;
   ReleaseMediaResources();
+  nsRefPtr<ShutdownPromise> p;
+
+  // Spin down the task queue if necessary. We wait until BreakCycles to null
+  // out mTaskQueue, since otherwise any remaining tasks could crash when they
+  // invoke GetTaskQueue()->IsCurrentThreadIn().
   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();
+    // If we own our task queue, shutdown ends when the task queue is done.
+    p = mTaskQueue->BeginShutdown();
+  } else {
+    // If we don't own our task queue, we resolve immediately (though
+    // asynchronously).
+    p = new ShutdownPromise(__func__);
+    p->Resolve(true, __func__);
   }
-  mTaskQueue = nullptr;
+
+  return p;
 }
 
 AudioDecodeRendezvous::AudioDecodeRendezvous()
   : mMonitor("AudioDecodeRendezvous")
   , mHaveResult(false)
 {
 }
 
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -60,17 +60,17 @@ public:
   // WARNING: If you override this, you must call the base implementation
   // in your override.
   virtual void BreakCycles();
 
   // 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 nsRefPtr<ShutdownPromise> Shutdown();
 
   virtual void SetCallback(RequestSampleCallback* aDecodedSampleCallback);
   MediaTaskQueue* EnsureTaskQueue();
 
   virtual bool OnDecodeThread()
   {
     return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
   }
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2465,73 +2465,87 @@ public:
                                                       mStateMachine.forget()));
     return NS_OK;
   }
 private:
   nsRefPtr<MediaDecoder> mDecoder;
   nsRefPtr<MediaDecoderStateMachine> mStateMachine;
 };
 
+void
+MediaDecoderStateMachine::ShutdownReader()
+{
+  MOZ_ASSERT(OnDecodeThread());
+  mReader->Shutdown()->Then(GetStateMachineThread(), __func__, this,
+                            &MediaDecoderStateMachine::FinishShutdown,
+                            &MediaDecoderStateMachine::FinishShutdown);
+}
+
+void
+MediaDecoderStateMachine::FinishShutdown(bool aSuccess)
+{
+  MOZ_ASSERT(OnStateMachineThread());
+  MOZ_ASSERT(aSuccess);
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+
+  // 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();
+
+  // Now that those threads are stopped, there's no possibility of
+  // mPendingWakeDecoder being needed again. Revoke it.
+  mPendingWakeDecoder = nullptr;
+
+  MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
+             "How did we escape from the shutdown state?");
+  // We must daisy-chain these events to destroy the decoder. We must
+  // destroy the decoder on the main thread, but we can't destroy the
+  // decoder while this thread holds the decoder monitor. We can't
+  // dispatch an event to the main thread to destroy the decoder from
+  // here, as the event may run before the dispatch returns, and we
+  // hold the decoder monitor here. We also want to guarantee that the
+  // state machine is destroyed on the main thread, and so the
+  // event runner running this function (which holds a reference to the
+  // state machine) needs to finish and be released in order to allow
+  // that. So we dispatch an event to run after this event runner has
+  // finished and released its monitor/references. That event then will
+  // dispatch an event to the main thread to release the decoder and
+  // state machine.
+  GetStateMachineThread()->Dispatch(
+    new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
+
+  DECODER_LOG("Dispose Event Dispatched");
+}
+
 nsresult MediaDecoderStateMachine::RunStateMachine()
 {
   AssertCurrentThreadInMonitor();
 
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
 
   switch (mState) {
     case DECODER_STATE_SHUTDOWN: {
       if (IsPlaying()) {
         StopPlayback();
       }
 
       StopAudioThread();
       FlushDecoding();
 
-      // Put a task in the decode queue to shutdown the reader and wait for
+      // Put a task in the decode queue to shutdown the reader.
       // the queue to spin down.
-      {
-        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());
-        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();
-
-      // Now that those threads are stopped, there's no possibility of
-      // mPendingWakeDecoder being needed again. Revoke it.
-      mPendingWakeDecoder = nullptr;
-
-      MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
-                 "How did we escape from the shutdown state?");
-      // We must daisy-chain these events to destroy the decoder. We must
-      // destroy the decoder on the main thread, but we can't destroy the
-      // decoder while this thread holds the decoder monitor. We can't
-      // dispatch an event to the main thread to destroy the decoder from
-      // here, as the event may run before the dispatch returns, and we
-      // hold the decoder monitor here. We also want to guarantee that the
-      // state machine is destroyed on the main thread, and so the
-      // event runner running this function (which holds a reference to the
-      // state machine) needs to finish and be released in order to allow
-      // that. So we dispatch an event to run after this event runner has
-      // finished and released its monitor/references. That event then will
-      // dispatch an event to the main thread to release the decoder and
-      // state machine.
-      GetStateMachineThread()->Dispatch(
-        new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
-
-      DECODER_LOG("SHUTDOWN OK");
+      RefPtr<nsIRunnable> task;
+      task = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ShutdownReader);
+      DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+      DECODER_LOG("Shutdown started");
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
       if (IsPlaying()) {
         StopPlayback();
       }
       StopAudioThread();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -157,16 +157,18 @@ public:
   void SetVolume(double aVolume);
   void SetAudioCaptured(bool aCapture);
 
   // Check if the decoder needs to become dormant state.
   bool IsDormantNeeded();
   // Set/Unset dormant state.
   void SetDormant(bool aDormant);
   void Shutdown();
+  void ShutdownReader();
+  void FinishShutdown(bool aSuccess);
 
   // Called from the main thread to get the duration. The decoder monitor
   // must be obtained before calling this. It is in units of microseconds.
   int64_t GetDuration();
 
   // Called from the main thread to set the duration of the media resource
   // if it is able to be obtained via HTTP headers. Called from the
   // state machine thread to set the duration if it is obtained from the
--- a/dom/media/MediaTaskQueue.cpp
+++ b/dom/media/MediaTaskQueue.cpp
@@ -129,22 +129,27 @@ MediaTaskQueue::AwaitShutdownAndIdle()
 {
   MonitorAutoLock mon(mQueueMonitor);
   while (!mIsShutdown) {
     mQueueMonitor.Wait();
   }
   AwaitIdleLocked();
 }
 
-void
+nsRefPtr<ShutdownPromise>
 MediaTaskQueue::BeginShutdown()
 {
   MonitorAutoLock mon(mQueueMonitor);
   mIsShutdown = true;
+  nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
+  if (!mIsRunning) {
+    mShutdownPromise.Resolve(true, __func__);
+  }
   mon.NotifyAll();
+  return p;
 }
 
 nsresult
 MediaTaskQueue::FlushAndDispatch(TemporaryRef<nsIRunnable> aRunnable)
 {
   MonitorAutoLock mon(mQueueMonitor);
   AutoSetFlushing autoFlush(this);
   FlushLocked();
@@ -203,16 +208,17 @@ MediaTaskQueue::Runner::Run()
 {
   RefPtr<nsIRunnable> event;
   {
     MonitorAutoLock mon(mQueue->mQueueMonitor);
     MOZ_ASSERT(mQueue->mIsRunning);
     mQueue->mRunningThread = NS_GetCurrentThread();
     if (mQueue->mTasks.size() == 0) {
       mQueue->mIsRunning = false;
+      mQueue->mShutdownPromise.ResolveIfExists(true, __func__);
       mon.NotifyAll();
       return NS_OK;
     }
     event = mQueue->mTasks.front().mRunnable;
     mQueue->mTasks.pop();
   }
   MOZ_ASSERT(event);
 
@@ -230,16 +236,17 @@ MediaTaskQueue::Runner::Run()
   // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
   event = nullptr;
 
   {
     MonitorAutoLock mon(mQueue->mQueueMonitor);
     if (mQueue->mTasks.size() == 0) {
       // No more events to run. Exit the task runner.
       mQueue->mIsRunning = false;
+      mQueue->mShutdownPromise.ResolveIfExists(true, __func__);
       mon.NotifyAll();
       mQueue->mRunningThread = nullptr;
       return NS_OK;
     }
   }
 
   // There's at least one more event that we can run. Dispatch this Runner
   // to the thread pool again to ensure it runs again. Note that we don't just
--- a/dom/media/MediaTaskQueue.h
+++ b/dom/media/MediaTaskQueue.h
@@ -7,23 +7,26 @@
 #ifndef MediaTaskQueue_h_
 #define MediaTaskQueue_h_
 
 #include <queue>
 #include "mozilla/RefPtr.h"
 #include "mozilla/Monitor.h"
 #include "SharedThreadPool.h"
 #include "nsThreadUtils.h"
+#include "MediaPromise.h"
 
 class nsIRunnable;
 
 namespace mozilla {
 
 class SharedThreadPool;
 
+typedef MediaPromise<bool, bool> ShutdownPromise;
+
 // Abstracts executing runnables in order in a thread pool. The runnables
 // dispatched to the MediaTaskQueue will be executed in the order in which
 // they're received, and are guaranteed to not be executed concurrently.
 // They may be executed on different threads, and a memory barrier is used
 // to make this threadsafe for objects that aren't already threadsafe.
 class MediaTaskQueue MOZ_FINAL {
   ~MediaTaskQueue();
 
@@ -45,17 +48,19 @@ public:
   // Removes all pending tasks from the task queue, and blocks until
   // the currently running task (if any) finishes.
   void Flush();
 
   // Puts the queue in a shutdown state and returns immediately. The queue will
   // remain alive at least until all the events are drained, because the Runners
   // hold a strong reference to the task queue, and one of them is always held
   // by the threadpool event queue when the task queue is non-empty.
-  void BeginShutdown();
+  //
+  // The returned promise is resolved when the queue goes empty.
+  nsRefPtr<ShutdownPromise> BeginShutdown();
 
   // Blocks until all task finish executing.
   void AwaitIdle();
 
   // Blocks until the queue is flagged for shutdown and all tasks have finished
   // executing.
   void AwaitShutdownAndIdle();
 
@@ -100,16 +105,17 @@ private:
   RefPtr<nsIThread> mRunningThread;
 
   // True if we've dispatched an event to the pool to execute events from
   // the queue.
   bool mIsRunning;
 
   // True if we've started our shutdown process.
   bool mIsShutdown;
+  MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
 
   class MOZ_STACK_CLASS AutoSetFlushing
   {
   public:
     explicit AutoSetFlushing(MediaTaskQueue* aTaskQueue) : mTaskQueue(aTaskQueue)
     {
       mTaskQueue->mQueueMonitor.AssertCurrentThreadOwns();
       mTaskQueue->mIsFlushing = true;
--- a/dom/media/android/AndroidMediaReader.cpp
+++ b/dom/media/android/AndroidMediaReader.cpp
@@ -94,25 +94,26 @@ nsresult AndroidMediaReader::ReadMetadat
     mInfo.mAudio.mRate = sampleRate;
   }
 
  *aInfo = mInfo;
  *aTags = nullptr;
   return NS_OK;
 }
 
-void AndroidMediaReader::Shutdown()
+nsRefPtr<ShutdownPromise>
+AndroidMediaReader::Shutdown()
 {
   ResetDecode();
   if (mPlugin) {
     GetAndroidMediaPluginHost()->DestroyDecoder(mPlugin);
     mPlugin = nullptr;
   }
 
-  MediaDecoderReader::Shutdown();
+  return MediaDecoderReader::Shutdown();
 }
 
 // Resets all state related to decoding, emptying all buffers etc.
 nsresult AndroidMediaReader::ResetDecode()
 {
   if (mLastVideoFrame) {
     mLastVideoFrame = nullptr;
   }
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -66,17 +66,17 @@ public:
     // not used
     return true;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
 
-  virtual void Shutdown() MOZ_OVERRIDE;
+  virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
   class ImageBufferCallback : public MPAPI::BufferCallback {
     typedef mozilla::layers::Image Image;
 
   public:
     ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer);
     void *operator()(size_t aWidth, size_t aHeight,
                      MPAPI::ColorFormat aColorFormat) MOZ_OVERRIDE;
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -134,17 +134,17 @@ MP4Reader::MP4Reader(AbstractMediaDecode
   MOZ_COUNT_CTOR(MP4Reader);
 }
 
 MP4Reader::~MP4Reader()
 {
   MOZ_COUNT_DTOR(MP4Reader);
 }
 
-void
+nsRefPtr<ShutdownPromise>
 MP4Reader::Shutdown()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
 
   if (mAudio.mDecoder) {
     Flush(kAudio);
     mAudio.mDecoder->Shutdown();
     mAudio.mDecoder = nullptr;
@@ -167,17 +167,17 @@ MP4Reader::Shutdown()
   // Dispose of the queued sample before shutting down the demuxer
   mQueuedVideoSample = nullptr;
 
   if (mPlatform) {
     mPlatform->Shutdown();
     mPlatform = nullptr;
   }
 
-  MediaDecoderReader::Shutdown();
+  return MediaDecoderReader::Shutdown();
 }
 
 void
 MP4Reader::InitLayersBackendType()
 {
   if (!IsVideoContentType(mDecoder->GetResource()->GetContentType())) {
     // Not playing video, we don't care about the layers backend type.
     return;
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -66,17 +66,17 @@ public:
   virtual bool IsWaitingMediaResources() MOZ_OVERRIDE;
   virtual bool IsDormantNeeded() MOZ_OVERRIDE;
   virtual void ReleaseMediaResources() MOZ_OVERRIDE;
   virtual void SetSharedDecoderManager(SharedDecoderManager* aManager)
     MOZ_OVERRIDE;
 
   virtual nsresult ResetDecode() MOZ_OVERRIDE;
 
-  virtual void Shutdown() MOZ_OVERRIDE;
+  virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
 private:
 
   void ReturnEOS(TrackType aTrack);
   void ReturnOutput(MediaData* aData, TrackType aTrack);
 
   // Sends input to decoder for aTrack, and output to the state machine,
   // if necessary.
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -6,24 +6,24 @@
 
 #ifndef MOZILLA_MEDIASOURCEDECODER_H_
 #define MOZILLA_MEDIASOURCEDECODER_H_
 
 #include "mozilla/Attributes.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "MediaDecoder.h"
+#include "MediaSourceReader.h"
 
 class nsIStreamListener;
 
 namespace mozilla {
 
 class MediaResource;
 class MediaDecoderStateMachine;
-class MediaSourceReader;
 class SourceBufferDecoder;
 class TrackBuffer;
 
 namespace dom {
 
 class HTMLMediaElement;
 class MediaSource;
 
@@ -66,16 +66,18 @@ public:
   // Indicates the point in time at which the reader should consider
   // registered TrackBuffers essential for initialization.
   void PrepareReaderInitialization();
 
 #ifdef MOZ_EME
   virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE;
 #endif
 
+  MediaSourceReader* GetReader() { return mReader; }
+
 private:
   // The owning MediaSource holds a strong reference to this decoder, and
   // calls Attach/DetachMediaSource on this decoder to set and clear
   // mMediaSource.
   dom::MediaSource* mMediaSource;
   nsRefPtr<MediaSourceReader> mReader;
 
   // Protected by GetReentrantMonitor()
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -231,44 +231,63 @@ MediaSourceReader::OnNotDecoded(MediaDat
     return;
   }
 
   // We don't have the data the caller wants. Tell that we're waiting for JS to
   // give us more data.
   GetCallback()->OnNotDecoded(aType, WAITING_FOR_DATA);
 }
 
-void
+nsRefPtr<ShutdownPromise>
 MediaSourceReader::Shutdown()
 {
-  MediaDecoderReader::Shutdown();
-  for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
-    mTrackBuffers[i]->Shutdown();
+  MOZ_ASSERT(mMediaSourceShutdownPromise.IsEmpty());
+  nsRefPtr<ShutdownPromise> p = mMediaSourceShutdownPromise.Ensure(__func__);
+
+  ContinueShutdown(true);
+  return p;
+}
+
+void
+MediaSourceReader::ContinueShutdown(bool aSuccess)
+{
+  MOZ_ASSERT(aSuccess);
+  if (mTrackBuffers.Length()) {
+    mTrackBuffers[0]->Shutdown()->Then(GetTaskQueue(), __func__, this,
+                                       &MediaSourceReader::ContinueShutdown,
+                                       &MediaSourceReader::ContinueShutdown);
+    mShutdownTrackBuffers.AppendElement(mTrackBuffers[0]);
+    mTrackBuffers.RemoveElementAt(0);
+    return;
   }
+
   mAudioTrack = nullptr;
   mAudioReader = nullptr;
   mVideoTrack = nullptr;
   mVideoReader = nullptr;
+
+  MediaDecoderReader::Shutdown()->ChainTo(mMediaSourceShutdownPromise.Steal(), __func__);
 }
 
 void
 MediaSourceReader::BreakCycles()
 {
   MediaDecoderReader::BreakCycles();
 
   // These were cleared in Shutdown().
   MOZ_ASSERT(!mAudioTrack);
   MOZ_ASSERT(!mAudioReader);
   MOZ_ASSERT(!mVideoTrack);
   MOZ_ASSERT(!mVideoReader);
+  MOZ_ASSERT(!mTrackBuffers.Length());
 
-  for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
-    mTrackBuffers[i]->BreakCycles();
+  for (uint32_t i = 0; i < mShutdownTrackBuffers.Length(); ++i) {
+    mShutdownTrackBuffers[i]->BreakCycles();
   }
-  mTrackBuffers.Clear();
+  mShutdownTrackBuffers.Clear();
 }
 
 already_AddRefed<MediaDecoderReader>
 MediaSourceReader::SelectReader(int64_t aTarget,
                                 const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders)
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -92,17 +92,17 @@ public:
   nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE;
 
   already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType);
 
   void AddTrackBuffer(TrackBuffer* aTrackBuffer);
   void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);
   void OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const MediaInfo& aInfo);
 
-  void Shutdown();
+  nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
   virtual void BreakCycles();
 
   bool IsShutdown()
   {
     ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
     return mDecoder->IsShutdown();
   }
@@ -130,16 +130,17 @@ private:
                                                     const nsTArray<nsRefPtr<SourceBufferDecoder>>& aTrackDecoders);
 
   void AttemptSeek();
 
   nsRefPtr<MediaDecoderReader> mAudioReader;
   nsRefPtr<MediaDecoderReader> mVideoReader;
 
   nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
+  nsTArray<nsRefPtr<TrackBuffer>> mShutdownTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
   nsRefPtr<TrackBuffer> mAudioTrack;
   nsRefPtr<TrackBuffer> mVideoTrack;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 
@@ -171,16 +172,19 @@ private:
   // the mDiscontinuity field set to true once we have the
   // first decoded sample. These flags are set during seeking
   // so we can detect when we have the first decoded sample
   // after a seek.
   bool mAudioIsSeeking;
   bool mVideoIsSeeking;
 
   bool mHasEssentialTrackBuffers;
+
+  void ContinueShutdown(bool aSuccess);
+  MediaPromiseHolder<ShutdownPromise> mMediaSourceShutdownPromise;
 #ifdef MOZ_FMP4
   nsRefPtr<SharedDecoderManager> mSharedDecoderManager;
 #endif
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_MEDIASOURCEREADER_H_ */
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -91,33 +91,49 @@ public:
     return true;
   }
 
 private:
   TrackBuffer* mOwner;
   nsAutoTArray<nsRefPtr<SourceBufferDecoder>,2> mDecoders;
 };
 
-void
+nsRefPtr<ShutdownPromise>
 TrackBuffer::Shutdown()
 {
-  // Finish any decoder initialization, which may add to mInitializedDecoders.
-  // Shutdown waits for any pending events, which may require the monitor,
-  // so we must not hold the monitor during this call.
-  mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
-  mTaskQueue->BeginShutdown();
-  mTaskQueue->AwaitShutdownAndIdle();
+  MOZ_ASSERT(mShutdownPromise.IsEmpty());
+  nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
+
+  RefPtr<MediaTaskQueue> queue = mTaskQueue;
   mTaskQueue = nullptr;
+  queue->BeginShutdown()
+       ->Then(mParentDecoder->GetReader()->GetTaskQueue(), __func__, this,
+              &TrackBuffer::ContinueShutdown, &TrackBuffer::ContinueShutdown);
 
+  return p;
+}
+
+void
+TrackBuffer::ContinueShutdown(bool aSuccess)
+{
+  MOZ_ASSERT(aSuccess);
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
-    mDecoders[i]->GetReader()->Shutdown();
+  if (mDecoders.Length()) {
+    mDecoders[0]->GetReader()->Shutdown()
+                ->Then(mParentDecoder->GetReader()->GetTaskQueue(), __func__, this,
+                       &TrackBuffer::ContinueShutdown, &TrackBuffer::ContinueShutdown);
+    mShutdownDecoders.AppendElement(mDecoders[0]);
+    mDecoders.RemoveElementAt(0);
+    return;
   }
+
   mInitializedDecoders.Clear();
   mParentDecoder = nullptr;
+
+  mShutdownPromise.Resolve(true, __func__);
 }
 
 bool
 TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecodersToInitialize decoders(this);
   // TODO: Run more of the buffer append algorithm asynchronously.
@@ -428,22 +444,23 @@ TrackBuffer::ContainsTime(int64_t aTime)
   return false;
 }
 
 void
 TrackBuffer::BreakCycles()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
-    mDecoders[i]->BreakCycles();
+  for (uint32_t i = 0; i < mShutdownDecoders.Length(); ++i) {
+    mShutdownDecoders[i]->BreakCycles();
   }
-  mDecoders.Clear();
+  mShutdownDecoders.Clear();
 
   // These are cleared in Shutdown()
+  MOZ_ASSERT(!mDecoders.Length());
   MOZ_ASSERT(mInitializedDecoders.IsEmpty());
   MOZ_ASSERT(!mParentDecoder);
 }
 
 void
 TrackBuffer::ResetDecode()
 {
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -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/. */
 
 #ifndef MOZILLA_TRACKBUFFER_H_
 #define MOZILLA_TRACKBUFFER_H_
 
 #include "SourceBufferDecoder.h"
+#include "MediaPromise.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/Maybe.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nscore.h"
 
@@ -28,17 +29,17 @@ class TimeRanges;
 } // namespace dom
 
 class TrackBuffer MOZ_FINAL {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackBuffer);
 
   TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
 
-  void Shutdown();
+  nsRefPtr<ShutdownPromise> Shutdown();
 
   // Append data to the current decoder.  Also responsible for calling
   // NotifyDataArrived on the decoder to keep buffered range computation up
   // to date.  Returns false if the append failed.
   bool AppendData(const uint8_t* aData, uint32_t aLength);
   bool EvictData(uint32_t aThreshold);
   void EvictBefore(double aTime);
 
@@ -126,16 +127,21 @@ private:
   // initialize (i.e. call ReadMetadata on) decoders as they are created via
   // NewDecoder.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   // All of the decoders managed by this TrackBuffer.  Access protected by
   // mParentDecoder's monitor.
   nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;
 
+  // During shutdown, we move decoders from mDecoders to mShutdownDecoders after
+  // invoking Shutdown. This is all so that we can avoid destroying the decoders
+  // off-main-thread. :-(
+  nsTArray<nsRefPtr<SourceBufferDecoder>> mShutdownDecoders;
+
   // Contains only the initialized decoders managed by this TrackBuffer.
   // Access protected by mParentDecoder's monitor.
   nsTArray<nsRefPtr<SourceBufferDecoder>> mInitializedDecoders;
 
   // Decoders which are waiting on a Content Decryption Module to be able to
   // finish ReadMetadata.
   nsTArray<nsRefPtr<SourceBufferDecoder>> mWaitingDecoders;
 
@@ -148,12 +154,15 @@ private:
   // The last start and end timestamps added to the TrackBuffer via
   // AppendData.  Accessed on the main thread only.
   int64_t mLastStartTimestamp;
   Maybe<int64_t> mLastEndTimestamp;
 
   // Set when the first decoder used by this TrackBuffer is initialized.
   // Protected by mParentDecoder's monitor.
   MediaInfo mInfo;
+
+  void ContinueShutdown(bool aSuccess);
+  MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
 };
 
 } // namespace mozilla
 #endif /* MOZILLA_TRACKBUFFER_H_ */
--- a/dom/media/mediasource/moz.build
+++ b/dom/media/mediasource/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 
 EXPORTS += [
     'AsyncEventRunner.h',
     'MediaSourceDecoder.h',
+    'MediaSourceReader.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'MediaSource.h',
     'SourceBuffer.h',
     'SourceBufferList.h',
 ]
 
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -339,21 +339,21 @@ MediaCodecReader::ReleaseMediaResources(
   }
   if (mAudioTrack.mSource != nullptr && !mAudioTrack.mSourceIsStopped) {
     mAudioTrack.mSource->stop();
     mAudioTrack.mSourceIsStopped = true;
   }
   ReleaseCriticalResources();
 }
 
-void
+nsRefPtr<ShutdownPromise>
 MediaCodecReader::Shutdown()
 {
   ReleaseResources();
-  MediaDecoderReader::Shutdown();
+  return MediaDecoderReader::Shutdown();
 }
 
 void
 MediaCodecReader::DispatchAudioTask()
 {
   if (mAudioTrack.mTaskQueue && mAudioTrack.mTaskQueue->IsEmpty()) {
     RefPtr<nsIRunnable> task =
       NS_NewRunnableMethod(this,
--- a/dom/media/omx/MediaCodecReader.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -63,17 +63,17 @@ public:
   virtual bool IsDormantNeeded();
 
   // Release media resources they should be released in dormant state
   virtual void ReleaseMediaResources();
 
   // Destroys the decoding state. The reader cannot be made usable again.
   // This is different from ReleaseMediaResources() as Shutdown() is
   // irreversible, whereas ReleaseMediaResources() is reversible.
-  virtual void Shutdown();
+  virtual nsRefPtr<ShutdownPromise> Shutdown();
 
   // Used to retrieve some special information that can only be retrieved after
   // all contents have been continuously parsed. (ex. total duration of some
   // variable-bit-rate MP3 files.)
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
   // Flush the MediaTaskQueue, flush MediaCodec and raise the mDiscontinuity.
   virtual nsresult ResetDecode() MOZ_OVERRIDE;
--- a/dom/media/omx/MediaOmxReader.cpp
+++ b/dom/media/omx/MediaOmxReader.cpp
@@ -170,27 +170,30 @@ nsresult MediaOmxReader::Init(MediaDecod
 void MediaOmxReader::ReleaseDecoder()
 {
   if (mOmxDecoder.get()) {
     mOmxDecoder->ReleaseDecoder();
   }
   mOmxDecoder.clear();
 }
 
-void MediaOmxReader::Shutdown()
+nsRefPtr<ShutdownPromise>
+MediaOmxReader::Shutdown()
 {
   nsCOMPtr<nsIRunnable> cancelEvent =
     NS_NewRunnableMethod(this, &MediaOmxReader::CancelProcessCachedData);
   NS_DispatchToMainThread(cancelEvent);
 
-  MediaDecoderReader::Shutdown();
+  nsRefPtr<ShutdownPromise> p = MediaDecoderReader::Shutdown();
 
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(this, &MediaOmxReader::ReleaseDecoder);
   NS_DispatchToMainThread(event);
+
+  return p;
 }
 
 bool MediaOmxReader::IsWaitingMediaResources()
 {
   return mIsWaitingResources;
 }
 
 void MediaOmxReader::UpdateIsWaitingMediaResources()
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -99,17 +99,17 @@ public:
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
 
   virtual bool IsMediaSeekable() MOZ_OVERRIDE;
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
-  virtual void Shutdown() MOZ_OVERRIDE;
+  virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
   bool IsShutdown() {
     MutexAutoLock lock(mMutex);
     return mIsShutdown;
   }
 
   void ReleaseDecoder();
 
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -213,31 +213,32 @@ WebMReader::~WebMReader()
   if (mOpusDecoder) {
     opus_multistream_decoder_destroy(mOpusDecoder);
     mOpusDecoder = nullptr;
   }
   MOZ_ASSERT(!mVideoDecoder);
   MOZ_COUNT_DTOR(WebMReader);
 }
 
-void WebMReader::Shutdown()
+nsRefPtr<ShutdownPromise>
+WebMReader::Shutdown()
 {
 #if defined(MOZ_PDM_VPX)
   if (mVideoTaskQueue) {
     mVideoTaskQueue->BeginShutdown();
     mVideoTaskQueue->AwaitShutdownAndIdle();
   }
 #endif
 
   if (mVideoDecoder) {
     mVideoDecoder->Shutdown();
     mVideoDecoder = nullptr;
   }
 
-  MediaDecoderReader::Shutdown();
+  return MediaDecoderReader::Shutdown();
 }
 
 nsresult WebMReader::Init(MediaDecoderReader* aCloneDonor)
 {
   vorbis_info_init(&mVorbisInfo);
   vorbis_comment_init(&mVorbisComment);
   memset(&mVorbisDsp, 0, sizeof(vorbis_dsp_state));
   memset(&mVorbisBlock, 0, sizeof(vorbis_block));
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -128,17 +128,17 @@ class WebMReader : public MediaDecoderRe
 {
 public:
   explicit WebMReader(AbstractMediaDecoder* aDecoder);
 
 protected:
   ~WebMReader();
 
 public:
-  virtual void Shutdown() MOZ_OVERRIDE;
+  virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
   virtual nsresult Init(MediaDecoderReader* aCloneDonor);
   virtual nsresult ResetDecode();
   virtual bool DecodeAudioData();
 
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold);
 
   virtual bool HasAudio()