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
--- 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()
{