Bug 677711 - Kill plugin processes when the child detects the browser is hung. r=bsmedberg
authorJim Mathies <jmathies@mozilla.com>
Tue, 16 Aug 2011 05:25:34 -0500
changeset 75395 bef7bcdcd41e563f14b43439997f5d7689aaaeaa
parent 75363 85b5d609a73cddbf7f09e4b708266975c24e7731
child 75396 f9379ad86c5d1afdadd75bf4a468c4a2dab968f9
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewersbsmedberg
bugs677711
milestone8.0a1
Bug 677711 - Kill plugin processes when the child detects the browser is hung. r=bsmedberg
dom/plugins/ipc/PPluginModule.ipdl
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/PluginModuleChild.h
dom/plugins/ipc/PluginModuleParent.cpp
modules/libpref/src/init/all.js
--- 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