Bug 1043531: Safely delete GMP processes when unused and handle plugin crashes r=cpearce a=sylvestre
authorRandell Jesup <rjesup@jesup.org>
Mon, 28 Jul 2014 02:36:24 -0400
changeset 217311 1d0db9d586fa6e260ee8ff766fe28c73f76bb60f
parent 217310 8d795cfeec2f19ba9eaa98266afef9737b62a61c
child 217312 3f2683625e4a460182522ab423191279cf1eec0b
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, sylvestre
bugs1043531
milestone33.0a2
Bug 1043531: Safely delete GMP processes when unused and handle plugin crashes r=cpearce a=sylvestre
content/media/gmp/GMPParent.cpp
content/media/gmp/GMPParent.h
content/media/gmp/GMPService.cpp
content/media/gmp/GMPService.h
content/media/gmp/GMPSharedMemManager.cpp
--- a/content/media/gmp/GMPParent.cpp
+++ b/content/media/gmp/GMPParent.cpp
@@ -26,32 +26,43 @@ using CrashReporter::GetIDFromMinidump;
 #endif
 
 namespace mozilla {
 namespace gmp {
 
 GMPParent::GMPParent()
   : mState(GMPStateNotLoaded)
   , mProcess(nullptr)
-  , mDeleteProcessOnUnload(false)
+  , mDeleteProcessOnlyOnUnload(false)
+  , mAbnormalShutdownInProgress(false)
 {
 }
 
 GMPParent::~GMPParent()
 {
   // Can't Close or Destroy the process here, since destruction is MainThread only
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 nsresult
-GMPParent::Init(nsIFile* aPluginDir)
+GMPParent::CloneFrom(const GMPParent* aOther)
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+  MOZ_ASSERT(aOther->mDirectory && aOther->mService, "null plugin directory");
+  return Init(aOther->mService, aOther->mDirectory);
+}
+
+nsresult
+GMPParent::Init(GeckoMediaPluginService *aService, nsIFile* aPluginDir)
 {
   MOZ_ASSERT(aPluginDir);
+  MOZ_ASSERT(aService);
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
 
+  mService = aService;
   mDirectory = aPluginDir;
 
   nsAutoString leafname;
   nsresult rv = aPluginDir->GetLeafName(leafname);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
@@ -94,30 +105,30 @@ GMPParent::LoadProcess()
   return NS_OK;
 }
 
 void
 GMPParent::CloseIfUnused()
 {
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
 
-  if ((mDeleteProcessOnUnload ||
+  if ((mDeleteProcessOnlyOnUnload ||
        mState == GMPStateLoaded ||
        mState == GMPStateUnloading) &&
       mVideoDecoders.IsEmpty() &&
       mVideoEncoders.IsEmpty()) {
     Shutdown();
   }
 }
 
 void
 GMPParent::CloseActive(bool aDieWhenUnloaded)
 {
   if (aDieWhenUnloaded) {
-    mDeleteProcessOnUnload = true; // don't allow this to go back...
+    mDeleteProcessOnlyOnUnload = true; // don't allow this to go back...
   }
   if (mState == GMPStateLoaded) {
     mState = GMPStateUnloading;
   }
 
   // Invalidate and remove any remaining API objects.
   for (uint32_t i = mVideoDecoders.Length(); i > 0; i--) {
     mVideoDecoders[i - 1]->Shutdown();
@@ -134,35 +145,42 @@ GMPParent::CloseActive(bool aDieWhenUnlo
   CloseIfUnused();
 }
 
 void
 GMPParent::Shutdown()
 {
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
 
+  if (mAbnormalShutdownInProgress) {
+    return;
+  }
   MOZ_ASSERT(mVideoDecoders.IsEmpty() && mVideoEncoders.IsEmpty());
   if (mState == GMPStateNotLoaded || mState == GMPStateClosing) {
     return;
   }
 
-  // XXX Get rid of mDeleteProcessOnUnload and do this on all Shutdowns once
-  // Bug 1043671 is fixed (backout this patch)
-  if (mDeleteProcessOnUnload) {
-    mState = GMPStateClosing;
-    DeleteProcess();
-  } else {
-    mState = GMPStateNotLoaded;
-  }
+  mState = GMPStateClosing;
+  DeleteProcess();
+  // XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
+  // Bug 1043671 is fixed
+  if (!mDeleteProcessOnlyOnUnload) {
+    // Destroy ourselves and rise from the fire to save memory
+    nsRefPtr<GMPParent> self(this);
+    mService->ReAddOnGMPThread(self);
+  } // else we've been asked to die and stay dead
   MOZ_ASSERT(mState == GMPStateNotLoaded);
 }
 
 void
 GMPParent::DeleteProcess()
 {
+  // Don't Close() twice!
+  // Probably remove when bug 1043671 is resolved
+  MOZ_ASSERT(mState == GMPStateClosing);
   Close();
   mProcess->Delete();
   mProcess = nullptr;
   mState = GMPStateNotLoaded;
 }
 
 void
 GMPParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder)
@@ -200,16 +218,17 @@ GMPParent::VideoEncoderDestroyed(GMPVide
 
 GMPState
 GMPParent::State() const
 {
   return mState;
 }
 
 #ifdef DEBUG
+// Not changing to use mService since we'll be removing it
 nsIThread*
 GMPParent::GMPThread()
 {
   if (!mGMPThread) {
     nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
     MOZ_ASSERT(mps);
     if (!mps) {
       return nullptr;
@@ -371,21 +390,25 @@ GMPParent::ActorDestroy(ActorDestroyReas
 
     // NotifyObservers is mainthread-only
     NS_DispatchToMainThread(WrapRunnableNM(&GMPNotifyObservers, id),
                             NS_DISPATCH_NORMAL);
   }
 #endif
   // warn us off trying to close again
   mState = GMPStateClosing;
+  mAbnormalShutdownInProgress = true;
   CloseActive(false);
 
   // Normal Shutdown() will delete the process on unwind.
   if (AbnormalShutdown == aWhy) {
-    NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &GMPParent::DeleteProcess));
+    mState = GMPStateClosing;
+    nsRefPtr<GMPParent> self(this);
+    // Note: final destruction will be Dispatched to ourself
+    mService->ReAddOnGMPThread(self);
   }
 }
 
 mozilla::dom::PCrashReporterParent*
 GMPParent::AllocPCrashReporterParent(const NativeThreadId& aThread)
 {
 #ifndef MOZ_CRASHREPORTER
   MOZ_ASSERT(false, "Should only be sent if crash reporting is enabled.");
--- a/content/media/gmp/GMPParent.h
+++ b/content/media/gmp/GMPParent.h
@@ -2,16 +2,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 GMPParent_h_
 #define GMPParent_h_
 
 #include "GMPProcessParent.h"
+#include "GMPService.h"
 #include "GMPVideoDecoderParent.h"
 #include "GMPVideoEncoderParent.h"
 #include "mozilla/gmp/PGMPParent.h"
 #include "nsCOMPtr.h"
 #include "nscore.h"
 #include "nsISupports.h"
 #include "nsString.h"
 #include "nsTArray.h"
@@ -51,17 +52,19 @@ enum GMPState {
 
 class GMPParent MOZ_FINAL : public PGMPParent
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(GMPParent)
 
   GMPParent();
 
-  nsresult Init(nsIFile* aPluginDir);
+  nsresult Init(GeckoMediaPluginService *aService, nsIFile* aPluginDir);
+  nsresult CloneFrom(const GMPParent* aOther);
+
   nsresult LoadProcess();
 
   // Called internally to close this if we don't need it
   void CloseIfUnused();
 
   // Notify all active de/encoders that we are closing, either because of
   // normal shutdown or unexpected shutdown/crash.
   void CloseActive(bool aDieWhenUnloaded);
@@ -104,16 +107,17 @@ public:
   bool CanBeUsedFrom(const nsAString& aOrigin) const;
 
   already_AddRefed<nsIFile> GetDirectory() {
     return nsCOMPtr<nsIFile>(mDirectory).forget();
   }
 
 private:
   ~GMPParent();
+  nsRefPtr<GeckoMediaPluginService> mService;
   bool EnsureProcessLoaded();
   nsresult ReadGMPMetaData();
 #ifdef MOZ_CRASHREPORTER
   void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
   void GetCrashID(nsString& aResult);
 #endif
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
@@ -127,17 +131,18 @@ private:
   GMPState mState;
   nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
   nsString mName; // base name of plugin on disk, UTF-16 because used for paths
   nsCString mDisplayName; // name of plugin displayed to users
   nsCString mDescription; // description of plugin for display to users
   nsCString mVersion;
   nsTArray<nsAutoPtr<GMPCapability>> mCapabilities;
   GMPProcessParent* mProcess;
-  bool mDeleteProcessOnUnload;
+  bool mDeleteProcessOnlyOnUnload;
+  bool mAbnormalShutdownInProgress;
 
   nsTArray<nsRefPtr<GMPVideoDecoderParent>> mVideoDecoders;
   nsTArray<nsRefPtr<GMPVideoEncoderParent>> mVideoEncoders;
 #ifdef DEBUG
   nsCOMPtr<nsIThread> mGMPThread;
 #endif
   // Origin the plugin is assigned to, or empty if the the plugin is not
   // assigned to an origin.
--- a/content/media/gmp/GMPService.cpp
+++ b/content/media/gmp/GMPService.cpp
@@ -10,16 +10,17 @@
 #include "GeckoChildProcessHost.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsXPCOMPrivate.h"
 #include "mozilla/Services.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsIConsoleService.h"
 #include "mozilla/unused.h"
+#include "runnable_utils.h"
 
 namespace mozilla {
 namespace gmp {
 
 static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
 
 class GMPServiceCreateHelper MOZ_FINAL : public nsRunnable
 {
@@ -397,48 +398,78 @@ public:
   }
   already_AddRefed<GMPParent> GetParent() {
     return mParent.forget();
   }
 private:
   nsRefPtr<GMPParent> mParent;
 };
 
+GMPParent*
+GeckoMediaPluginService::ClonePlugin(const GMPParent* aOriginal)
+{
+  MOZ_ASSERT(aOriginal);
+
+  // The GMPParent inherits from IToplevelProtocol, which must be created
+  // on the main thread to be threadsafe. See Bug 1035653.
+  nsRefPtr<CreateGMPParentTask> task(new CreateGMPParentTask());
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+    MOZ_ASSERT(mainThread);
+    mozilla::SyncRunnable::DispatchToThread(mainThread, task);
+  }
+
+  nsRefPtr<GMPParent> gmp = task->GetParent();
+  nsresult rv = gmp->CloneFrom(aOriginal);
+
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Can't Create GMPParent");
+    return nullptr;
+  }
+
+  MutexAutoLock lock(mMutex);
+  mPlugins.AppendElement(gmp);
+
+  return gmp.get();
+}
+
 void
 GeckoMediaPluginService::AddOnGMPThread(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  //LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   // The GMPParent inherits from IToplevelProtocol, which must be created
   // on the main thread to be threadsafe. See Bug 1035653.
   nsRefPtr<CreateGMPParentTask> task(new CreateGMPParentTask());
   nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   MOZ_ASSERT(mainThread);
   mozilla::SyncRunnable::DispatchToThread(mainThread, task);
   nsRefPtr<GMPParent> gmp = task->GetParent();
-  rv = gmp->Init(directory);
+  rv = gmp->Init(this, directory);
   if (NS_FAILED(rv)) {
     NS_WARNING("Can't Create GMPParent");
     return;
   }
 
   MutexAutoLock lock(mMutex);
   mPlugins.AppendElement(gmp);
 }
 
 void
 GeckoMediaPluginService::RemoveOnGMPThread(const nsAString& aDirectory)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  //LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, NS_LossyConvertUTF16toASCII(aDirectory).get()));
 
   nsCOMPtr<nsIFile> directory;
   nsresult rv = NS_NewLocalFile(aDirectory, false, getter_AddRefs(directory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   MutexAutoLock lock(mMutex);
@@ -451,10 +482,35 @@ GeckoMediaPluginService::RemoveOnGMPThre
       return;
     }
   }
   NS_WARNING("Removing GMP which was never added.");
   nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
   cs->LogStringMessage(MOZ_UTF16("Removing GMP which was never added."));
 }
 
+// May remove when Bug 1043671 is fixed
+static void Dummy(nsRefPtr<GMPParent>& aOnDeathsDoor)
+{
+  // exists solely to do nothing and let the Runnable kill the GMPParent
+  // when done.
+}
+
+void
+GeckoMediaPluginService::ReAddOnGMPThread(nsRefPtr<GMPParent>& aOld)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+  //LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, (void*) aOld));
+
+  nsRefPtr<GMPParent> gmp = ClonePlugin(aOld);
+  // Note: both are now in the list
+  // Until we give up the GMPThread, we're safe even if we unlock temporarily
+  // since off-main-thread users just test for existance; they don't modify the list.
+  MutexAutoLock lock(mMutex);
+  mPlugins.RemoveElement(aOld);
+
+  // Schedule aOld to be destroyed.  We can't destroy it from here since we
+  // may be inside ActorDestroyed() for it.
+  NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld));
+}
+
 } // namespace gmp
 } // namespace mozilla
--- a/content/media/gmp/GMPService.h
+++ b/content/media/gmp/GMPService.h
@@ -45,16 +45,21 @@ private:
 
   void UnloadPlugins();
 
   void LoadFromEnvironment();
   void ProcessPossiblePlugin(nsIFile* aDir);
 
   void AddOnGMPThread(const nsAString& aSearchDir);
   void RemoveOnGMPThread(const nsAString& aSearchDir);
+protected:
+  friend class GMPParent;
+  void ReAddOnGMPThread(nsRefPtr<GMPParent>& aOld);
+private:
+  GMPParent* ClonePlugin(const GMPParent* aOriginal);
 
   class PathRunnable : public nsRunnable
   {
   public:
     PathRunnable(GeckoMediaPluginService* service, const nsAString& path,
                  bool add)
       : mService(service)
       , mPath(path)
--- a/content/media/gmp/GMPSharedMemManager.cpp
+++ b/content/media/gmp/GMPSharedMemManager.cpp
@@ -16,22 +16,24 @@ namespace gmp {
 
 // YUV buffers go from Encoder parent to child; pool there, and then return
 // with Decoded() frames to the Decoder parent and goes into the parent pool.
 // Compressed (encoded) data goes from the Decoder parent to the child;
 // pool there, and then return with Encoded() frames and goes into the parent
 // pool.
 static StaticAutoPtr<nsTArray<ipc::Shmem>> sGmpFreelist[GMPSharedMemManager::kGMPNumTypes];
 static uint32_t sGMPShmemManagerCount = 0;
+static uint32_t sGmpAllocated[GMPSharedMemManager::kGMPNumTypes]; // 0's
 
 GMPSharedMemManager::GMPSharedMemManager()
 {
   if (!sGMPShmemManagerCount) {
     for (uint32_t i = 0; i < GMPSharedMemManager::kGMPNumTypes; i++) {
       sGmpFreelist[i] = new nsTArray<ipc::Shmem>();
+      sGmpAllocated[i] = 0;
     }
   }
   sGMPShmemManagerCount++;
 }
 
 GMPSharedMemManager::~GMPSharedMemManager()
 {
   MOZ_ASSERT(sGMPShmemManagerCount > 0);
@@ -44,18 +46,16 @@ GMPSharedMemManager::~GMPSharedMemManage
 }
 
 static nsTArray<ipc::Shmem>&
 GetGmpFreelist(GMPSharedMemManager::GMPMemoryClasses aTypes)
 {
   return *(sGmpFreelist[aTypes]);
 }
 
-static uint32_t sGmpAllocated[GMPSharedMemManager::kGMPNumTypes]; // 0's
-
 bool
 GMPSharedMemManager::MgrAllocShmem(GMPMemoryClasses aClass, size_t aSize,
                                    ipc::Shmem::SharedMemory::SharedMemoryType aType,
                                    ipc::Shmem* aMem)
 {
   CheckThread();
 
   // first look to see if we have a free buffer large enough