Bug 677711 - Kill plugin processes when the child detects the browser is hung. r=bsmedberg
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -107,16 +107,18 @@ child:
rpc NPP_GetSitesWithData()
returns (nsCString[] sites);
// Windows specific message to set up an audio session in the plugin process
async SetAudioSessionData(nsID aID,
nsString aDisplayName,
nsString aIconPath);
+ async SetParentHangTimeout(uint32_t seconds);
+
parent:
/**
* This message is only used on X11 platforms.
*
* Send a dup of the plugin process's X socket to the parent
* process. In theory, this scheme keeps the plugin's X resources
* around until after both the plugin process shuts down *and* the
* parent process closes the dup fd. This is used to prevent the
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -39,16 +39,17 @@
#ifdef MOZ_WIDGET_QT
#include <QtCore/QTimer>
#include "nsQAppInstance.h"
#include "NestedLoopTimer.h"
#endif
#include "mozilla/plugins/PluginModuleChild.h"
+#include "mozilla/ipc/SyncChannel.h"
#ifdef MOZ_WIDGET_GTK2
#include <gtk/gtk.h>
#endif
#include "nsILocalFile.h"
#include "pratom.h"
@@ -509,16 +510,34 @@ PluginModuleChild::ExitedCxxStack()
"nested loop timeout not scheduled");
delete mNestedLoopTimerObject;
mNestedLoopTimerObject = NULL;
}
#endif
bool
+PluginModuleChild::RecvSetParentHangTimeout(const uint32_t& aSeconds)
+{
+#ifdef XP_WIN
+ SetReplyTimeoutMs(((aSeconds > 0) ? (1000 * aSeconds) : 0));
+#endif
+ return true;
+}
+
+bool
+PluginModuleChild::ShouldContinueFromReplyTimeout()
+{
+#ifdef XP_WIN
+ NS_RUNTIMEABORT("terminating child process");
+#endif
+ return true;
+}
+
+bool
PluginModuleChild::InitGraphics()
{
#if defined(MOZ_WIDGET_GTK2)
// Work around plugins that don't interact well with GDK
// client-side windows.
PR_SetEnv("GDK_NATIVE_WINDOWS=1");
gtk_init(0, 0);
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -106,16 +106,19 @@ class PluginModuleChild : public PPlugin
protected:
NS_OVERRIDE
virtual mozilla::ipc::RPCChannel::RacyRPCPolicy
MediateRPCRace(const Message& parent, const Message& child)
{
return MediateRace(parent, child);
}
+ NS_OVERRIDE
+ virtual bool ShouldContinueFromReplyTimeout();
+
// Implement the PPluginModuleChild interface
virtual bool AnswerNP_GetEntryPoints(NPError* rv);
virtual bool AnswerNP_Initialize(NativeThreadId* tid, NPError* rv);
virtual PPluginIdentifierChild*
AllocPPluginIdentifier(const nsCString& aString,
const int32_t& aInt,
const bool& aTemporary);
@@ -163,16 +166,19 @@ protected:
virtual bool
AnswerNPP_GetSitesWithData(InfallibleTArray<nsCString>* aResult);
virtual bool
RecvSetAudioSessionData(const nsID& aId,
const nsString& aDisplayName,
const nsString& aIconPath);
+ virtual bool
+ RecvSetParentHangTimeout(const uint32_t& aSeconds);
+
virtual void
ActorDestroy(ActorDestroyReason why);
NS_NORETURN void QuickExit();
NS_OVERRIDE virtual bool
RecvProcessNativeEventsInRPCCall();
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -74,17 +74,18 @@ using base::KillProcess;
using mozilla::PluginLibrary;
using mozilla::ipc::SyncChannel;
using namespace mozilla;
using namespace mozilla::plugins;
using namespace mozilla::plugins::parent;
-static const char kTimeoutPref[] = "dom.ipc.plugins.timeoutSecs";
+static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs";
+static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs";
static const char kLaunchTimeoutPref[] = "dom.ipc.plugins.processLaunchTimeoutSecs";
template<>
struct RunnableMethodTraits<mozilla::plugins::PluginModuleParent>
{
typedef mozilla::plugins::PluginModuleParent Class;
static void RetainCallee(Class* obj) { }
static void ReleaseCallee(Class* obj) { }
@@ -104,17 +105,17 @@ PluginModuleParent::LoadModule(const cha
if (!launched) {
// Need to set this so the destructor doesn't complain.
parent->mShutdown = true;
return nsnull;
}
parent->Open(parent->mSubprocess->GetChannel(),
parent->mSubprocess->GetChildProcessHandle());
- TimeoutChanged(kTimeoutPref, parent);
+ TimeoutChanged(kChildTimeoutPref, parent);
return parent.forget();
}
PluginModuleParent::PluginModuleParent(const char* aFilePath)
: mSubprocess(new PluginProcessParent(aFilePath))
, mPluginThread(0)
, mShutdown(false)
@@ -126,17 +127,18 @@ PluginModuleParent::PluginModuleParent(c
, mTaskFactory(this)
{
NS_ASSERTION(mSubprocess, "Out of memory!");
if (!mIdentifiers.Init()) {
NS_ERROR("Out of memory");
}
- Preferences::RegisterCallback(TimeoutChanged, kTimeoutPref, this);
+ Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this);
+ Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this);
}
PluginModuleParent::~PluginModuleParent()
{
NS_ASSERTION(OkToCleanup(), "unsafe destruction");
#ifdef OS_MACOSX
if (mCATimer) {
@@ -151,17 +153,18 @@ PluginModuleParent::~PluginModuleParent(
}
NS_ASSERTION(mShutdown, "NP_Shutdown didn't");
if (mSubprocess) {
mSubprocess->Delete();
mSubprocess = nsnull;
}
- Preferences::UnregisterCallback(TimeoutChanged, kTimeoutPref, this);
+ Preferences::UnregisterCallback(TimeoutChanged, kChildTimeoutPref, this);
+ Preferences::UnregisterCallback(TimeoutChanged, kParentTimeoutPref, this);
}
#ifdef MOZ_CRASHREPORTER
void
PluginModuleParent::WritePluginExtraDataForMinidump(const nsAString& id)
{
typedef nsDependentCString CS;
@@ -221,24 +224,27 @@ PluginModuleParent::RecvAppendNotesToCra
mCrashNotes.Append(aNotes);
return true;
}
int
PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thead!");
- NS_ABORT_IF_FALSE(!strcmp(aPref, kTimeoutPref),
- "unexpected pref callback");
-
- PRInt32 timeoutSecs = Preferences::GetInt(kTimeoutPref, 0);
- int32 timeoutMs = (timeoutSecs > 0) ? (1000 * timeoutSecs) :
- SyncChannel::kNoTimeout;
-
- static_cast<PluginModuleParent*>(aModule)->SetReplyTimeoutMs(timeoutMs);
+ if (!strcmp(aPref, kChildTimeoutPref)) {
+ // The timeout value used by the parent for children
+ PRInt32 timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0);
+ int32 timeoutMs = (timeoutSecs > 0) ? (1000 * timeoutSecs) :
+ SyncChannel::kNoTimeout;
+ static_cast<PluginModuleParent*>(aModule)->SetReplyTimeoutMs(timeoutMs);
+ } else if (!strcmp(aPref, kParentTimeoutPref)) {
+ // The timeout value used by the child for its parent
+ PRInt32 timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0);
+ static_cast<PluginModuleParent*>(aModule)->SendSetParentHangTimeout(timeoutSecs);
+ }
return 0;
}
void
PluginModuleParent::CleanupFromTimeout()
{
if (!mShutdown)
Close();
@@ -916,16 +922,18 @@ PluginModuleParent::NPP_New(NPMIMEType p
return NS_ERROR_FAILURE;
}
if (*error != NPERR_NO_ERROR) {
NPP_Destroy(instance, 0);
return NS_ERROR_FAILURE;
}
+ TimeoutChanged(kParentTimeoutPref, this);
+
return NS_OK;
}
nsresult
PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags,
uint64_t maxAge)
{
if (!mClearSiteDataSupported)
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1448,23 +1448,28 @@ pref("editor.positioning.offset",
pref("dom.max_chrome_script_run_time", 20);
pref("dom.max_script_run_time", 10);
#ifndef DEBUG
// How long a plugin is allowed to process a synchronous IPC message
// before we consider it "hung".
pref("dom.ipc.plugins.timeoutSecs", 45);
+// How long a plugin process will wait for a response from the parent
+// to a synchronous request before terminating itself. After this
+// point the child assumes the parent is hung.
+pref("dom.ipc.plugins.parentTimeoutSecs", 15);
// How long a plugin launch is allowed to take before
// we consider it failed.
pref("dom.ipc.plugins.processLaunchTimeoutSecs", 45);
#else
// No timeout in DEBUG builds
pref("dom.ipc.plugins.timeoutSecs", 0);
pref("dom.ipc.plugins.processLaunchTimeoutSecs", 0);
+pref("dom.ipc.plugins.parentTimeoutSecs", 0);
#endif
// Disable oopp for standard java. They run their own process isolation (which
// conflicts with our implementation, at least on Windows).
pref("dom.ipc.plugins.java.enabled", false);
#ifndef ANDROID
#ifndef XP_MACOSX