Bug 1173634 - Report pending async shutdowns in shutdown hangs. r=cpearce

--HG--
extra : rebase_source : 1b3f4d52abf0bacd066848834728783e9069a545
This commit is contained in:
Gerald Squelart 2015-06-17 17:35:00 +02:00
Родитель f4ccd23df1
Коммит b383286615
2 изменённых файлов: 100 добавлений и 37 удалений

Просмотреть файл

@ -34,6 +34,10 @@
#include "nsHashKeys.h"
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
#if defined(MOZ_CRASHREPORTER)
#include "nsExceptionHandler.h"
#endif
#include <limits>
namespace mozilla {
@ -79,7 +83,7 @@ static bool sHaveSetTimeoutPrefCache = false;
GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent()
: mShuttingDown(false)
, mScannedPluginOnDisk(false)
, mWaitingForPluginsAsyncShutdown(false)
, mWaitingForPluginsSyncShutdown(false)
{
MOZ_ASSERT(NS_IsMainThread());
if (!sHaveSetTimeoutPrefCache) {
@ -210,10 +214,11 @@ GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
// back to the GMPParent, which then registers the GMPParent by calling
// GMPService::AsyncShutdownNeeded().
//
// On shutdown, we set mWaitingForPluginsAsyncShutdown to true, and then
// On shutdown, we set mWaitingForPluginsSyncShutdown to true, and then
// call UnloadPlugins on the GMPThread, and process events on the main
// thread until an event sets mWaitingForPluginsAsyncShutdown=false on
// the main thread.
// thread until 1. An event sets mWaitingForPluginsSyncShutdown=false on
// the main thread; then 2. All async-shutdown plugins have indicated
// they have completed shutdown.
//
// UnloadPlugins() sends close messages for all plugins' API objects to
// the GMP interfaces in the child process, and then sends the async
@ -221,14 +226,14 @@ GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
// shutdown, it calls GMPAsyncShutdownHost::ShutdownComplete(), which
// sends a message back to the parent, which calls
// GMPService::AsyncShutdownComplete(). If all plugins requiring async
// shutdown have called AsyncShutdownComplete() we stick an event on the
// main thread to set mWaitingForPluginsAsyncShutdown=false. We must use
// an event to do this, as we must ensure the main thread processes an
// shutdown have called AsyncShutdownComplete() we stick a dummy event on
// the main thread, where the list of pending plugins is checked. We must
// use an event to do this, as we must ensure the main thread processes an
// event to run its loop. This will unblock the main thread, and shutdown
// of other components will proceed.
//
// We set a timer in UnloadPlugins(), and abort waiting for async
// shutdown if the GMPs are taking too long to shutdown.
// During shutdown, each GMPParent starts a timer, and pretends shutdown
// is complete if it is taking too long.
//
// We shutdown in "profile-change-teardown", as the profile dir is
// still writable then, and it's required for GMPStorage. We block the
@ -238,7 +243,7 @@ GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
// GMPStorage needs to work up until the shutdown-complete notification
// arrives from the GMP process.
mWaitingForPluginsAsyncShutdown = true;
mWaitingForPluginsSyncShutdown = true;
nsCOMPtr<nsIThread> gmpThread;
{
@ -249,18 +254,56 @@ GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
}
if (gmpThread) {
LOGD(("%s::%s Starting to unload plugins, waiting for first sync shutdown..."
, __CLASS__, __FUNCTION__));
gmpThread->Dispatch(
NS_NewRunnableMethod(this,
&GeckoMediaPluginServiceParent::UnloadPlugins),
NS_DISPATCH_NORMAL);
} else {
MOZ_ASSERT(mPlugins.IsEmpty());
mWaitingForPluginsAsyncShutdown = false;
}
// Wait for plugins to do async shutdown...
while (mWaitingForPluginsAsyncShutdown) {
NS_ProcessNextEvent(NS_GetCurrentThread(), true);
// Wait for UnloadPlugins() to do initial sync shutdown...
while (mWaitingForPluginsSyncShutdown) {
NS_ProcessNextEvent(NS_GetCurrentThread(), true);
}
// Wait for other plugins (if any) to do async shutdown...
auto syncShutdownPluginsRemaining =
std::numeric_limits<decltype(mAsyncShutdownPlugins.Length())>::max();
for (;;) {
{
MutexAutoLock lock(mMutex);
if (mAsyncShutdownPlugins.IsEmpty()) {
LOGD(("%s::%s Finished unloading all plugins"
, __CLASS__, __FUNCTION__));
#if defined(MOZ_CRASHREPORTER)
CrashReporter::RemoveCrashReportAnnotation(
NS_LITERAL_CSTRING("AsyncPluginShutdown"));
#endif
break;
} else if (mAsyncShutdownPlugins.Length() < syncShutdownPluginsRemaining) {
// First time here, or number of pending plugins has decreased.
// -> Update list of pending plugins in crash report.
syncShutdownPluginsRemaining = mAsyncShutdownPlugins.Length();
LOGD(("%s::%s Still waiting for %d plugins to shutdown..."
, __CLASS__, __FUNCTION__, (int)syncShutdownPluginsRemaining));
#if defined(MOZ_CRASHREPORTER)
nsAutoCString names;
for (const auto& plugin : mAsyncShutdownPlugins) {
if (!names.IsEmpty()) { names.Append(NS_LITERAL_CSTRING(", ")); }
names.Append(plugin->GetDisplayName());
}
CrashReporter::AnnotateCrashReport(
NS_LITERAL_CSTRING("AsyncPluginShutdown"),
names);
#endif
}
}
NS_ProcessNextEvent(NS_GetCurrentThread(), true);
}
} else {
// GMP thread has already shutdown.
MOZ_ASSERT(mPlugins.IsEmpty());
mWaitingForPluginsSyncShutdown = false;
}
} else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
@ -326,6 +369,7 @@ GeckoMediaPluginServiceParent::AsyncShutdownNeeded(GMPParent* aParent)
LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MutexAutoLock lock(mMutex);
MOZ_ASSERT(!mAsyncShutdownPlugins.Contains(aParent));
mAsyncShutdownPlugins.AppendElement(aParent);
}
@ -333,39 +377,60 @@ GeckoMediaPluginServiceParent::AsyncShutdownNeeded(GMPParent* aParent)
void
GeckoMediaPluginServiceParent::AsyncShutdownComplete(GMPParent* aParent)
{
LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
LOGD(("%s::%s %p '%s'", __CLASS__, __FUNCTION__,
aParent, aParent->GetDisplayName().get()));
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
mAsyncShutdownPlugins.RemoveElement(aParent);
if (mAsyncShutdownPlugins.IsEmpty() && mShuttingDownOnGMPThread) {
{
MutexAutoLock lock(mMutex);
mAsyncShutdownPlugins.RemoveElement(aParent);
}
if (mShuttingDownOnGMPThread) {
// The main thread may be waiting for async shutdown of plugins,
// which has completed. Break the main thread out of its waiting loop.
// one of which has completed. Wake up the main thread by sending a task.
nsCOMPtr<nsIRunnable> task(NS_NewRunnableMethod(
this, &GeckoMediaPluginServiceParent::SetAsyncShutdownComplete));
this, &GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete));
NS_DispatchToMainThread(task);
}
}
void
GeckoMediaPluginServiceParent::SetAsyncShutdownComplete()
GeckoMediaPluginServiceParent::NotifyAsyncShutdownComplete()
{
MOZ_ASSERT(NS_IsMainThread());
mWaitingForPluginsAsyncShutdown = false;
// Nothing to do, this task is just used to wake up the event loop in Observe().
}
void
GeckoMediaPluginServiceParent::NotifySyncShutdownComplete()
{
MOZ_ASSERT(NS_IsMainThread());
mWaitingForPluginsSyncShutdown = false;
}
void
GeckoMediaPluginServiceParent::UnloadPlugins()
{
LOGD(("%s::%s async_shutdown=%d", __CLASS__, __FUNCTION__,
mAsyncShutdownPlugins.Length()));
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MOZ_ASSERT(!mShuttingDownOnGMPThread);
mShuttingDownOnGMPThread = true;
{
MutexAutoLock lock(mMutex);
// Note: CloseActive is async; it will actually finish
LOGD(("%s::%s plugins:%u including async:%u", __CLASS__, __FUNCTION__,
mPlugins.Length(), mAsyncShutdownPlugins.Length()));
#ifdef DEBUG
for (const auto& plugin : mPlugins) {
LOGD(("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__,
plugin->GetDisplayName().get()));
}
for (const auto& plugin : mAsyncShutdownPlugins) {
LOGD(("%s::%s async plugin: '%s'", __CLASS__, __FUNCTION__,
plugin->GetDisplayName().get()));
}
#endif
// Note: CloseActive may be async; it could actually finish
// shutting down when all the plugins have unloaded.
for (size_t i = 0; i < mPlugins.Length(); i++) {
mPlugins[i]->CloseActive(true);
@ -373,11 +438,9 @@ GeckoMediaPluginServiceParent::UnloadPlugins()
mPlugins.Clear();
}
if (mAsyncShutdownPlugins.IsEmpty()) {
nsCOMPtr<nsIRunnable> task(NS_NewRunnableMethod(
this, &GeckoMediaPluginServiceParent::SetAsyncShutdownComplete));
NS_DispatchToMainThread(task);
}
nsCOMPtr<nsIRunnable> task(NS_NewRunnableMethod(
this, &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete));
NS_DispatchToMainThread(task);
}
void

Просмотреть файл

@ -73,7 +73,8 @@ private:
void UnloadPlugins();
void CrashPlugins();
void SetAsyncShutdownComplete();
void NotifySyncShutdownComplete();
void NotifyAsyncShutdownComplete();
void LoadFromEnvironment();
void ProcessPossiblePlugin(nsIFile* aDir);
@ -138,6 +139,7 @@ private:
// Protected by mMutex from the base class.
nsTArray<nsRefPtr<GMPParent>> mPlugins;
bool mShuttingDown;
nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins;
// True if we've inspected MOZ_GMP_PATH on the GMP thread and loaded any
// plugins found there into mPlugins.
@ -158,9 +160,7 @@ private:
T mValue;
};
MainThreadOnly<bool> mWaitingForPluginsAsyncShutdown;
nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins; // GMP Thread only.
MainThreadOnly<bool> mWaitingForPluginsSyncShutdown;
nsTArray<nsString> mPluginsWaitingForDeletion;