Bug 1062293 - Ensure the graph stays alive when doing driver switches and audio stream shutdown operations. r=jesup a=lmandel
authorPaul Adenot <paul@paul.cx>
Wed, 03 Sep 2014 15:52:43 +0200
changeset 224771 94140d5b60085f363115e6dbd730af2b8f4ec828
parent 224770 731305558f1caad7b6241cb94810589c05408628
child 224772 2a6352464e211ec9a0eb8190475de3a44dd13dc2
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjesup, lmandel
bugs1062293
milestone34.0a2
Bug 1062293 - Ensure the graph stays alive when doing driver switches and audio stream shutdown operations. r=jesup a=lmandel This basically gets a grip on the graph while doing driver switches operations on another thread (system thread or audio thread), because those can take time. Because the graph is refcounted, it'll be freed when the last operation finishes, which is what we want. This patch also only allows driver switching when the graph is in state LIFECYCLE_RUNNING, which is what we want anyway.
content/media/GraphDriver.cpp
content/media/GraphDriver.h
content/media/MediaStreamGraph.cpp
--- a/content/media/GraphDriver.cpp
+++ b/content/media/GraphDriver.cpp
@@ -82,17 +82,17 @@ void GraphDriver::SetGraphTime(GraphDriv
 void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver)
 {
 
   LIFECYCLE_LOG("Switching to new driver: %p (%s)",
       aNextDriver, aNextDriver->AsAudioCallbackDriver() ?
       "AudioCallbackDriver" : "SystemClockDriver");
   // Sometimes we switch twice to a new driver per iteration, this is probably a
   // bug.
-  MOZ_ASSERT(!mNextDriver || !mNextDriver->AsAudioCallbackDriver());
+  MOZ_ASSERT(!mNextDriver || mNextDriver->AsAudioCallbackDriver());
   mNextDriver = aNextDriver;
 }
 
 void GraphDriver::EnsureImmediateWakeUpLocked()
 {
   mGraphImpl->GetMonitor().AssertCurrentThreadOwns();
   mWaitState = WAITSTATE_WAKING_UP;
   mGraphImpl->GetMonitor().Notify();
@@ -126,27 +126,16 @@ void GraphDriver::EnsureNextIterationLoc
   }
 
   if (mNeedAnotherIteration) {
     return;
   }
   mNeedAnotherIteration = true;
 }
 
-ThreadedDriver::ThreadedDriver(MediaStreamGraphImpl* aGraphImpl)
-  : GraphDriver(aGraphImpl)
-{ }
-
-ThreadedDriver::~ThreadedDriver()
-{
-  if (mThread) {
-    mThread->Shutdown();
-  }
-}
-
 class MediaStreamGraphShutdownThreadRunnable : public nsRunnable {
 public:
   explicit MediaStreamGraphShutdownThreadRunnable(GraphDriver* aDriver)
     : mDriver(aDriver)
   {
   }
   NS_IMETHOD Run()
   {
@@ -168,16 +157,36 @@ public:
       mDriver = nullptr;
     }
     return NS_OK;
   }
 private:
   nsRefPtr<GraphDriver> mDriver;
 };
 
+void GraphDriver::Shutdown()
+{
+  if (AsAudioCallbackDriver()) {
+    LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n");
+    nsRefPtr<AsyncCubebTask> releaseEvent =
+      new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN);
+    releaseEvent->Dispatch();
+  }
+}
+
+ThreadedDriver::ThreadedDriver(MediaStreamGraphImpl* aGraphImpl)
+  : GraphDriver(aGraphImpl)
+{ }
+
+ThreadedDriver::~ThreadedDriver()
+{
+  if (mThread) {
+    mThread->Shutdown();
+  }
+}
 class MediaStreamGraphInitThreadRunnable : public nsRunnable {
 public:
   explicit MediaStreamGraphInitThreadRunnable(ThreadedDriver* aDriver)
     : mDriver(aDriver)
   {
   }
   NS_IMETHOD Run()
   {
@@ -436,16 +445,27 @@ OfflineClockDriver::WaitForNextIteration
 }
 
 void
 OfflineClockDriver::WakeUp()
 {
   MOZ_ASSERT(false, "An offline graph should not have to wake up.");
 }
 
+AsyncCubebTask::AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation)
+  : mDriver(aDriver),
+    mOperation(aOperation),
+    mShutdownGrip(aDriver->GraphImpl())
+{
+  MOZ_ASSERT(mDriver->mAudioStream || aOperation == INIT, "No audio stream !");
+}
+
+AsyncCubebTask::~AsyncCubebTask()
+{
+}
 
 NS_IMETHODIMP
 AsyncCubebTask::Run()
 {
   MOZ_ASSERT(mThread);
   if (NS_IsMainThread()) {
     mThread->Shutdown(); // can't shutdown from the thread itself, darn
     // don't null out mThread!
@@ -462,16 +482,17 @@ AsyncCubebTask::Run()
     case AsyncCubebOperation::INIT:
       LIFECYCLE_LOG("AsyncCubebOperation::INIT\n");
       mDriver->Init();
       break;
     case AsyncCubebOperation::SHUTDOWN:
       LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n");
       mDriver->Stop();
       mDriver = nullptr;
+      mShutdownGrip = nullptr;
       break;
     case AsyncCubebOperation::SLEEP: {
       {
         LIFECYCLE_LOG("AsyncCubebOperation::SLEEP\n");
         MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor());
         // We might just have been awoken
         if (mDriver->mNeedAnotherIteration) {
           mDriver->mPauseRequested = false;
--- a/content/media/GraphDriver.h
+++ b/content/media/GraphDriver.h
@@ -91,16 +91,17 @@ public:
   /* Start the graph, init the driver, start the thread. */
   virtual void Start() = 0;
   /* Stop the graph, shutting down the thread. */
   virtual void Stop() = 0;
   /* Resume after a stop */
   virtual void Resume() = 0;
   /* Revive this driver, as more messages just arrived. */
   virtual void Revive() = 0;
+  void Shutdown();
   /* Rate at which the GraphDriver runs, in ms. This can either be user
    * controlled (because we are using a {System,Offline}ClockDriver, and decide
    * how often we want to wakeup/how much we want to process per iteration), or
    * it can be indirectly set by the latency of the audio backend, and the
    * number of buffers of this audio backend: say we have four buffers, and 40ms
    * latency, we will get a callback approximately every 10ms. */
   virtual uint32_t IterationDuration() = 0;
 
@@ -459,39 +460,35 @@ class AsyncCubebTask : public nsRunnable
 public:
   enum AsyncCubebOperation {
     INIT,
     SHUTDOWN,
     SLEEP
   };
 
 
-  AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation)
-    : mDriver(aDriver),
-      mOperation(aOperation)
-  {
-    MOZ_ASSERT(mDriver->mAudioStream || aOperation == INIT, "No audio stream !");
-  }
+  AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation);
 
   nsresult Dispatch()
   {
     // Can't add 'this' as the event to run, since mThread may not be set yet
     nsresult rv = NS_NewNamedThread("CubebOperation", getter_AddRefs(mThread));
     if (NS_SUCCEEDED(rv)) {
       // Note: event must not null out mThread!
       rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL);
     }
     return rv;
   }
 
 protected:
-  virtual ~AsyncCubebTask() {};
+  virtual ~AsyncCubebTask();
 
 private:
   NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL;
   nsCOMPtr<nsIThread> mThread;
   nsRefPtr<AudioCallbackDriver> mDriver;
   AsyncCubebOperation mOperation;
+  nsRefPtr<MediaStreamGraphImpl> mShutdownGrip;
 };
 
 }
 
 #endif // GRAPHDRIVER_H_
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -537,18 +537,21 @@ MediaStreamGraphImpl::UpdateStreamOrder(
   if (!audioTrackPresent &&
       CurrentDriver()->AsAudioCallbackDriver()) {
     bool started;
     {
       MonitorAutoLock mon(mMonitor);
       started = CurrentDriver()->AsAudioCallbackDriver()->IsStarted();
     }
     if (started) {
-      SystemClockDriver* driver = new SystemClockDriver(this);
-      CurrentDriver()->SwitchAtNextIteration(driver);
+      MonitorAutoLock mon(mMonitor);
+      if (mLifecycleState == LIFECYCLE_RUNNING) {
+        SystemClockDriver* driver = new SystemClockDriver(this);
+        CurrentDriver()->SwitchAtNextIteration(driver);
+      }
     }
   }
 
   if (shouldAEC && !mFarendObserverRef && gFarendObserver) {
     mFarendObserverRef = gFarendObserver;
     mMixer.AddCallback(mFarendObserverRef);
   } else if (!shouldAEC && mFarendObserverRef){
     if (mMixer.FindCallback(mFarendObserverRef)) {
@@ -916,19 +919,22 @@ MediaStreamGraphImpl::CreateOrDestroyAud
           aStream->mAudioOutputStreams.AppendElement();
         audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
         audioOutputStream->mBlockedAudioTime = 0;
         audioOutputStream->mLastTickWritten = 0;
         audioOutputStream->mTrackID = tracks->GetID();
 
         if (!CurrentDriver()->AsAudioCallbackDriver() &&
             !CurrentDriver()->Switching()) {
-          AudioCallbackDriver* driver = new AudioCallbackDriver(this);
-          mMixer.AddCallback(driver);
-          CurrentDriver()->SwitchAtNextIteration(driver);
+          MonitorAutoLock mon(mMonitor);
+          if (mLifecycleState == LIFECYCLE_RUNNING) {
+            AudioCallbackDriver* driver = new AudioCallbackDriver(this);
+            mMixer.AddCallback(driver);
+            CurrentDriver()->SwitchAtNextIteration(driver);
+          }
         }
       }
     }
   }
 
   for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) {
     if (!audioOutputStreamsFound[i]) {
       aStream->mAudioOutputStreams.RemoveElementAt(i);
@@ -1473,17 +1479,17 @@ public:
                  "We should know the graph thread control loop isn't running!");
 
     LIFECYCLE_LOG("Shutting down graph %p", mGraph.get());
 
     if (mGraph->CurrentDriver()->AsAudioCallbackDriver()) {
       MOZ_ASSERT(!mGraph->CurrentDriver()->AsAudioCallbackDriver()->InCallback());
     }
 
-    mGraph->CurrentDriver()->Stop();
+    mGraph->CurrentDriver()->Shutdown();
 
     // mGraph's thread is not running so it's OK to do whatever here
     if (mGraph->IsEmpty()) {
       // mGraph is no longer needed, so delete it.
       mGraph->Destroy();
     } else {
       // The graph is not empty.  We must be in a forced shutdown, or a
       // non-realtime graph that has finished processing.  Some later
@@ -1600,28 +1606,29 @@ MediaStreamGraphImpl::RunInStableState(b
       }
     }
     mStreamUpdates.Clear();
 
     if (mCurrentTaskMessageQueue.IsEmpty()) {
       if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && IsEmpty()) {
         // Complete shutdown. First, ensure that this graph is no longer used.
         // A new graph graph will be created if one is needed.
-        LIFECYCLE_LOG("Disconnecting MediaStreamGraph %p", this);
-        if (this == gGraph) {
-          // null out gGraph if that's the graph being shut down
-          gGraph = nullptr;
-        }
         // Asynchronously clean up old graph. We don't want to do this
         // synchronously because it spins the event loop waiting for threads
         // to shut down, and we don't want to do that in a stable state handler.
         mLifecycleState = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
         LIFECYCLE_LOG("Sending MediaStreamGraphShutDownRunnable %p", this);
         nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this );
         NS_DispatchToMainThread(event);
+
+        LIFECYCLE_LOG("Disconnecting MediaStreamGraph %p", this);
+        if (this == gGraph) {
+          // null out gGraph if that's the graph being shut down
+          gGraph = nullptr;
+        }
       }
     } else {
       if (mLifecycleState <= LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
         MessageBlock* block = mBackMessageQueue.AppendElement();
         block->mMessages.SwapElements(mCurrentTaskMessageQueue);
         block->mGraphUpdateIndex = mNextGraphUpdateIndex;
         ++mNextGraphUpdateIndex;
         CurrentDriver()->EnsureNextIterationLocked();