Bug 1315850 - Ask the GMPService for the GMP thread in GMPParent::ChildTerminated. r=gerald draft
authorChris Pearce <cpearce@mozilla.com>
Fri, 24 Mar 2017 13:38:00 +1300
changeset 504183 e507e48ee633cad8911287fb7296bbb1679a7bcb
parent 504181 404709f1aee80465a953fce1a1e715d49ebfbe35
child 550589 08217755c646742453b717263ba817c9e285ae98
push id50749
push userbmo:cpearce@mozilla.com
push dateFri, 24 Mar 2017 01:14:01 +0000
reviewersgerald
bugs1315850, 1163239
milestone55.0a1
Bug 1315850 - Ask the GMPService for the GMP thread in GMPParent::ChildTerminated. r=gerald When we shutdown the browser while the GMPService is active we can end up leaking a GMPParent, GeckoMediaPluginServiceParent, and a Runnable. I tracked this down to the runnable dispatched to the GMP thread in GMPParent::ChildTerminated(). The dispatch of this runnable is failing as we are dispatching the runnable to a reference of the GMP thread which we have previously acquired, but that thread is now shutdown. So the dispatch fails, and if you look in nsThread::DispatchInternal() you'll see that we deliberately leak the runnable if dispatch fails! The runnable leaking means that the references it holds to the GMPParent and the GMP service parent leak. The solution in this patch is to not cache a reference to the GMP thread on the GMPParent; instead we re-request the GMP thread from the GMPService when we want it. This means that in the case where the browser is shutting down, GMPParent::GMPThread() will return null, and we'll not leak the runnable. We'll then follow the (hacky) shutdown path added in bug 1163239. We also need to change GMPParent::GMPThread() and GMPContentParent::GMPThread() to return a reference to the GMP thread with a refcount held on it, in order to ensure we don't race with the GMP service shutting down the GMP thread while we're trying to dispatch to in on shutdown. MozReview-Commit-ID: CXv9VZqTRzY
dom/media/gmp/GMPContentParent.cpp
dom/media/gmp/GMPContentParent.h
dom/media/gmp/GMPDecryptorParent.h
dom/media/gmp/GMPParent.cpp
dom/media/gmp/GMPParent.h
dom/media/gmp/GMPVideoDecoderParent.cpp
--- a/dom/media/gmp/GMPContentParent.cpp
+++ b/dom/media/gmp/GMPContentParent.cpp
@@ -162,17 +162,17 @@ GMPContentParent::GetGMPDecryptor(GMPDec
   // It's dropped by calling Close() on the interface.
   NS_ADDREF(dp);
   mDecryptors.AppendElement(dp);
   *aGMPDP = dp;
 
   return NS_OK;
 }
 
-nsIThread*
+nsCOMPtr<nsIThread>
 GMPContentParent::GMPThread()
 {
   if (!mGMPThread) {
     nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
     MOZ_ASSERT(mps);
     if (!mps) {
       return nullptr;
     }
--- a/dom/media/gmp/GMPContentParent.h
+++ b/dom/media/gmp/GMPContentParent.h
@@ -35,17 +35,17 @@ public:
   void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder);
 
   nsresult GetGMPDecryptor(GMPDecryptorParent** aGMPKS);
   void DecryptorDestroyed(GMPDecryptorParent* aSession);
 
   already_AddRefed<ChromiumCDMParent> GetChromiumCDM();
   void ChromiumCDMDestroyed(ChromiumCDMParent* aCDM);
 
-  nsIThread* GMPThread();
+  nsCOMPtr<nsIThread> GMPThread();
 
   // GMPSharedMem
   void CheckThread() override;
 
   void SetDisplayName(const nsCString& aDisplayName)
   {
     mDisplayName = aDisplayName;
   }
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -114,16 +114,16 @@ private:
 
   bool mIsOpen;
   bool mShuttingDown;
   bool mActorDestroyed;
   RefPtr<GMPContentParent> mPlugin;
   uint32_t mPluginId;
   GMPDecryptorProxyCallback* mCallback;
 #ifdef DEBUG
-  nsIThread* const mGMPThread;
+  nsCOMPtr<nsIThread> const mGMPThread;
 #endif
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPDecryptorChild_h_
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -319,17 +319,17 @@ public:
   }
   nsString mNodeId;
 };
 
 void
 GMPParent::ChildTerminated()
 {
   RefPtr<GMPParent> self(this);
-  nsIThread* gmpThread = GMPThread();
+  nsCOMPtr<nsIThread> gmpThread = GMPThread();
 
   if (!gmpThread) {
     // Bug 1163239 - this can happen on shutdown.
     // PluginTerminated removes the GMP from the GMPService.
     // On shutdown we can have this case where it is already been
     // removed so there is no harm in not trying to remove it again.
     LOGD("%s::%s: GMPThread() returned nullptr.", __CLASS__, __FUNCTION__);
   } else {
@@ -368,36 +368,30 @@ GMPParent::DeleteProcess()
 }
 
 GMPState
 GMPParent::State() const
 {
   return mState;
 }
 
-// Not changing to use mService since we'll be removing it
-nsIThread*
+nsCOMPtr<nsIThread>
 GMPParent::GMPThread()
 {
-  if (!mGMPThread) {
-    nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
-    MOZ_ASSERT(mps);
-    if (!mps) {
-      return nullptr;
-    }
-    // Not really safe if we just grab to the mGMPThread, as we don't know
-    // what thread we're running on and other threads may be trying to
-    // access this without locks!  However, debug only, and primary failure
-    // mode outside of compiler-helped TSAN is a leak.  But better would be
-    // to use swap() under a lock.
-    mps->GetThread(getter_AddRefs(mGMPThread));
-    MOZ_ASSERT(mGMPThread);
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mps);
+  if (!mps) {
+    return nullptr;
   }
-
-  return mGMPThread;
+  // Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
+  // nullptr if the GeckoMediaPluginService has started shutdown.
+  nsCOMPtr<nsIThread> gmpThread;
+  mps->GetThread(getter_AddRefs(gmpThread));
+  return gmpThread;
 }
 
 /* static */
 bool
 GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
                         const nsCString& aAPI,
                         const nsTArray<nsCString>& aTags)
 {
@@ -585,17 +579,18 @@ mozilla::ipc::IPCResult
 GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
 {
   return IPC_OK();
 }
 
 PGMPTimerParent*
 GMPParent::AllocPGMPTimerParent()
 {
-  GMPTimerParent* p = new GMPTimerParent(GMPThread());
+  nsCOMPtr<nsIThread> thread = GMPThread();
+  GMPTimerParent* p = new GMPTimerParent(thread);
   mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
   return p;
 }
 
 bool
 GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
 {
   GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -95,17 +95,17 @@ public:
 
   // Called by the GMPService to forcibly close active de/encoders at shutdown
   void Shutdown();
 
   // This must not be called while we're in the middle of abnormal ActorDestroy
   void DeleteProcess();
 
   GMPState State() const;
-  nsIThread* GMPThread();
+  nsCOMPtr<nsIThread> GMPThread();
 
   // A GMP can either be a single instance shared across all NodeIds (like
   // in the OpenH264 case), or we can require a new plugin instance for every
   // NodeIds running the plugin (as in the EME plugin case).
   //
   // A NodeId is a hash of the ($urlBarOrigin, $ownerDocOrigin) pair.
   //
   // Plugins are associated with an NodeIds by calling SetNodeId() before
@@ -202,17 +202,16 @@ private:
   bool mDeleteProcessOnlyOnUnload;
   bool mAbnormalShutdownInProgress;
   bool mIsBlockingDeletion;
 
   bool mCanDecrypt;
 
   nsTArray<RefPtr<GMPTimerParent>> mTimers;
   nsTArray<RefPtr<GMPStorageParent>> mStorage;
-  nsCOMPtr<nsIThread> mGMPThread;
   // NodeId the plugin is assigned to, or empty if the the plugin is not
   // assigned to a NodeId.
   nsCString mNodeId;
   // This is used for GMP content in the parent, there may be more of these in
   // the content processes.
   RefPtr<GMPContentParent> mGMPContentParent;
   nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> mGetContentParentPromises;
   uint32_t mGMPContentChildCount;
--- a/dom/media/gmp/GMPVideoDecoderParent.cpp
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -204,17 +204,18 @@ GMPVideoDecoderParent::Reset()
   RefPtr<GMPVideoDecoderParent> self(this);
   nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self]() -> void
   {
     LOGD(("GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out waiting for ResetComplete", self.get()));
     self->mResetCompleteTimeout = nullptr;
     LogToBrowserConsole(NS_LITERAL_STRING("GMPVideoDecoderParent timed out waiting for ResetComplete()"));
   });
   CancelResetCompleteTimeout();
-  mResetCompleteTimeout = SimpleTimer::Create(task, 5000, mPlugin->GMPThread());
+  nsCOMPtr<nsIThread> thread = mPlugin->GMPThread();
+  mResetCompleteTimeout = SimpleTimer::Create(task, 5000, thread);
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 void
 GMPVideoDecoderParent::CancelResetCompleteTimeout()
 {