Bug 1035056 part 3 - Implement GMPAsyncShutdown interface. r=jesup

This commit is contained in:
Chris Pearce 2014-08-18 09:41:56 +12:00
Родитель 4da7fabe5c
Коммит 31a75b4662
9 изменённых файлов: 352 добавлений и 46 удалений

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

@ -37,7 +37,8 @@ namespace mozilla {
namespace gmp {
GMPChild::GMPChild()
: mLib(nullptr)
: mAsyncShutdown(nullptr)
, mLib(nullptr)
, mGetAPIFunc(nullptr)
, mGMPMessageLoop(MessageLoop::current())
{
@ -183,6 +184,14 @@ GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
return false;
}
void* sh = nullptr;
GMPAsyncShutdownHost* host = static_cast<GMPAsyncShutdownHost*>(this);
GMPErr err = mGetAPIFunc("async-shutdown", host, &sh);
if (err == GMPNoErr && sh) {
mAsyncShutdown = reinterpret_cast<GMPAsyncShutdown*>(sh);
SendAsyncShutdownRequired();
}
return true;
}
@ -397,5 +406,24 @@ GMPChild::RecvCrashPluginNow()
return true;
}
bool
GMPChild::RecvBeginAsyncShutdown()
{
MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
if (mAsyncShutdown) {
mAsyncShutdown->BeginShutdown();
} else {
ShutdownComplete();
}
return true;
}
void
GMPChild::ShutdownComplete()
{
MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current());
SendAsyncShutdownComplete();
}
} // namespace gmp
} // namespace mozilla

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

@ -9,14 +9,16 @@
#include "mozilla/gmp/PGMPChild.h"
#include "GMPSharedMemManager.h"
#include "GMPTimerChild.h"
#include "gmp-async-shutdown.h"
#include "gmp-entrypoints.h"
#include "prlink.h"
namespace mozilla {
namespace gmp {
class GMPChild : public PGMPChild,
public GMPSharedMem
class GMPChild : public PGMPChild
, public GMPSharedMem
, public GMPAsyncShutdownHost
{
public:
GMPChild();
@ -39,6 +41,9 @@ public:
// GMPSharedMem
virtual void CheckThread() MOZ_OVERRIDE;
// GMPAsyncShutdownHost
void ShutdownComplete() MOZ_OVERRIDE;
private:
virtual PCrashReporterChild* AllocPCrashReporterChild(const NativeThreadId& aThread) MOZ_OVERRIDE;
virtual bool DeallocPCrashReporterChild(PCrashReporterChild*) MOZ_OVERRIDE;
@ -63,10 +68,12 @@ private:
virtual bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) MOZ_OVERRIDE;
virtual bool RecvCrashPluginNow() MOZ_OVERRIDE;
virtual bool RecvBeginAsyncShutdown() MOZ_OVERRIDE;
virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
virtual void ProcessingError(Result aWhat) MOZ_OVERRIDE;
GMPAsyncShutdown* mAsyncShutdown;
nsRefPtr<GMPTimerChild> mTimerChild;
PRLibrary* mLib;

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

@ -55,6 +55,8 @@ GMPParent::GMPParent()
, mProcess(nullptr)
, mDeleteProcessOnlyOnUnload(false)
, mAbnormalShutdownInProgress(false)
, mAsyncShutdownRequired(false)
, mAsyncShutdownInProgress(false)
{
}
@ -149,6 +151,8 @@ void
GMPParent::CloseIfUnused()
{
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
LOGD(("%s::%s: %p mAsyncShutdownRequired=%d", __CLASS__, __FUNCTION__, this,
mAsyncShutdownRequired));
if ((mDeleteProcessOnlyOnUnload ||
mState == GMPStateLoaded ||
@ -157,8 +161,32 @@ GMPParent::CloseIfUnused()
mVideoEncoders.IsEmpty() &&
mDecryptors.IsEmpty() &&
mAudioDecoders.IsEmpty()) {
if (mAsyncShutdownRequired) {
if (!mAsyncShutdownInProgress) {
LOGD(("%s::%s: %p sending async shutdown notification", __CLASS__,
__FUNCTION__, this));
mAsyncShutdownInProgress = true;
if (!SendBeginAsyncShutdown()) {
AbortAsyncShutdown();
}
}
} else {
Shutdown();
}
}
}
void
GMPParent::AbortAsyncShutdown()
{
MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
nsRefPtr<GMPParent> kungFuDeathGrip(this);
mService->AsyncShutdownComplete(this);
mAsyncShutdownRequired = false;
mAsyncShutdownInProgress = false;
CloseIfUnused();
}
void
@ -229,7 +257,6 @@ GMPParent::Shutdown()
return;
}
mState = GMPStateClosing;
DeleteProcess();
// XXX Get rid of mDeleteProcessOnlyOnUnload and this code when
// Bug 1043671 is fixed
@ -245,10 +272,13 @@ void
GMPParent::DeleteProcess()
{
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
if (mState != GMPStateClosing) {
// Don't Close() twice!
// Probably remove when bug 1043671 is resolved
MOZ_ASSERT(mState == GMPStateClosing);
mState = GMPStateClosing;
Close();
}
mProcess->Delete();
LOGD(("%s::%s: Shut down process %p", __CLASS__, __FUNCTION__, (void *) mProcess));
mProcess = nullptr;
@ -532,8 +562,15 @@ GMPParent::ActorDestroy(ActorDestroyReason aWhy)
// Normal Shutdown() will delete the process on unwind.
if (AbnormalShutdown == aWhy) {
mState = GMPStateClosing;
nsRefPtr<GMPParent> self(this);
if (mAsyncShutdownRequired) {
mService->AsyncShutdownComplete(this);
mAsyncShutdownRequired = false;
}
// Must not call Close() again in DeleteProcess(), as we'll recurse
// infinitely if we do.
MOZ_ASSERT(mState == GMPStateClosing);
DeleteProcess();
// Note: final destruction will be Dispatched to ourself
mService->ReAddOnGMPThread(self);
}
@ -631,7 +668,7 @@ PGMPTimerParent*
GMPParent::AllocPGMPTimerParent()
{
GMPTimerParent* p = new GMPTimerParent(GMPThread());
NS_ADDREF(p); // Released in DeallocPGMPTimerParent.
mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
return p;
}
@ -639,7 +676,8 @@ bool
GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
{
GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
NS_RELEASE(p);
p->Shutdown();
mTimers.RemoveElement(p);
return true;
}
@ -806,5 +844,24 @@ GMPParent::SetOrigin(const nsAString& aOrigin)
mOrigin = aOrigin;
}
bool
GMPParent::RecvAsyncShutdownRequired()
{
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
mAsyncShutdownRequired = true;
mService->AsyncShutdownNeeded(this);
return true;
}
bool
GMPParent::RecvAsyncShutdownComplete()
{
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
MOZ_ASSERT(mAsyncShutdownRequired);
AbortAsyncShutdown();
return true;
}
} // namespace gmp
} // namespace mozilla

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

@ -126,6 +126,8 @@ public:
// GMPSharedMem
virtual void CheckThread() MOZ_OVERRIDE;
void AbortAsyncShutdown();
private:
~GMPParent();
nsRefPtr<GeckoMediaPluginService> mService;
@ -156,6 +158,9 @@ private:
virtual PGMPTimerParent* AllocPGMPTimerParent() MOZ_OVERRIDE;
virtual bool DeallocPGMPTimerParent(PGMPTimerParent* aActor) MOZ_OVERRIDE;
virtual bool RecvAsyncShutdownComplete() MOZ_OVERRIDE;
virtual bool RecvAsyncShutdownRequired() MOZ_OVERRIDE;
GMPState mState;
nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
nsString mName; // base name of plugin on disk, UTF-16 because used for paths
@ -171,10 +176,14 @@ private:
nsTArray<nsRefPtr<GMPVideoEncoderParent>> mVideoEncoders;
nsTArray<nsRefPtr<GMPDecryptorParent>> mDecryptors;
nsTArray<nsRefPtr<GMPAudioDecoderParent>> mAudioDecoders;
nsTArray<nsRefPtr<GMPTimerParent>> mTimers;
nsCOMPtr<nsIThread> mGMPThread;
// Origin the plugin is assigned to, or empty if the the plugin is not
// assigned to an origin.
nsAutoString mOrigin;
bool mAsyncShutdownRequired;
bool mAsyncShutdownInProgress;
};
} // namespace gmp

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

@ -30,10 +30,12 @@ GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
: GeckoChildProcessHost(GeckoProcessType_GMPlugin),
mGMPPath(aGMPPath)
{
MOZ_COUNT_CTOR(GMPProcessParent);
}
GMPProcessParent::~GMPProcessParent()
{
MOZ_COUNT_DTOR(GMPProcessParent);
}
bool

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

@ -19,6 +19,8 @@
#include "mozilla/unused.h"
#include "GMPDecryptorParent.h"
#include "GMPAudioDecoderParent.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/Preferences.h"
#include "runnable_utils.h"
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
#include "mozilla/Sandbox.h"
@ -127,17 +129,29 @@ GeckoMediaPluginService::GetGeckoMediaPluginService()
NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, nsIObserver)
#define GMP_DEFAULT_ASYNC_SHUTDONW_TIMEOUT 3000
static int32_t sMaxAsyncShutdownWaitMs = 0;
GeckoMediaPluginService::GeckoMediaPluginService()
: mMutex("GeckoMediaPluginService::mMutex")
, mShuttingDown(false)
, mShuttingDownOnGMPThread(false)
, mWaitingForPluginsAsyncShutdown(false)
{
MOZ_ASSERT(NS_IsMainThread());
static bool setTimeoutPrefCache = false;
if (!setTimeoutPrefCache) {
setTimeoutPrefCache = true;
Preferences::AddIntVarCache(&sMaxAsyncShutdownWaitMs,
"media.gmp.async-shutdown-timeout",
GMP_DEFAULT_ASYNC_SHUTDONW_TIMEOUT);
}
}
GeckoMediaPluginService::~GeckoMediaPluginService()
{
MOZ_ASSERT(mPlugins.IsEmpty());
MOZ_ASSERT(mAsyncShutdownPlugins.IsEmpty());
}
void
@ -147,7 +161,7 @@ GeckoMediaPluginService::Init()
nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
MOZ_ASSERT(obsService);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)));
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, "profile-change-teardown", false)));
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)));
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
@ -160,11 +174,22 @@ GeckoMediaPluginService::Init()
unused << GetThread(getter_AddRefs(thread));
}
void
AbortWaitingForGMPAsyncShutdown(nsITimer* aTimer, void* aClosure)
{
NS_WARNING("Timed out waiting for GMP async shutdown!");
nsRefPtr<GeckoMediaPluginService> service = sSingletonService.get();
if (service) {
service->AbortAsyncShutdown();
}
}
NS_IMETHODIMP
GeckoMediaPluginService::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aSomeData)
{
LOGD(("%s::%s: %s", __CLASS__, __FUNCTION__, aTopic));
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
nsCOMPtr<nsIPrefBranch> branch( do_QueryInterface(aSubject) );
if (branch) {
@ -184,7 +209,50 @@ GeckoMediaPluginService::Observe(nsISupports* aSubject,
}
}
}
} else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
} else if (!strcmp("profile-change-teardown", aTopic)) {
// How shutdown works:
//
// Some GMPs require time to do bookkeeping upon shutdown. These GMPs
// need to be given time to access storage during shutdown. To signal
// that time to shutdown is required, those GMPs implement the
// GMPAsyncShutdown interface.
//
// When we startup the child process, we query the GMP for the
// GMPAsyncShutdown interface, and if it's present, we send a message
// back to the GMPParent, which then registers the GMPParent by calling
// GMPService::AsyncShutdownNeeded().
//
// On shutdown, we set mWaitingForPluginsAsyncShutdown 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.
//
// UnloadPlugins() sends close messages for all plugins' API objects to
// the GMP interfaces in the child process, and then sends the async
// shutdown notifications to child GMPs. When a GMP has completed its
// 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
// 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.
//
// We shutdown in "profile-change-teardown", as the profile dir is
// still writable then, and it's required for GMPStorage. We block the
// shutdown process by spinning the main thread event loop until all GMPs
// have shutdown, or timeout has occurred.
//
// GMPStorage needs to work up until the shutdown-complete notification
// arrives from the GMP process.
mWaitingForPluginsAsyncShutdown = true;
nsCOMPtr<nsIThread> gmpThread;
{
MutexAutoLock lock(mMutex);
@ -194,11 +262,18 @@ GeckoMediaPluginService::Observe(nsISupports* aSubject,
}
if (gmpThread) {
gmpThread->Dispatch(NS_NewRunnableMethod(this, &GeckoMediaPluginService::UnloadPlugins),
NS_DISPATCH_SYNC);
gmpThread->Dispatch(
NS_NewRunnableMethod(this, &GeckoMediaPluginService::UnloadPlugins),
NS_DISPATCH_NORMAL);
} else {
MOZ_ASSERT(mPlugins.IsEmpty());
}
// Wait for plugins to do async shutdown...
while (mWaitingForPluginsAsyncShutdown) {
NS_ProcessNextEvent(NS_GetCurrentThread(), true);
}
} else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) {
nsCOMPtr<nsIThread> gmpThread;
{
@ -383,14 +458,88 @@ GeckoMediaPluginService::GetGMPDecryptor(nsTArray<nsCString>* aTags,
return NS_OK;
}
void
GeckoMediaPluginService::AsyncShutdownNeeded(GMPParent* aParent)
{
LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
mAsyncShutdownPlugins.AppendElement(aParent);
}
void
GeckoMediaPluginService::AsyncShutdownComplete(GMPParent* aParent)
{
LOGD(("%s::%s %p", __CLASS__, __FUNCTION__, aParent));
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
mAsyncShutdownPlugins.RemoveElement(aParent);
if (mAsyncShutdownPlugins.IsEmpty() && mShuttingDownOnGMPThread) {
// The main thread is waiting for async shutdown of plugins,
// which has completed. Break the main thread out of its waiting loop.
AbortAsyncShutdown();
}
}
void
GeckoMediaPluginService::SetAsyncShutdownComplete()
{
MOZ_ASSERT(NS_IsMainThread());
mWaitingForPluginsAsyncShutdown = false;
}
void
GeckoMediaPluginService::AbortAsyncShutdown()
{
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
for (size_t i = 0; i < mAsyncShutdownPlugins.Length(); i++) {
mAsyncShutdownPlugins[i]->AbortAsyncShutdown();
}
mAsyncShutdownPlugins.Clear();
if (mAsyncShutdownTimeout) {
mAsyncShutdownTimeout->Cancel();
mAsyncShutdownTimeout = nullptr;
}
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethod(
this, &GeckoMediaPluginService::SetAsyncShutdownComplete));
NS_DispatchToMainThread(task);
}
nsresult
GeckoMediaPluginService::SetAsyncShutdownTimeout()
{
MOZ_ASSERT(!mAsyncShutdownTimeout);
nsresult rv;
mAsyncShutdownTimeout = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to create timer for async GMP shutdown");
return NS_OK;
}
// Set timer to abort waiting for plugins to shutdown if they take
// too long.
rv = mAsyncShutdownTimeout->SetTarget(mGMPThread);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return mAsyncShutdownTimeout->InitWithFuncCallback(
&AbortWaitingForGMPAsyncShutdown, nullptr, sMaxAsyncShutdownWaitMs,
nsITimer::TYPE_ONE_SHOT);
}
void
GeckoMediaPluginService::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
// shutting down when all the plugins have unloaded.
@ -398,11 +547,28 @@ GeckoMediaPluginService::UnloadPlugins()
mPlugins[i]->CloseActive(true);
}
mPlugins.Clear();
}
if (!mAsyncShutdownPlugins.IsEmpty()) {
// We have plugins that require async shutdown. Set a timer to abort
// waiting if they take too long to shutdown.
if (NS_FAILED(SetAsyncShutdownTimeout())) {
mAsyncShutdownPlugins.Clear();
}
}
if (mAsyncShutdownPlugins.IsEmpty()) {
mAsyncShutdownPlugins.Clear();
nsRefPtr<nsIRunnable> task(NS_NewRunnableMethod(
this, &GeckoMediaPluginService::SetAsyncShutdownComplete));
NS_DispatchToMainThread(task);
}
}
void
GeckoMediaPluginService::CrashPlugins()
{
LOGD(("%s::%s", __CLASS__, __FUNCTION__));
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
MutexAutoLock lock(mMutex);
@ -645,7 +811,11 @@ GeckoMediaPluginService::ReAddOnGMPThread(nsRefPtr<GMPParent>& aOld)
MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, (void*) aOld));
nsRefPtr<GMPParent> gmp = ClonePlugin(aOld);
nsRefPtr<GMPParent> gmp;
if (!mShuttingDownOnGMPThread) {
// Don't re-add plugin if we're shutting down. Let the old plugin die.
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.

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

@ -10,11 +10,12 @@
#include "mozIGeckoMediaPluginService.h"
#include "nsIObserver.h"
#include "nsTArray.h"
#include "mozilla/Mutex.h"
#include "mozilla/Monitor.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsIThread.h"
#include "nsThreadUtils.h"
#include "nsITimer.h"
template <class> struct already_AddRefed;
@ -36,6 +37,10 @@ public:
NS_DECL_MOZIGECKOMEDIAPLUGINSERVICE
NS_DECL_NSIOBSERVER
void AsyncShutdownNeeded(GMPParent* aParent);
void AsyncShutdownComplete(GMPParent* aParent);
void AbortAsyncShutdown();
private:
~GeckoMediaPluginService();
@ -45,12 +50,16 @@ private:
void UnloadPlugins();
void CrashPlugins();
void SetAsyncShutdownComplete();
void LoadFromEnvironment();
void ProcessPossiblePlugin(nsIFile* aDir);
void AddOnGMPThread(const nsAString& aSearchDir);
void RemoveOnGMPThread(const nsAString& aSearchDir);
nsresult SetAsyncShutdownTimeout();
protected:
friend class GMPParent;
void ReAddOnGMPThread(nsRefPtr<GMPParent>& aOld);
@ -80,6 +89,26 @@ private:
nsCOMPtr<nsIThread> mGMPThread;
bool mShuttingDown;
bool mShuttingDownOnGMPThread;
template<typename T>
class MainThreadOnly {
public:
MainThreadOnly(T aValue)
: mValue(aValue)
{}
operator T&() {
MOZ_ASSERT(NS_IsMainThread());
return mValue;
}
private:
T mValue;
};
MainThreadOnly<bool> mWaitingForPluginsAsyncShutdown;
nsTArray<nsRefPtr<GMPParent>> mAsyncShutdownPlugins; // GMP Thread only.
nsCOMPtr<nsITimer> mAsyncShutdownTimeout; // GMP Thread only.
};
} // namespace gmp

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

@ -28,12 +28,16 @@ parent:
async PCrashReporter(NativeThreadId tid);
async PGMPTimer();
async AsyncShutdownComplete();
async AsyncShutdownRequired();
child:
async PGMPAudioDecoder();
async PGMPDecryptor();
async PGMPVideoDecoder();
async PGMPVideoEncoder();
async BeginAsyncShutdown();
async CrashPluginNow();
};