Bug 1207220: Ensure MediaShutdownManager waits until all MediaDecoder have completed their shutdown. r=cpearce a=sylvestre
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 04 Jan 2016 14:53:02 +1100
changeset 310617 c4c170ddf6e2517bbef38a057e7072ceaa2baa83
parent 310616 624eb61f2c315472061af83ef042ec1b18041532
child 310618 0b128b848d8909d9932c38ae1b16b9e5e99552b4
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, sylvestre
bugs1207220
milestone45.0a2
Bug 1207220: Ensure MediaShutdownManager waits until all MediaDecoder have completed their shutdown. r=cpearce a=sylvestre XPCOM when shutting down expects all tasks to be run synchronously. As such, we must ensure that the remaining MediaDecoder are shut down before continuing on the next task. In particular destroying gfxPlatforms must only ever happen after, as it is possible for the MediaDecoderReader to make use of gfx resources during shutdown.
dom/media/MediaDecoder.cpp
dom/media/MediaDecoder.h
dom/media/MediaShutdownManager.cpp
dom/media/MediaShutdownManager.h
dom/media/mediasource/MediaSourceDecoder.cpp
dom/media/mediasource/MediaSourceDecoder.h
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -590,59 +590,64 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::UpdateLogicalPosition);
 
   // mIgnoreProgressData
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::SeekingChanged);
 
   MediaShutdownManager::Instance().Register(this);
 }
 
-void
+RefPtr<ShutdownPromise>
 MediaDecoder::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mShuttingDown)
-    return;
-
+  if (mShuttingDown) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
   mShuttingDown = true;
 
   mResourceCallback->Disconnect();
 
 #ifdef MOZ_EME
   mCDMProxyPromiseHolder.RejectIfExists(true, __func__);
 #endif
 
   // This changes the decoder state to SHUTDOWN and does other things
   // necessary to unblock the state machine thread if it's blocked, so
   // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
+  RefPtr<ShutdownPromise> shutdown;
   if (mDecoderStateMachine) {
     mTimedMetadataListener.Disconnect();
     mMetadataLoadedListener.Disconnect();
     mFirstFrameLoadedListener.Disconnect();
     mOnPlaybackEvent.Disconnect();
     mOnSeekingStart.Disconnect();
     mOnMediaNotSeekable.Disconnect();
 
-    mDecoderStateMachine->BeginShutdown()->Then(
-      AbstractThread::MainThread(), __func__, this,
-      &MediaDecoder::FinishShutdown, &MediaDecoder::FinishShutdown);
+    shutdown = mDecoderStateMachine->BeginShutdown()
+        ->Then(AbstractThread::MainThread(), __func__, this,
+               &MediaDecoder::FinishShutdown,
+               &MediaDecoder::FinishShutdown)
+        ->CompletionPromise();
   }
 
   // Force any outstanding seek and byterange requests to complete
   // to prevent shutdown from deadlocking.
   if (mResource) {
     mResource->Close();
   }
 
   CancelDormantTimer();
 
   ChangeState(PLAY_STATE_SHUTDOWN);
 
   MediaShutdownManager::Instance().Unregister(this);
+
+  return shutdown ? shutdown : ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
 MediaDecoder::~MediaDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::RemoveMediaDecoder(this);
   UnpinForSeek();
   MOZ_COUNT_DTOR(MediaDecoder);
@@ -666,22 +671,23 @@ MediaDecoder::OnPlaybackEvent(MediaEvent
       DecodeError();
       break;
     case MediaEventType::Invalidate:
       Invalidate();
       break;
   }
 }
 
-void
+RefPtr<ShutdownPromise>
 MediaDecoder::FinishShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDecoderStateMachine->BreakCycles();
   SetStateMachine(nullptr);
+  return ShutdownPromise::CreateAndResolve(true, __func__);
 }
 
 MediaResourceCallback*
 MediaDecoder::GetResourceCallback() const
 {
   return mResourceCallback;
 }
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -341,17 +341,17 @@ public:
   // Subclasses must implement this.
   virtual MediaDecoder* Clone(MediaDecoderOwner* aOwner) = 0;
   // Create a new state machine to run this decoder.
   // Subclasses must implement this.
   virtual MediaDecoderStateMachine* CreateStateMachine() = 0;
 
   // Cleanup internal data structures. Must be called on the main
   // thread by the owning object before that object disposes of this object.
-  virtual void Shutdown();
+  virtual RefPtr<ShutdownPromise> Shutdown();
 
   // Start downloading the media. Decode the downloaded data up to the
   // point of the first frame of data.
   // This is called at most once per decoder, after Init().
   virtual nsresult Load(nsIStreamListener** aListener);
 
   // Called in |Load| to open mResource.
   nsresult OpenResource(nsIStreamListener** aStreamListener);
@@ -807,17 +807,17 @@ private:
 
   void OnPlaybackEvent(MediaEventType aEvent);
 
   void OnMediaNotSeekable()
   {
     SetMediaSeekable(false);
   }
 
-  void FinishShutdown();
+  RefPtr<ShutdownPromise> FinishShutdown();
 
   MediaEventProducer<void> mDataArrivedEvent;
 
   // The state machine object for handling the decoding. It is safe to
   // call methods of this object from other threads. Its internal data
   // is synchronised on a monitor. The lifetime of this object is
   // after mPlayState is LOADING and before mPlayState is SHUTDOWN. It
   // is safe to access it during this period.
--- a/dom/media/MediaShutdownManager.cpp
+++ b/dom/media/MediaShutdownManager.cpp
@@ -13,18 +13,19 @@
 namespace mozilla {
 
 extern LazyLogModule gMediaDecoderLog;
 #define DECODER_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg)
 
 NS_IMPL_ISUPPORTS(MediaShutdownManager, nsIObserver)
 
 MediaShutdownManager::MediaShutdownManager()
-  : mIsObservingShutdown(false),
-    mIsDoingXPCOMShutDown(false)
+  : mIsObservingShutdown(false)
+  , mIsDoingXPCOMShutDown(false)
+  , mCompletedShutdown(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(MediaShutdownManager);
 }
 
 MediaShutdownManager::~MediaShutdownManager()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -109,27 +110,58 @@ MediaShutdownManager::Shutdown()
 
   // Mark that we're shutting down, so that Unregister(*) calls don't remove
   // hashtable entries. If Unregsiter(*) was to remove from the hash table,
   // the iterations over the hashtables below would be disrupted.
   mIsDoingXPCOMShutDown = true;
 
   // Iterate over the decoders and shut them down, and remove them from the
   // hashtable.
+  nsTArray<RefPtr<ShutdownPromise>> promises;
   for (auto iter = mDecoders.Iter(); !iter.Done(); iter.Next()) {
-    iter.Get()->GetKey()->Shutdown();
+    promises.AppendElement(iter.Get()->GetKey()->Shutdown()->Then(
+      // We want to ensure that all shutdowns have completed, regardless
+      // of the ShutdownPromise being resolved or rejected. At this stage,
+      // a MediaDecoder's ShutdownPromise is only ever resolved, but as this may
+      // change in the future we want to avoid nasty surprises, so we wrap the
+      // ShutdownPromise into our own that will only ever be resolved.
+      AbstractThread::MainThread(), __func__,
+      []() -> RefPtr<ShutdownPromise> {
+        return ShutdownPromise::CreateAndResolve(true, __func__);
+      },
+      []() -> RefPtr<ShutdownPromise> {
+        return ShutdownPromise::CreateAndResolve(true, __func__);
+      })->CompletionPromise());
     iter.Remove();
   }
 
+  if (!promises.IsEmpty()) {
+    ShutdownPromise::All(AbstractThread::MainThread(), promises)
+      ->Then(AbstractThread::MainThread(), __func__, this,
+             &MediaShutdownManager::FinishShutdown,
+             &MediaShutdownManager::FinishShutdown);
+    // Wait for all decoders to complete their async shutdown...
+    while (!mCompletedShutdown) {
+      NS_ProcessNextEvent(NS_GetCurrentThread(), true);
+    }
+  }
+
   // Remove the MediaShutdownManager instance from the shutdown observer
   // list.
   nsContentUtils::UnregisterShutdownObserver(this);
 
   // Clear the singleton instance. The only remaining reference should be the
   // reference that the observer service used to call us with. The
   // MediaShutdownManager will be deleted once the observer service cleans
   // up after it finishes its notifications.
   sInstance = nullptr;
 
   DECODER_LOG(LogLevel::Debug, ("MediaShutdownManager::Shutdown() end."));
 }
 
+void
+MediaShutdownManager::FinishShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mCompletedShutdown = true;
+}
+
 } // namespace mozilla
--- a/dom/media/MediaShutdownManager.h
+++ b/dom/media/MediaShutdownManager.h
@@ -76,29 +76,34 @@ public:
   void Unregister(MediaDecoder* aDecoder);
 
 private:
 
   MediaShutdownManager();
   virtual ~MediaShutdownManager();
 
   void Shutdown();
+  void FinishShutdown();
 
   // Ensures we have a shutdown listener if we need one, and removes the
   // listener and destroys the singleton if we don't.
   void EnsureCorrectShutdownObserverState();
 
   static StaticRefPtr<MediaShutdownManager> sInstance;
 
   // References to the MediaDecoder. The decoders unregister themselves
   // in their Shutdown() method, so we'll drop the reference naturally when
   // we're shutting down (in the non xpcom-shutdown case).
   nsTHashtable<nsRefPtrHashKey<MediaDecoder>> mDecoders;
 
   // True if we have an XPCOM shutdown observer.
   bool mIsObservingShutdown;
 
   bool mIsDoingXPCOMShutDown;
+
+  // Will be set to true once all registered MediaDecoders have completed their
+  // shutdown.
+  bool mCompletedShutdown;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -134,29 +134,29 @@ MediaSourceDecoder::GetBuffered()
     }
     buffered.Intersection(range);
   }
 
   MSE_DEBUG("ranges=%s", DumpTimeRanges(buffered).get());
   return buffered;
 }
 
-void
+RefPtr<ShutdownPromise>
 MediaSourceDecoder::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("Shutdown");
   // Detach first so that TrackBuffers are unused on the main thread when
   // shut down on the decode task queue.
   if (mMediaSource) {
     mMediaSource->Detach();
   }
   mDemuxer = nullptr;
 
-  MediaDecoder::Shutdown();
+  return MediaDecoder::Shutdown();
 }
 
 /*static*/
 already_AddRefed<MediaResource>
 MediaSourceDecoder::CreateResource(nsIPrincipal* aPrincipal)
 {
   return RefPtr<MediaResource>(new MediaSourceResource(aPrincipal)).forget();
 }
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -45,17 +45,17 @@ public:
   // We can't do this in the constructor because we don't know what type of
   // media we're dealing with by that point.
   void NotifyDormantSupported(bool aSupported)
   {
     MOZ_ASSERT(NS_IsMainThread());
     mDormantSupported = aSupported;
   }
 
-  virtual void Shutdown() override;
+  virtual RefPtr<ShutdownPromise> Shutdown() override;
 
   static already_AddRefed<MediaResource> CreateResource(nsIPrincipal* aPrincipal = nullptr);
 
   void AttachMediaSource(dom::MediaSource* aMediaSource);
   void DetachMediaSource();
 
   void Ended(bool aEnded);