From 7e8db9f9a4e296d006b247573eb777fcf1b62a61 Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Wed, 2 Sep 2009 17:18:27 -0700 Subject: [PATCH] Force child processes to close and wait for them on shutdown --- dom/ipc/ContentProcess.ipdl | 2 + dom/ipc/ContentProcessChild.cpp | 24 +++++++ dom/ipc/ContentProcessChild.h | 3 + dom/ipc/ContentProcessParent.cpp | 71 +++++++++++++++++-- dom/ipc/ContentProcessParent.h | 18 ++++- dom/plugins/PluginProcessParent.cpp | 1 + .../src/chrome/common/child_process_host.h | 6 ++ ipc/glue/GeckoChildProcessHost.cpp | 15 +++- ipc/glue/GeckoChildProcessHost.h | 7 +- toolkit/xre/nsEmbedFunctions.cpp | 19 ++++- xpcom/build/nsXULAppAPI.h | 3 + 11 files changed, 156 insertions(+), 13 deletions(-) diff --git a/dom/ipc/ContentProcess.ipdl b/dom/ipc/ContentProcess.ipdl index 0f36f0271639..bdb48e29e8e2 100644 --- a/dom/ipc/ContentProcess.ipdl +++ b/dom/ipc/ContentProcess.ipdl @@ -21,6 +21,8 @@ child: TestShell(); ~TestShell(); + + Quit(); }; } diff --git a/dom/ipc/ContentProcessChild.cpp b/dom/ipc/ContentProcessChild.cpp index 6781c512d446..78a8acbbdfb9 100644 --- a/dom/ipc/ContentProcessChild.cpp +++ b/dom/ipc/ContentProcessChild.cpp @@ -8,6 +8,9 @@ #include "nsXULAppAPI.h" +#include "base/message_loop.h" +#include "base/task.h" + using namespace mozilla::ipc; namespace mozilla { @@ -16,6 +19,7 @@ namespace dom { ContentProcessChild* ContentProcessChild::sSingleton; ContentProcessChild::ContentProcessChild() + : mQuit(PR_FALSE) { } @@ -73,9 +77,29 @@ ContentProcessChild::TestShellDestructor(TestShellProtocolChild* shell) void ContentProcessChild::Quit() { + NS_ASSERTION(mQuit, "Exiting uncleanly!"); mIFrames.Clear(); mTestShells.Clear(); } +static void +QuitIOLoop() +{ + MessageLoop::current()->Quit(); +} + +nsresult +ContentProcessChild::RecvQuit() +{ + mQuit = PR_TRUE; + + Quit(); + + XRE_GetIOMessageLoop()->PostTask(FROM_HERE, + NewRunnableFunction(&QuitIOLoop)); + + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/ipc/ContentProcessChild.h b/dom/ipc/ContentProcessChild.h index 5daef3e0a9cc..a53c7b58d68f 100644 --- a/dom/ipc/ContentProcessChild.h +++ b/dom/ipc/ContentProcessChild.h @@ -33,6 +33,7 @@ public: virtual nsresult TestShellDestructor(TestShellProtocolChild*); void Quit(); + virtual nsresult RecvQuit(); private: static ContentProcessChild* sSingleton; @@ -40,6 +41,8 @@ private: nsTArray > mIFrames; nsTArray > mTestShells; + PRBool mQuit; + DISALLOW_EVIL_CONSTRUCTORS(ContentProcessChild); }; diff --git a/dom/ipc/ContentProcessParent.cpp b/dom/ipc/ContentProcessParent.cpp index b21597a86ec7..ee7e6afa8abe 100644 --- a/dom/ipc/ContentProcessParent.cpp +++ b/dom/ipc/ContentProcessParent.cpp @@ -5,7 +5,19 @@ #include "TabParent.h" #include "mozilla/ipc/TestShellParent.h" +#include "nsIObserverService.h" + +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + using namespace mozilla::ipc; +using mozilla::MonitorAutoEnter; + +namespace { +PRBool gSingletonDied = PR_FALSE; +} namespace mozilla { namespace dom { @@ -15,10 +27,20 @@ ContentProcessParent* ContentProcessParent::gSingleton; ContentProcessParent* ContentProcessParent::GetSingleton() { - if (!gSingleton) - gSingleton = new ContentProcessParent(); - - return gSingleton; + if (!gSingleton && !gSingletonDied) { + nsRefPtr parent = new ContentProcessParent(); + if (parent) { + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) { + if (NS_SUCCEEDED(obs->AddObserver(parent, "xpcom-shutdown", + PR_FALSE))) { + gSingleton = parent; + } + } + } + } + return gSingleton; } TabParent* @@ -34,15 +56,50 @@ ContentProcessParent::CreateTestShell() } ContentProcessParent::ContentProcessParent() - : mSubprocess(GeckoProcessType_Content) + : mMonitor("ContentProcessParent::mMonitor") { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); // TODO: async launching! - mSubprocess.SyncLaunch(); - Open(mSubprocess.GetChannel()); + mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, this); + mSubprocess->SyncLaunch(); + Open(mSubprocess->GetChannel()); } ContentProcessParent::~ContentProcessParent() { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(gSingleton == this, "More than one singleton?!"); + gSingletonDied = PR_TRUE; + gSingleton = nsnull; +} + +NS_IMPL_ISUPPORTS1(ContentProcessParent, nsIObserver) + +NS_IMETHODIMP +ContentProcessParent::Observe(nsISupports* aSubject, + const char* aTopic, + const PRUnichar* aData) +{ + if (!strcmp(aTopic, "xpcom-shutdown") && mSubprocess) { + SendQuit(); +#ifdef OS_WIN + MonitorAutoEnter mon(mMonitor); + while (mSubprocess) { + mon.Wait(); + } +#endif + } + return NS_OK; +} + +void +ContentProcessParent::OnWaitableEventSignaled(base::WaitableEvent *event) +{ + // The child process has died! Sadly we're on the wrong thread to do much. + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + MonitorAutoEnter mon(mMonitor); + mSubprocess = nsnull; + mon.Notify(); } IFrameEmbeddingProtocolParent* diff --git a/dom/ipc/ContentProcessParent.h b/dom/ipc/ContentProcessParent.h index 71aa59437b76..2a876afc1b90 100644 --- a/dom/ipc/ContentProcessParent.h +++ b/dom/ipc/ContentProcessParent.h @@ -4,9 +4,14 @@ #ifndef mozilla_dom_ContentProcessParent_h #define mozilla_dom_ContentProcessParent_h +#include "base/waitable_event_watcher.h" + #include "mozilla/dom/ContentProcessProtocolParent.h" #include "mozilla/ipc/GeckoChildProcessHost.h" +#include "nsIObserver.h" +#include "mozilla/Monitor.h" + namespace mozilla { namespace ipc { class TestShellParent; @@ -17,7 +22,9 @@ namespace dom { class TabParent; class ContentProcessParent - : private ContentProcessProtocolParent + : private ContentProcessProtocolParent, + public base::WaitableEventWatcher::Delegate, + public nsIObserver { private: typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost; @@ -30,6 +37,11 @@ public: static ContentProcessParent* FreeSingleton(); #endif + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + virtual void OnWaitableEventSignaled(base::WaitableEvent *event); + TabParent* CreateTab(const MagicWindowHandle& hwnd); mozilla::ipc::TestShellParent* CreateTestShell(); @@ -50,7 +62,9 @@ private: virtual TestShellProtocolParent* TestShellConstructor(); virtual nsresult TestShellDestructor(TestShellProtocolParent* shell); - GeckoChildProcessHost mSubprocess; + mozilla::Monitor mMonitor; + + GeckoChildProcessHost* mSubprocess; }; } // namespace dom diff --git a/dom/plugins/PluginProcessParent.cpp b/dom/plugins/PluginProcessParent.cpp index d2a80e2623de..bd6fb75817f1 100644 --- a/dom/plugins/PluginProcessParent.cpp +++ b/dom/plugins/PluginProcessParent.cpp @@ -51,6 +51,7 @@ PluginProcessParent::PluginProcessParent(const std::string& aPluginFilePath) : GeckoChildProcessHost(GeckoProcessType_Plugin), mPluginFilePath(aPluginFilePath) { + // XXXbent Need to catch crashing plugins by watching the process event! } PluginProcessParent::~PluginProcessParent() diff --git a/ipc/chromium/src/chrome/common/child_process_host.h b/ipc/chromium/src/chrome/common/child_process_host.h index 810e1d9a4b0f..8baad836112f 100644 --- a/ipc/chromium/src/chrome/common/child_process_host.h +++ b/ipc/chromium/src/chrome/common/child_process_host.h @@ -97,8 +97,14 @@ class ChildProcessHost : // Sends the given notification to the notification service on the UI thread. void Notify(NotificationType type); +#ifdef CHROMIUM_MOZILLA_BUILD + protected: +#endif // WaitableEventWatcher::Delegate implementation: virtual void OnWaitableEventSignaled(base::WaitableEvent *event); +#ifdef CHROMIUM_MOZILLA_BUILD + private: +#endif // By using an internal class as the IPC::Channel::Listener, we can intercept // OnMessageReceived/OnChannelConnected and do our own processing before diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 1b84f3c0ad47..f539d8565cd4 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -53,11 +53,13 @@ struct RunnableMethodTraits static void ReleaseCallee(GeckoChildProcessHost* obj) { } }; -GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType) +GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType, + base::WaitableEventWatcher::Delegate* aDelegate) : ChildProcessHost(RENDER_PROCESS), // FIXME/cjones: we should own this enum mProcessType(aProcessType), mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"), - mLaunched(false) + mLaunched(false), + mDelegate(aDelegate) { } @@ -155,3 +157,12 @@ GeckoChildProcessHost::OnChannelError() { // XXXbent Notify that the child process is gone? } + +void +GeckoChildProcessHost::OnWaitableEventSignaled(base::WaitableEvent *event) +{ + if (mDelegate) { + mDelegate->OnWaitableEventSignaled(event); + } + ChildProcessHost::OnWaitableEventSignaled(event); +} diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h index 420d64814d3e..8f0f271948e2 100644 --- a/ipc/glue/GeckoChildProcessHost.h +++ b/ipc/glue/GeckoChildProcessHost.h @@ -56,7 +56,8 @@ protected: typedef mozilla::Monitor Monitor; public: - GeckoChildProcessHost(GeckoProcessType aProcessType=GeckoProcessType_Default); + GeckoChildProcessHost(GeckoProcessType aProcessType=GeckoProcessType_Default, + base::WaitableEventWatcher::Delegate* aDelegate=nsnull); bool SyncLaunch(std::vector aExtraOpts=std::vector()); bool AsyncLaunch(std::vector aExtraOpts=std::vector()); @@ -67,6 +68,8 @@ public: virtual bool CanShutdown() { return true; } + virtual void OnWaitableEventSignaled(base::WaitableEvent *event); + IPC::Channel* GetChannel() { return channelp(); } @@ -85,6 +88,8 @@ protected: base::file_handle_mapping_vector mFileMap; #endif + base::WaitableEventWatcher::Delegate* mDelegate; + private: DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost); }; diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index 5ef88a72f4a8..c5be44d31cf9 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -85,6 +85,7 @@ using mozilla::ipc::GeckoChildProcessHost; using mozilla::ipc::GeckoThread; +using mozilla::ipc::BrowserProcessSubThread; using mozilla::ipc::ScopedXREEmbed; using mozilla::plugins::PluginThreadChild; @@ -238,6 +239,8 @@ XRE_StringToChildProcessType(const char* aProcessTypeString) #ifdef MOZ_IPC static GeckoProcessType sChildProcessType = GeckoProcessType_Default; +static MessageLoop* sIOMessageLoop; + nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], @@ -288,7 +291,11 @@ XRE_InitChildProcess(int aArgc, ChildProcess process(mainThread); // Do IPC event loop - MessageLoop::current()->Run(); + sIOMessageLoop = MessageLoop::current(); + + sIOMessageLoop->Run(); + + sIOMessageLoop = nsnull; } return NS_OK; @@ -300,6 +307,16 @@ XRE_GetProcessType() return sChildProcessType; } +MessageLoop* +XRE_GetIOMessageLoop() +{ + if (sChildProcessType == GeckoProcessType_Default) { + NS_ASSERTION(!sIOMessageLoop, "Shouldn't be set on parent process!"); + return BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO); + } + return sIOMessageLoop; +} + namespace { class MainFunctionRunnable : public nsRunnable diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h index 63a3d4e3c2e6..7b7d64ca2583 100644 --- a/xpcom/build/nsXULAppAPI.h +++ b/xpcom/build/nsXULAppAPI.h @@ -477,6 +477,9 @@ class MessageLoop; XRE_API(void, XRE_ShutdownChildProcess, (MessageLoop* aUILoop)) +XRE_API(MessageLoop*, + XRE_GetIOMessageLoop, ()) + struct JSContext; struct JSString;