bug 529005: detect child process shutdowns vs. crashes, and expose this information to IPDL actors in a new |ActorDestroy(why)| interface. also ensure that subprotocol actors are notified of shutdown and cleaned up properly. r=bsmedberg r=bent
authorChris Jones <jones.chris.g@gmail.com>
Thu, 03 Dec 2009 02:16:28 -0600
changeset 36140 7ebfbec7521c2e98786bad0d0f00720f2bb2d3e1
parent 36139 b8b7a32cf66d061d17d390b67f6968da59e95564
child 36141 2829e0190d535f8c43ab9739e5228fe8197896c3
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg, bent
bugs529005
milestone1.9.3a1pre
bug 529005: detect child process shutdowns vs. crashes, and expose this information to IPDL actors in a new |ActorDestroy(why)| interface. also ensure that subprotocol actors are notified of shutdown and cleaned up properly. r=bsmedberg r=bent
dom/plugins/PluginModuleChild.cpp
dom/plugins/PluginModuleChild.h
dom/plugins/PluginModuleParent.cpp
ipc/glue/AsyncChannel.cpp
ipc/glue/AsyncChannel.h
ipc/glue/ProtocolUtils.h
ipc/glue/RPCChannel.cpp
ipc/glue/RPCChannel.h
ipc/glue/SyncChannel.cpp
ipc/glue/SyncChannel.h
ipc/ipdl/ipdl.py
ipc/ipdl/ipdl/lower.py
ipc/ipdl/ipdl/type.py
ipc/ipdl/test/cxx/IPDLUnitTestTypes.h
ipc/ipdl/test/cxx/IPDLUnitTests.h
ipc/ipdl/test/cxx/Makefile.in
ipc/ipdl/test/cxx/PTestShutdown.ipdl
ipc/ipdl/test/cxx/PTestShutdownSub.ipdl
ipc/ipdl/test/cxx/PTestShutdownSubsub.ipdl
ipc/ipdl/test/cxx/TestArrays.cpp
ipc/ipdl/test/cxx/TestArrays.h
ipc/ipdl/test/cxx/TestDesc.cpp
ipc/ipdl/test/cxx/TestDesc.h
ipc/ipdl/test/cxx/TestLatency.cpp
ipc/ipdl/test/cxx/TestLatency.h
ipc/ipdl/test/cxx/TestManyChildAllocs.cpp
ipc/ipdl/test/cxx/TestManyChildAllocs.h
ipc/ipdl/test/cxx/TestSanity.cpp
ipc/ipdl/test/cxx/TestSanity.h
ipc/ipdl/test/cxx/TestShutdown.cpp
ipc/ipdl/test/cxx/TestShutdown.h
ipc/ipdl/test/cxx/ipdl.mk
ipc/ipdl/test/ipdl/ok/managerProtocol.ipdl
toolkit/library/dlldeps-xul.cpp
toolkit/xre/nsEmbedFunctions.cpp
xpcom/build/nsXULAppAPI.h
--- a/dom/plugins/PluginModuleChild.cpp
+++ b/dom/plugins/PluginModuleChild.cpp
@@ -44,16 +44,17 @@
 #endif
 
 #include "nsILocalFile.h"
 
 #include "pratom.h"
 #include "nsDebug.h"
 #include "nsCOMPtr.h"
 #include "nsPluginsDir.h"
+#include "nsXULAppAPI.h"
 
 #include "mozilla/plugins/PluginInstanceChild.h"
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/BrowserStreamChild.h"
 #include "mozilla/plugins/PluginStreamChild.h"
 
 #include "nsNPAPIPlugin.h"
 
@@ -189,23 +190,36 @@ PluginModuleChild::InitGraphics()
     return true;
 }
 
 bool
 PluginModuleChild::AnswerNP_Shutdown(NPError *rv)
 {
     AssertPluginThread();
 
-    // FIXME/cjones: should all instances be dead by now?
+    // the PluginModuleParent shuts down this process after this RPC
+    // call pops off its stack
 
     *rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR;
+
+    // weakly guard against re-entry after NP_Shutdown
+    memset(&mFunctions, 0, sizeof(mFunctions));
+
     return true;
 }
 
 void
+PluginModuleChild::ActorDestroy(ActorDestroyReason why)
+{
+    // doesn't matter why we're being destroyed; it's up to us to
+    // initiate (clean) shutdown
+    XRE_ShutdownChildProcess();
+}
+
+void
 PluginModuleChild::CleanUp()
 {
 }
 
 const char*
 PluginModuleChild::GetUserAgent()
 {
     if (!CallNPN_UserAgent(&mUserAgent))
--- a/dom/plugins/PluginModuleChild.h
+++ b/dom/plugins/PluginModuleChild.h
@@ -116,16 +116,19 @@ protected:
                                      const nsCString& aMimeType,
                                      const uint16_t& aMode,
                                      const nsTArray<nsCString>& aNames,
                                      const nsTArray<nsCString>& aValues,
                                      NPError* rv);
     virtual bool
     AnswerNP_Shutdown(NPError *rv);
 
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+
 public:
     PluginModuleChild();
     virtual ~PluginModuleChild();
 
     bool Init(const std::string& aPluginFilename,
               base::ProcessHandle aParentProcessHandle,
               MessageLoop* aIOLoop,
               IPC::Channel* aChannel);
--- a/dom/plugins/PluginModuleParent.cpp
+++ b/dom/plugins/PluginModuleParent.cpp
@@ -468,19 +468,22 @@ PluginModuleParent::NP_Initialize(NPNets
 }
 #endif
 
 nsresult
 PluginModuleParent::NP_Shutdown(NPError* error)
 {
     _MOZ_LOG(__FUNCTION__);
 
-    // FIXME/cjones: should all sub-actors be dead by now?
+    bool ok = CallNP_Shutdown(error);
 
-    bool ok = CallNP_Shutdown(error);
+    // if NP_Shutdown() is nested within another RPC call, this will
+    // break things.  but lord help us if we're doing that anyway; the
+    // plugin dso will have been unloaded on the other side by the
+    // CallNP_Shutdown() message
     Close();
 
     return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 PluginModuleParent::NP_GetMIMEDescription(char** mimeDesc)
 {
--- a/ipc/glue/AsyncChannel.cpp
+++ b/ipc/glue/AsyncChannel.cpp
@@ -34,16 +34,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "mozilla/ipc/AsyncChannel.h"
 #include "mozilla/ipc/GeckoThread.h"
+#include "mozilla/ipc/ProtocolUtils.h"
 
 #include "nsDebug.h"
 #include "nsTraceRefcnt.h"
 #include "nsXULAppAPI.h"
 
 using mozilla::MutexAutoLock;
 
 template<>
@@ -66,21 +67,17 @@ AsyncChannel::AsyncChannel(AsyncListener
     mWorkerLoop()
 {
     MOZ_COUNT_CTOR(AsyncChannel);
 }
 
 AsyncChannel::~AsyncChannel()
 {
     MOZ_COUNT_DTOR(AsyncChannel);
-    if (!mChild && mTransport)
-        Close();
-    // we only hold a weak ref to the transport, which is "owned"
-    // by GeckoChildProcess/GeckoThread
-    mTransport = 0;
+    Clear();
 }
 
 bool
 AsyncChannel::Open(Transport* aTransport, MessageLoop* aIOLoop)
 {
     NS_PRECONDITION(!mTransport, "Open() called > once");
     NS_PRECONDITION(aTransport, "need transport layer");
 
@@ -124,29 +121,44 @@ AsyncChannel::Open(Transport* aTransport
     }
 
     return true;
 }
 
 void
 AsyncChannel::Close()
 {
-    MutexAutoLock lock(mMutex);
+    {
+        MutexAutoLock lock(mMutex);
 
-    if (!mChild && ChannelConnected == mChannelState) {
+        if (ChannelConnected != mChannelState)
+            // XXX be strict about this until there's a compelling reason
+            // to relax
+            NS_RUNTIMEABORT("Close() called on closed channel!");
+
         AssertWorkerThread();
 
-        mIOLoop->PostTask(
-            FROM_HERE, NewRunnableMethod(this, &AsyncChannel::OnClose));
+        // notify the other side that we're about to close our socket
+        SendGoodbye();
+
+        mChannelState = ChannelClosing;
 
-        while (ChannelConnected == mChannelState)
+        // and post the task will do the actual close
+        mIOLoop->PostTask(
+            FROM_HERE, NewRunnableMethod(this, &AsyncChannel::OnCloseChannel));
+
+        while (ChannelClosing == mChannelState)
             mCvar.Wait();
+
+        // TODO sort out Close() on this side racing with Close() on the
+        // other side
+        mChannelState = ChannelClosed;
     }
 
-    mTransport = NULL;
+    return NotifyChannelClosed();
 }
 
 bool
 AsyncChannel::Send(Message* msg)
 {
     AssertWorkerThread();
     mMutex.AssertNotCurrentThreadOwns();
     NS_ABORT_IF_FALSE(MSG_ROUTING_NONE != msg->routing_id(), "need a route");
@@ -168,19 +180,128 @@ AsyncChannel::Send(Message* msg)
 
 void
 AsyncChannel::OnDispatchMessage(const Message& msg)
 {
     AssertWorkerThread();
     NS_ASSERTION(!msg.is_reply(), "can't process replies here");
     NS_ASSERTION(!(msg.is_sync() || msg.is_rpc()), "async dispatch only");
 
+    if (MaybeInterceptGoodbye(msg))
+        // there's a NotifyMaybeChannelError event waiting for us, or
+        // will be soon
+        return;
+
+    // it's OK to dispatch messages if the channel is closed/error'd,
+    // since we don't have a reply to send back
+
     (void)MaybeHandleError(mListener->OnMessageReceived(msg), "AsyncChannel");
 }
 
+// This is an async message
+class GoodbyeMessage : public IPC::Message
+{
+public:
+    enum { ID = GOODBYE_MESSAGE_TYPE };
+    GoodbyeMessage() :
+        IPC::Message(MSG_ROUTING_NONE, ID, PRIORITY_NORMAL)
+    {
+    }
+    // XXX not much point in implementing this; maybe could help with
+    // debugging?
+    static bool Read(const Message* msg)
+    {
+        return true;
+    }
+    void Log(const std::string& aPrefix,
+             FILE* aOutf) const
+    {
+        fputs("(special `Goodbye' message)", aOutf);
+    }
+};
+
+void
+AsyncChannel::SendGoodbye()
+{
+    AssertWorkerThread();
+
+    mIOLoop->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this, &AsyncChannel::OnSend, new GoodbyeMessage()));
+}
+
+bool
+AsyncChannel::MaybeInterceptGoodbye(const Message& msg)
+{
+    // IPDL code isn't allowed to send MSG_ROUTING_NONE messages, so
+    // there's no chance of confusion here
+    if (MSG_ROUTING_NONE != msg.routing_id())
+        return false;
+
+    if (msg.is_sync() || msg.is_rpc() || GOODBYE_MESSAGE_TYPE != msg.type())
+        NS_RUNTIMEABORT("received unknown MSG_ROUTING_NONE message when expecting `Goodbye'");
+
+    MutexAutoLock lock(mMutex);
+    // TODO sort out Close() on this side racing with Close() on the
+    // other side
+    mChannelState = ChannelClosing;
+
+    printf("NOTE: %s process received `Goodbye', closing down\n",
+           mChild ? "child" : "parent");
+
+    return true;
+}
+
+void
+AsyncChannel::NotifyChannelClosed()
+{
+    if (ChannelClosed != mChannelState)
+        NS_RUNTIMEABORT("channel should have been closed!");
+
+    // OK, the IO thread just closed the channel normally.  Let the
+    // listener know about it.
+    mListener->OnChannelClose();
+    Clear();
+}
+
+void
+AsyncChannel::NotifyMaybeChannelError()
+{
+    // TODO sort out Close() on this side racing with Close() on the
+    // other side
+    if (ChannelClosing == mChannelState) {
+        // the channel closed, but we received a "Goodbye" message
+        // warning us about it. no worries
+        mChannelState = ChannelClosed;
+        return NotifyChannelClosed();
+    }
+
+    // Oops, error!  Let the listener know about it.
+    mChannelState = ChannelError;
+    mListener->OnChannelError();
+
+    Clear();
+}
+
+void
+AsyncChannel::Clear()
+{
+    mListener = 0;
+    mIOLoop = 0;
+    mWorkerLoop = 0;
+
+    if (mTransport) {
+        mTransport->set_listener(0);
+
+        // we only hold a weak ref to the transport, which is "owned"
+        // by GeckoChildProcess/GeckoThread
+        mTransport = 0;
+    }
+}
+
 bool
 AsyncChannel::MaybeHandleError(Result code, const char* channelName)
 {
     if (MsgProcessed == code)
         return true;
 
     const char* errorMsg;
     switch (code) {
@@ -196,17 +317,17 @@ AsyncChannel::MaybeHandleError(Result co
     case MsgRouteError:
         errorMsg = "Route error: message sent to unknown actor ID";
         break;
     case MsgValueError:
         errorMsg = "Value error: message was deserialized, but contained an illegal value";
         break;
 
     default:
-        NOTREACHED();
+        NS_RUNTIMEABORT("unknown Result code");
         return false;
     }
 
     PrintErrorMessage(channelName, errorMsg);
     return false;
 }
 
 void
@@ -227,100 +348,81 @@ AsyncChannel::ReportConnectionError(cons
     default:
         NOTREACHED();
     }
 
     PrintErrorMessage(channelName, errorMsg);
 }
 
 //
-// The methods below run in the context of the IO thread, and can proxy
-// back to the methods above
+// The methods below run in the context of the IO thread
 //
 
 void
 AsyncChannel::OnMessageReceived(const Message& msg)
 {
     AssertIOThread();
     NS_ASSERTION(mChannelState != ChannelError, "Shouldn't get here!");
 
     // wake up the worker, there's work to do
     mWorkerLoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(this, &AsyncChannel::OnDispatchMessage, msg));
 }
 
 void
+AsyncChannel::OnChannelOpened()
+{
+    AssertIOThread();
+    mChannelState = ChannelOpening;
+    /*assert*/mTransport->Connect();
+}
+
+void
 AsyncChannel::OnChannelConnected(int32 peer_pid)
 {
     AssertIOThread();
 
     MutexAutoLock lock(mMutex);
     mChannelState = ChannelConnected;
     mCvar.Notify();
 }
 
 void
 AsyncChannel::OnChannelError()
 {
     AssertIOThread();
 
-    {
-        MutexAutoLock lock(mMutex);
-        mChannelState = ChannelError;
-    }
-
-    if (XRE_GetProcessType() == GeckoProcessType_Default) {
-        // Parent process, one of our children died. Notify?
-    }
-    else {
-        // Child process, initiate quit sequence.
-#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
-        // XXXbent this is totally out of place, but works for now.
-        XRE_ShutdownChildProcess(mWorkerLoop);
+    MutexAutoLock lock(mMutex);
 
-        // Must exit the IO loop, which will then join with the UI loop.
-        MessageLoop::current()->Quit();
-#else
-        // FIXME need to devote some thought to the most
-        // effective/least easily overrideable, yet quiet, way to
-        // exit.  abort() is a little loud
-        _exit(0);
-#endif
-    }
-}
+    // NB: this can race with the `Goodbye' event being processed by
+    // the worker thread
+    if (ChannelClosing != mChannelState)
+        mChannelState = ChannelError;
 
-void
-AsyncChannel::OnChannelOpened()
-{
-    AssertIOThread();
-    mChannelState = ChannelOpening;
-    /*assert*/mTransport->Connect();
+    mWorkerLoop->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this, &AsyncChannel::NotifyMaybeChannelError));
 }
 
 void
 AsyncChannel::OnSend(Message* aMsg)
 {
     AssertIOThread();
     mTransport->Send(aMsg);
-    // mTransport deletes aMsg
+    // mTransport assumes ownership of aMsg
 }
 
 void
-AsyncChannel::OnClose()
+AsyncChannel::OnCloseChannel()
 {
     AssertIOThread();
 
     mTransport->Close();
 
-    // don't lose error-state information
-    if (ChannelError != mChannelState)
-        mChannelState = ChannelClosed;
-
-    if (!mChild) {
-        MutexAutoLock lock(mMutex);
-        mCvar.Notify();
-    }
+    MutexAutoLock lock(mMutex);
+    mChannelState = ChannelClosed;
+    mCvar.Notify();
 }
 
 
 } // namespace ipc
 } // namespace mozilla
--- a/ipc/glue/AsyncChannel.h
+++ b/ipc/glue/AsyncChannel.h
@@ -70,47 +70,58 @@ class AsyncChannel : public IPC::Channel
 protected:
     typedef mozilla::CondVar CondVar;
     typedef mozilla::Mutex Mutex;
 
     enum ChannelState {
         ChannelClosed,
         ChannelOpening,
         ChannelConnected,
+        ChannelClosing,
         ChannelError
     };
 
 public:
     typedef IPC::Channel Transport;
     typedef IPC::Message Message;
 
     class /*NS_INTERFACE_CLASS*/ AsyncListener: protected HasResultCodes
     {
     public:
         virtual ~AsyncListener() { }
+
+        virtual void OnChannelClose() = 0;
+        virtual void OnChannelError() = 0;
         virtual Result OnMessageReceived(const Message& aMessage) = 0;
     };
 
 public:
+    //
+    // These methods are called on the "worker" thread
+    //
     AsyncChannel(AsyncListener* aListener);
     virtual ~AsyncChannel();
 
     // Open  from the perspective of the transport layer; the underlying
     // socketpair/pipe should already be created.
     //
     // Returns true iff the transport layer was successfully connected,
     // i.e., mChannelState == ChannelConnected.
     bool Open(Transport* aTransport, MessageLoop* aIOLoop=0);
     
     // Close the underlying transport channel.
     void Close();
 
     // Asynchronously send a message to the other side of the channel
     bool Send(Message* msg);
 
+    //
+    // These methods are called on the "IO" thread
+    //
+
     // Implement the IPC::Channel::Listener interface
     NS_OVERRIDE virtual void OnMessageReceived(const Message& msg);
     NS_OVERRIDE virtual void OnChannelConnected(int32 peer_pid);
     NS_OVERRIDE virtual void OnChannelError();
 
 protected:
     // Can be run on either thread
     void AssertWorkerThread()
@@ -136,20 +147,31 @@ protected:
     void ReportConnectionError(const char* channelName);
 
     void PrintErrorMessage(const char* channelName, const char* msg)
     {
         fprintf(stderr, "\n###!!! [%s][%s] Error: %s\n\n",
                 mChild ? "Child" : "Parent", channelName, msg);
     }
 
+    // Run on the worker thread
+
+    void SendGoodbye();
+    bool MaybeInterceptGoodbye(const Message& msg);
+
+    void NotifyChannelClosed();
+    void NotifyMaybeChannelError();
+
+    void Clear();
+
     // Run on the IO thread
+
     void OnChannelOpened();
     void OnSend(Message* aMsg);
-    void OnClose();
+    void OnCloseChannel();
 
     Transport* mTransport;
     AsyncListener* mListener;
     ChannelState mChannelState;
     Mutex mMutex;
     CondVar mCvar;
     MessageLoop* mIOLoop;       // thread where IO happens
     MessageLoop* mWorkerLoop;   // thread where work is done
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -41,17 +41,24 @@
 #define mozilla_ipc_ProtocolUtils_h 1
 
 #include "base/process.h"
 #include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 
 #include "prenv.h"
 
-#include "mozilla/ipc/RPCChannel.h"
+
+// WARNING: this takes into account the private, special-message-type
+// enum in ipc_channel.h.  They need to be kept in sync.
+namespace {
+enum {
+    GOODBYE_MESSAGE_TYPE       = kuint16max - 1,
+};
+}
 
 namespace mozilla {
 namespace ipc {
 
 
 // Used to pass references to protocol actors across the wire.
 // Actors created on the parent-side have a positive ID, and actors
 // allocated on the child side have a negative ID.
@@ -59,16 +66,23 @@ struct ActorHandle
 {
     int mId;
 };
 
 template<class ListenerT>
 class /*NS_INTERFACE_CLASS*/ IProtocolManager
 {
 public:
+    enum ActorDestroyReason {
+        Deletion,
+        AncestorDeletion,
+        NormalShutdown,
+        AbnormalShutdown
+    };
+
     typedef base::ProcessHandle ProcessHandle;
 
     virtual int32 Register(ListenerT*) = 0;
     virtual int32 RegisterID(ListenerT*, int32) = 0;
     virtual ListenerT* Lookup(int32) = 0;
     virtual void Unregister(int32) = 0;
     // XXX odd duck, acknowledged
     virtual ProcessHandle OtherProcess() const = 0;
--- a/ipc/glue/RPCChannel.cpp
+++ b/ipc/glue/RPCChannel.cpp
@@ -79,16 +79,17 @@ RPCChannel::~RPCChannel()
     MOZ_COUNT_DTOR(RPCChannel);
     // FIXME/cjones: impl
 }
 
 bool
 RPCChannel::Call(Message* msg, Message* reply)
 {
     AssertWorkerThread();
+    mMutex.AssertNotCurrentThreadOwns();
     RPC_ASSERT(!ProcessingSyncMessage(),
                "violation of sync handler invariant");
     RPC_ASSERT(msg->is_rpc(), "can only Call() RPC messages here");
 
     MutexAutoLock lock(mMutex);
 
     if (!Connected()) {
         ReportConnectionError("RPCChannel");
@@ -412,27 +413,22 @@ RPCChannel::OnMessageReceived(const Mess
         NotifyWorkerThread();
 }
 
 
 void
 RPCChannel::OnChannelError()
 {
     AssertIOThread();
-    {
-        MutexAutoLock lock(mMutex);
 
-        mChannelState = ChannelError;
-
-        if (AwaitingSyncReply()
-            || 0 < StackDepth())
-            NotifyWorkerThread();
-    }
+    AsyncChannel::OnChannelError();
 
     // skip SyncChannel::OnError(); we subsume its duties
-
-    return AsyncChannel::OnChannelError();
+    MutexAutoLock lock(mMutex);
+    if (AwaitingSyncReply()
+        || 0 < StackDepth())
+        NotifyWorkerThread();
 }
 
 
 } // namespace ipc
 } // namespace mozilla
 
--- a/ipc/glue/RPCChannel.h
+++ b/ipc/glue/RPCChannel.h
@@ -52,16 +52,19 @@ namespace ipc {
 class RPCChannel : public SyncChannel
 {
 public:
     class /*NS_INTERFACE_CLASS*/ RPCListener :
         public SyncChannel::SyncListener
     {
     public:
         virtual ~RPCListener() { }
+
+        virtual void OnChannelClose() = 0;
+        virtual void OnChannelError() = 0;
         virtual Result OnMessageReceived(const Message& aMessage) = 0;
         virtual Result OnMessageReceived(const Message& aMessage,
                                          Message*& aReply) = 0;
         virtual Result OnCallReceived(const Message& aMessage,
                                       Message*& aReply) = 0;
     };
 
     // What happens if RPC calls race?
--- a/ipc/glue/SyncChannel.cpp
+++ b/ipc/glue/SyncChannel.cpp
@@ -71,16 +71,17 @@ SyncChannel::~SyncChannel()
 
 // static
 bool SyncChannel::sIsPumpingMessages = false;
 
 bool
 SyncChannel::Send(Message* msg, Message* reply)
 {
     AssertWorkerThread();
+    mMutex.AssertNotCurrentThreadOwns();
     NS_ABORT_IF_FALSE(!ProcessingSyncMessage(),
                       "violation of sync handler invariant");
     NS_ABORT_IF_FALSE(msg->is_sync(), "can only Send() sync messages here");
 
     MutexAutoLock lock(mMutex);
 
     if (!Connected()) {
         ReportConnectionError("SyncChannel");
@@ -172,27 +173,22 @@ SyncChannel::OnMessageReceived(const Mes
         NotifyWorkerThread();
     }
 }
 
 void
 SyncChannel::OnChannelError()
 {
     AssertIOThread();
-    {
-        MutexAutoLock lock(mMutex);
 
-        mChannelState = ChannelError;
+    AsyncChannel::OnChannelError();
 
-        if (AwaitingSyncReply()) {
-            NotifyWorkerThread();
-        }
-    }
-
-    return AsyncChannel::OnChannelError();
+    MutexAutoLock lock(mMutex);
+    if (AwaitingSyncReply())
+        NotifyWorkerThread();
 }
 
 //
 // Synchronization between worker and IO threads
 //
 
 // Windows versions of the following two functions live in
 // WindowsMessageLoop.cpp.
--- a/ipc/glue/SyncChannel.h
+++ b/ipc/glue/SyncChannel.h
@@ -52,16 +52,19 @@ protected:
     typedef uint16 MessageId;
 
 public:
     class /*NS_INTERFACE_CLASS*/ SyncListener : 
         public AsyncChannel::AsyncListener
     {
     public:
         virtual ~SyncListener() { }
+
+        virtual void OnChannelClose() = 0;
+        virtual void OnChannelError() = 0;
         virtual Result OnMessageReceived(const Message& aMessage) = 0;
         virtual Result OnMessageReceived(const Message& aMessage,
                                          Message*& aReply) = 0;
     };
 
     SyncChannel(SyncListener* aListener);
     virtual ~SyncChannel();
 
--- a/ipc/ipdl/ipdl.py
+++ b/ipc/ipdl/ipdl.py
@@ -45,38 +45,38 @@ op = optparse.OptionParser(usage='ipdl.p
 op.add_option('-I', '--include', dest='includedirs', default=[ ],
               action='append',
               help='Additional directory to search for included protocol specifications')
 op.add_option('-v', '--verbose', dest='verbosity', default=1, action='count',
               help='Verbose logging (specify -vv or -vvv for very verbose logging)')
 op.add_option('-q', '--quiet', dest='verbosity', action='store_const', const=0,
               help="Suppress logging output")
 op.add_option('-d', '--outheaders-dir', dest='headersdir', default='.',
-              help="""Director into which C++ headers will be generated.
+              help="""Directory into which C++ headers will be generated.
 A protocol Foo in the namespace bar will cause the headers
   dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h
 to be generated""")
 op.add_option('-o', '--outcpp-dir', dest='cppdir', default='.',
-              help="""Director into which C++ sources will be generated
+              help="""Directory into which C++ sources will be generated
 A protocol Foo in the namespace bar will cause the sources
   cppdir/FooParent.cpp, cppdir/FooChild.cpp
 to be generated""")
 
 
 options, files = op.parse_args()
 _verbosity = options.verbosity
 headersdir = options.headersdir
 cppdir = options.cppdir
 includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ]
 
 if not len(files):
     op.error("No IPDL files specified")
 
 log(1, 'Generated C++ headers will be generated relative to "%s"', headersdir)
-log(1, 'Generated C++ sources will be generated relative to "%s"', cppdir)
+log(1, 'Generated C++ sources will be generated in "%s"', cppdir)
 
 allprotocols = []
 
 for f in files:
     log(1, 'Parsing specification %s', f)
     if f == '-':
         fd = sys.stdin
         filename = '<stdin>'
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -334,16 +334,19 @@ def _callCxxArraySetLength(arr, lenexpr)
 def _callCxxArrayInsertSorted(arr, elt):
     return ExprCall(ExprSelect(arr, '.', 'InsertElementSorted'),
                     args=[ elt ])
 
 def _callCxxArrayRemoveSorted(arr, elt):
     return ExprCall(ExprSelect(arr, '.', 'RemoveElementSorted'),
                     args=[ elt ])
 
+def _callCxxArrayClear(arr):
+    return ExprCall(ExprSelect(arr, '.', 'Clear'))
+
 def _otherSide(side):
     if side == 'child':  return 'parent'
     if side == 'parent':  return 'child'
     assert 0
 
 def _ifLogging(stmts):
     iflogging = StmtIf(ExprCall(ExprVar('mozilla::ipc::LoggingEnabled')))
     iflogging.addifstmts(stmts)
@@ -405,16 +408,28 @@ def errfnSendDtor(msg):
 # used in |OnMessage*()| handlers that hand in-messages off to Recv*()
 # interface methods
 def errfnRecv(msg, errcode=_Result.ValuError):
     return [
         _fatalError(msg),
         StmtReturn(errcode)
     ]
 
+def _destroyMethod():
+    return ExprVar('ActorDestroy')
+
+class _DestroyReason:
+    @staticmethod
+    def Type():  return Type('ActorDestroyReason')
+
+    Deletion = ExprVar('Deletion')
+    AncestorDeletion = ExprVar('AncestorDeletion')
+    NormalShutdown = ExprVar('NormalShutdown')
+    AbnormalShutdown = ExprVar('AbnormalShutdown')
+
 ##-----------------------------------------------------------------------------
 ## Intermediate representation (IR) nodes used during lowering
 
 class _ConvertToCxxType(TypeVisitor):
     def __init__(self, side):  self.side = side
     
     def visitBuiltinCxxType(self, t):
         return Type(t.name())
@@ -1364,16 +1379,21 @@ class Protocol(ipdl.ast.Protocol):
     def managedMethod(self, actortype, side):
         assert self.decl.type.isManagerOf(actortype)
         return ExprVar('Managed'+  _actorName(actortype.name(), side))
 
     def managedVar(self, actortype, side):
         assert self.decl.type.isManagerOf(actortype)
         return ExprVar('mManaged'+ _actorName(actortype.name(), side))
 
+    def managedVarType(self, actortype, side, const=0, ref=0):
+        assert self.decl.type.isManagerOf(actortype)
+        return _cxxArrayType(self.managedCxxType(actortype, side),
+                             const=const, ref=ref)
+
     def managerArrayExpr(self, thisvar, side):
         """The member var my manager keeps of actors of my type."""
         assert self.decl.type.isManaged()
         return ExprSelect(
             ExprCall(self.managerMethod(thisvar)),
             '->', 'mManaged'+ _actorName(self.decl.type.name(), side))
 
     # shmem stuff
@@ -2476,20 +2496,21 @@ class _GenerateProtocolActorCode(ipdl.as
         # FIXME: all actors impl Iface for now
         if p.decl.type.isManager() or 1:
             self.hdrfile.addthing(CppDirective('include', '"base/id_map.h"'))
 
         self.hdrfile.addthings([
             CppDirective('include', '"'+ p.channelHeaderFile() +'"'),
             Whitespace.NL ])
 
-        inherits = [ Inherit(Type(p.fqListenerName())) ]
-        if p.decl.type.isManager():
-            inherits.append(Inherit(p.managerInterfaceType()))
-        self.cls = Class(self.clsname, inherits=inherits, abstract=True)
+        self.cls = Class(
+            self.clsname,
+            inherits=[ Inherit(Type(p.fqListenerName()), viz='protected'),
+                       Inherit(p.managerInterfaceType(), viz='protected') ],
+            abstract=True)
 
         friends = _FindFriends().findFriends(p.decl.type)
         if p.decl.type.isManaged():
             friends.add(p.decl.type.manager)
 
         # |friend| managed actors so that they can call our Dealloc*()
         friends.update(p.decl.type.manages)
 
@@ -2547,16 +2568,24 @@ class _GenerateProtocolActorCode(ipdl.as
                 virtual=1, pure=1)))
 
             self.cls.addstmt(StmtDecl(MethodDecl(
                 _deallocMethod(managed).name,
                 params=[ Decl(actortype, 'actor') ],
                 ret=Type.BOOL,
                 virtual=1, pure=1)))
 
+        # optional Shutdown() method; default is no-op
+        self.cls.addstmts([
+            Whitespace.NL,
+            MethodDefn(MethodDecl(
+                _destroyMethod().name,
+                params=[ Decl(_DestroyReason.Type(), 'why') ],
+                virtual=1))
+        ])
 
         self.cls.addstmt(Whitespace.NL)
 
         self.cls.addstmts((
             [ Label.PRIVATE ]
             + self.standardTypedefs()
             + [ Whitespace.NL ]
         ))
@@ -2628,19 +2657,18 @@ class _GenerateProtocolActorCode(ipdl.as
 
             self.cls.addstmts([ managermeth, Whitespace.NL ])
 
         ## managed[T]()
         for managed in p.decl.type.manages:
             arrvar = ExprVar('aArr')
             meth = MethodDefn(MethodDecl(
                 p.managedMethod(managed, self.side).name,
-                params=[ Decl(
-                    _cxxArrayType(p.managedCxxType(managed, self.side), ref=1),
-                    arrvar.name) ],
+                params=[ Decl(p.managedVarType(managed, self.side, ref=1),
+                              arrvar.name) ],
                 const=1))
             meth.addstmt(StmtExpr(ExprAssn(
                 arrvar, p.managedVar(managed, self.side))))
             self.cls.addstmts([ meth, Whitespace.NL ])
 
         ## OnMessageReceived()/OnCallReceived()
 
         # save these away for use in message handler case stmts
@@ -2732,16 +2760,33 @@ class _GenerateProtocolActorCode(ipdl.as
             ])
             if p.decl.type.toplevel().talksRpc():
                 self.cls.addstmts([
                     makeHandlerMethod('OnCallReceived', self.rpcSwitch,
                                       hasReply=1, dispatches=dispatches),
                     Whitespace.NL
                 ])
 
+        destroysubtreevar = ExprVar('DestroySubtree')
+        deallocsubtreevar = ExprVar('DeallocSubtree')
+
+        # OnChannelClose()
+        onclose = MethodDefn(MethodDecl('OnChannelClose'))
+        onclose.addstmt(StmtExpr(ExprCall(
+            destroysubtreevar,
+            args=[ _DestroyReason.NormalShutdown ])))
+        self.cls.addstmts([ onclose, Whitespace.NL ])
+
+        # OnChannelClose()
+        onerror = MethodDefn(MethodDecl('OnChannelError'))
+        onerror.addstmt(StmtExpr(ExprCall(
+            destroysubtreevar,
+            args=[ _DestroyReason.AbnormalShutdown ])))
+        self.cls.addstmts([ onerror, Whitespace.NL ])
+
         # FIXME: only manager protocols and non-manager protocols with
         # union types need Lookup().  we'll give it to all for the
         # time being (simpler)
         if 1 or p.decl.type.isManager():
             self.cls.addstmts(self.implementManagerIface())
 
         if p.usesShmem():
             self.cls.addstmts(self.makeShmemIface())
@@ -2776,16 +2821,112 @@ class _GenerateProtocolActorCode(ipdl.as
                 _printErrorMessage("  may have failed to kill child!"))
             fatalerror.addstmt(ifkill)
         else:
             # and if it happens on the child side, the child commits
             # seppuko
             fatalerror.addstmt(
                 _runtimeAbort('['+ actorname +'] abort()ing as a result'))
         self.cls.addstmts([ fatalerror, Whitespace.NL ])
+
+        ## DestroySubtree(bool normal)
+        whyvar = ExprVar('why')
+        subtreewhyvar = ExprVar('subtreewhy')
+        kidsvar = ExprVar('kids')
+        ivar = ExprVar('i')
+        ithkid = ExprIndex(kidsvar, ivar)
+
+        destroysubtree = MethodDefn(MethodDecl(
+            destroysubtreevar.name,
+            params=[ Decl(_DestroyReason.Type(), whyvar.name) ]))
+
+        if p.decl.type.isManager():
+            # only declare this for managers to avoid unused var warnings
+            destroysubtree.addstmts([
+                StmtDecl(
+                    Decl(_DestroyReason.Type(), subtreewhyvar.name),
+                    init=ExprConditional(
+                        ExprBinary(_DestroyReason.Deletion, '==', whyvar),
+                        _DestroyReason.AncestorDeletion, whyvar)),
+                Whitespace.NL
+            ])
+
+        for managed in p.decl.type.manages:
+            foreachdestroy = StmtFor(
+                init=Param(Type.UINT32, ivar.name, ExprLiteral.ZERO),
+                cond=ExprBinary(ivar, '<', _callCxxArrayLength(kidsvar)),
+                update=ExprPrefixUnop(ivar, '++'))
+            foreachdestroy.addstmt(StmtExpr(ExprCall(
+                ExprSelect(ithkid, '->', destroysubtreevar.name),
+                args=[ subtreewhyvar ])))
+
+            block = StmtBlock()
+            block.addstmts([
+                Whitespace(
+                    '// Recursively shutting down %s kids\n'% (managed.name()),
+                    indent=1),
+                StmtDecl(
+                    Decl(p.managedVarType(managed, self.side), kidsvar.name),
+                    init=p.managedVar(managed, self.side)),
+                foreachdestroy,
+            ])
+            destroysubtree.addstmt(block)
+        # finally, destroy "us"
+        destroysubtree.addstmt(StmtExpr(
+            ExprCall(_destroyMethod(), args=[ whyvar ])))
+
+        # XXX kick off DeallocSubtree() here rather than in a new
+        # event because that may be tricky on shutdown.  revisit if
+        # need be
+        if p.decl.type.isToplevel():
+            destroysubtree.addstmt(StmtExpr(ExprCall(deallocsubtreevar)))
+        
+        self.cls.addstmts([ destroysubtree, Whitespace.NL ])
+
+        ## DeallocSubtree()
+        deallocsubtree = MethodDefn(MethodDecl(deallocsubtreevar.name))
+        for managed in p.decl.type.manages:
+            foreachrecurse = StmtFor(
+                init=Param(Type.UINT32, ivar.name, ExprLiteral.ZERO),
+                cond=ExprBinary(ivar, '<', _callCxxArrayLength(kidsvar)),
+                update=ExprPrefixUnop(ivar, '++'))
+            foreachrecurse.addstmt(StmtExpr(ExprCall(
+                ExprSelect(ithkid, '->', deallocsubtreevar.name))))
+
+            foreachdealloc = StmtFor(
+                init=Param(Type.UINT32, ivar.name, ExprLiteral.ZERO),
+                cond=ExprBinary(ivar, '<', _callCxxArrayLength(kidsvar)),
+                update=ExprPrefixUnop(ivar, '++'))
+            foreachdealloc.addstmts([
+                StmtExpr(ExprCall(_deallocMethod(managed),
+                                  args=[ ithkid ]))
+            ])
+
+            block = StmtBlock()
+            block.addstmts([
+                Whitespace(
+                    '// Recursively deleting %s kids\n'% (managed.name()),
+                    indent=1),
+                StmtDecl(
+                    Decl(p.managedVarType(managed, self.side, ref=1),
+                         kidsvar.name),
+                    init=p.managedVar(managed, self.side)),
+                foreachrecurse,
+                Whitespace.NL,
+                # no need to copy |kids| here; we're the ones deleting
+                # stragglers, no outside C++ is being invoked (except
+                # Dealloc(subactor))
+                foreachdealloc,
+                StmtExpr(_callCxxArrayClear(p.managedVar(managed, self.side))),
+
+            ])
+            deallocsubtree.addstmt(block)
+        # don't delete outselves: either the manager will do it, or
+        # we're toplevel
+        self.cls.addstmts([ deallocsubtree, Whitespace.NL ])
         
         ## private members
         self.cls.addstmt(StmtDecl(Decl(p.channelType(), 'mChannel')))
         if p.decl.type.isToplevel():
             self.cls.addstmts([
                 StmtDecl(Decl(Type('IDMap', T=Type('ChannelListener')),
                               p.actorMapVar().name)),
                 StmtDecl(Decl(_actorIdType(), p.lastActorIdVar().name)),
@@ -2804,20 +2945,19 @@ class _GenerateProtocolActorCode(ipdl.as
                               p.shmemMapVar().name)),
                 StmtDecl(Decl(_shmemIdType(), p.lastShmemIdVar().name))
             ])
 
         for managed in p.decl.type.manages:
             self.cls.addstmts([
                 Whitespace('// Sorted by pointer value\n', indent=1),
                 StmtDecl(Decl(
-                    _cxxArrayType(p.managedCxxType(managed, self.side)),
+                    p.managedVarType(managed, self.side),
                     p.managedVar(managed, self.side).name)) ])
 
-
     def implementManagerIface(self):
         p = self.protocol
         routedvar = ExprVar('aRouted')
         idvar = ExprVar('aId')
         listenertype = Type('ChannelListener', ptr=1)
 
         register = MethodDefn(MethodDecl(
             p.registerMethod().name,
@@ -3277,18 +3417,19 @@ class _GenerateProtocolActorCode(ipdl.as
 
         return method
 
     def dtorPrologue(self, actorexpr):
         return [ self.failIfNullActor(actorexpr), Whitespace.NL ]
 
     def dtorEpilogue(self, md, actorexpr):
         return (self.unregisterActor(actorexpr)
-                + [ StmtExpr(self.callRemoveActor(md, actorexpr)),
-                    StmtExpr(self.callDeallocActor(md, actorexpr))
+                + [ StmtExpr(self.callActorDestroy(actorexpr)) ]
+                + [ StmtExpr(self.callRemoveActor(actorexpr)) ]
+                + [ StmtExpr(self.callDeallocActor(md, actorexpr))
                 ])
 
     def genAsyncSendMethod(self, md):
         method = MethodDefn(self.makeSendMethodDecl(md))
         msgvar, stmts = self.makeMessage(md, errfnSend)
         sendok, sendstmts = self.sendAsync(md, msgvar)
         method.addstmts(stmts
                         +[ Whitespace.NL ]
@@ -3576,24 +3717,27 @@ class _GenerateProtocolActorCode(ipdl.as
             ])
 
     def callAllocActor(self, md, retsems):
         return ExprCall(
             _allocMethod(md.decl.type.constructedType()),
             args=md.makeCxxArgs(params=1, retsems=retsems, retcallsems='out',
                                 implicit=0))
 
-    def callRemoveActor(self, md, actorexpr):
-        actortype = md.decl.type.constructedType()
-        if not actortype.isManaged():
-            return Whitespace('// unmanaged protocol\n')     
-
-        return _callCxxArrayRemoveSorted(
-            self.protocol.managerArrayExpr(actorexpr, self.side),
-            actorexpr)
+    def callActorDestroy(self, actorexpr, why=_DestroyReason.Deletion):
+        return ExprCall(ExprSelect(actorexpr, '->', 'DestroySubtree'),
+                        args=[ why ])
+
+    def callRemoveActor(self, actorexpr, actorarray=None):
+        if not self.protocol.decl.type.isManaged():
+            return Whitespace('// unmanaged protocol')
+        
+        if actorarray is None:
+            actorarray = self.protocol.managerArrayExpr(actorexpr, self.side)
+        return _callCxxArrayRemoveSorted(actorarray, actorexpr)
 
     def callDeallocActor(self, md, actorexpr):
         actor = md.decl.type.constructedType()
         return ExprCall(
             ExprSelect(ExprCall(self.protocol.managerMethod(actorexpr)), '->',
                        _deallocMethod(md.decl.type.constructedType()).name),
             args=[ actorexpr ])
 
--- a/ipc/ipdl/ipdl/type.py
+++ b/ipc/ipdl/ipdl/type.py
@@ -849,16 +849,19 @@ class GatherDecls(TcheckVisitor):
 
         # check the trigger message
         mname = t.msg
         if mname in self.seentriggers:
             self.error(loc, "trigger `%s' appears multiple times", mname)
         self.seentriggers.add(mname)
         
         mdecl = self.symtab.lookup(mname)
+        if mdecl.type.isIPDL() and mdecl.type.isProtocol():
+            mdecl = self.symtab.lookup(mname +'Constructor')
+        
         if mdecl is None:
             self.error(loc, "message `%s' has not been declared", mname)
         elif not mdecl.type.isMessage():
             self.error(
                 loc,
                 "`%s' should have message type, but instead has type `%s'",
                 mname, mdecl.type.typename())
         else:
--- a/ipc/ipdl/test/cxx/IPDLUnitTestTypes.h
+++ b/ipc/ipdl/test/cxx/IPDLUnitTestTypes.h
@@ -34,43 +34,44 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla__ipdltest_IPDLUnitTestTypes_h
 #define mozilla__ipdltest_IPDLUnitTestTypes_h
 
+#include "mozilla/ipc/ProtocolUtils.h" // ActorDestroyReason
 
 namespace mozilla {
 namespace _ipdltest {
 
 struct DirtyRect
 {
-    int x; int y; int w; int h;
+  int x; int y; int w; int h;
 };
 
 }
 }
 
 namespace IPC {
 template<>
 struct ParamTraits<mozilla::_ipdltest::DirtyRect>
 {
-    typedef mozilla::_ipdltest::DirtyRect paramType;
-    static void Write(Message* aMsg, const paramType& aParam) {
-        WriteParam(aMsg, aParam.x);
-        WriteParam(aMsg, aParam.y);
-        WriteParam(aMsg, aParam.w);
-        WriteParam(aMsg, aParam.h);
-    }
-    static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
-    {
-        return (ReadParam(aMsg, aIter, &aResult->x) &&
-                ReadParam(aMsg, aIter, &aResult->y) &&
-                ReadParam(aMsg, aIter, &aResult->w) &&
-                ReadParam(aMsg, aIter, &aResult->h));
-    }
+  typedef mozilla::_ipdltest::DirtyRect paramType;
+  static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(aMsg, aParam.x);
+    WriteParam(aMsg, aParam.y);
+    WriteParam(aMsg, aParam.w);
+    WriteParam(aMsg, aParam.h);
+  }
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    return (ReadParam(aMsg, aIter, &aResult->x) &&
+            ReadParam(aMsg, aIter, &aResult->y) &&
+            ReadParam(aMsg, aIter, &aResult->w) &&
+            ReadParam(aMsg, aIter, &aResult->h));
+  }
 };
 }
 
 
 #endif // ifndef mozilla__ipdltest_IPDLUnitTestTypes_h
--- a/ipc/ipdl/test/cxx/IPDLUnitTests.h
+++ b/ipc/ipdl/test/cxx/IPDLUnitTests.h
@@ -38,17 +38,25 @@
 
 #ifndef mozilla__ipdltest_IPDLUnitTests_h
 #define mozilla__ipdltest_IPDLUnitTests_h 1
 
 #include "base/message_loop.h"
 #include "base/process.h"
 #include "chrome/common/ipc_channel.h"
 
+#include "mozilla/ipc/GeckoThread.h"
+
+#include "nsIAppShell.h"
+
+#include "nsCOMPtr.h"
 #include "nsDebug.h"
+#include "nsServiceManagerUtils.h" // do_GetService()
+#include "nsWidgetsCID.h"       // NS_APPSHELL_CID
+
 
 #define MOZ_IPDL_TESTFAIL_LABEL "TEST-UNEXPECTED-FAIL"
 #define MOZ_IPDL_TESTPASS_LABEL "TEST-PASS"
 
 
 namespace mozilla {
 namespace _ipdltest {
 
@@ -88,23 +96,38 @@ inline void passed(const char* fmt, ...)
   fputc('\n', stdout);
 }
 
 //-----------------------------------------------------------------------------
 // parent process only
 
 void IPDLUnitTestMain(void* aData);
 
-// TODO: could clean up parent actor/subprocess
-
+inline void
+QuitParent()
+{
+  static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+  nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
+  appShell->Exit();
+}
 
 //-----------------------------------------------------------------------------
 // child process only
 
 void IPDLUnitTestChildInit(IPC::Channel* transport,
                            base::ProcessHandle parent,
                            MessageLoop* worker);
 
+inline void
+QuitChild()
+{
+  mozilla::ipc::BrowserProcessSubThread::
+    GetMessageLoop(mozilla::ipc::BrowserProcessSubThread::IO)->
+      PostTask(FROM_HERE, new MessageLoop::QuitTask());
+
+  MessageLoopForUI::current()->Quit();
+}
+
 } // namespace _ipdltest
 } // namespace mozilla
 
 
 #endif // ifndef mozilla__ipdltest_IPDLUnitTests_h
--- a/ipc/ipdl/test/cxx/Makefile.in
+++ b/ipc/ipdl/test/cxx/Makefile.in
@@ -58,16 +58,17 @@ FORCE_STATIC_LIB = 1
 EXPORT_LIBRARY = 1
 
 # Please keep these organized in the order "easy"-to-"hard"
 IPDLTESTS = \
   TestSanity  \
   TestLatency \
   TestManyChildAllocs  \
   TestDesc \
+  TestShutdown \
   TestArrays \
   $(NULL)
 
 IPDLTESTSRCS = $(addsuffix .cpp,$(IPDLTESTS))
 IPDLTESTHDRS = $(addprefix $(srcdir)/,$(addsuffix .h,$(IPDLTESTS)))
 
 TESTER_TEMPLATE := $(srcdir)/IPDLUnitTests.template.cpp
 GENTESTER := $(srcdir)/genIPDLUnitTests.py
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/PTestShutdown.ipdl
@@ -0,0 +1,37 @@
+include protocol "PTestShutdownSub.ipdl";
+
+namespace mozilla {
+namespace _ipdltest {
+
+sync protocol PTestShutdown {
+    manages PTestShutdownSub;
+
+child:
+    Start();
+
+parent:
+    // NB: we test deletion and crashing only, not shutdown, because
+    // crashing is the same code path as shutdown, and other IPDL unit
+    // tests check shutdown semantics
+    PTestShutdownSub(bool expectCrash);
+
+    // Used to synchronize between parent and child, to avoid racies
+    // around flushing socket write queues
+    sync Sync();
+
+    __delete__();
+
+
+state START:
+    send Start goto TESTING;
+
+state TESTING:
+    recv PTestShutdownSub goto TESTING;
+    recv Sync goto DYING;
+
+state DYING:
+    recv __delete__;
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/PTestShutdownSub.ipdl
@@ -0,0 +1,21 @@
+include protocol "PTestShutdown.ipdl";
+include protocol "PTestShutdownSubsub.ipdl";
+
+namespace mozilla {
+namespace _ipdltest {
+
+sync protocol PTestShutdownSub {
+    manager PTestShutdown;
+    manages PTestShutdownSubsub;
+
+parent:
+    PTestShutdownSubsub(bool expectParentDeleted);
+    __delete__();
+
+state CREATING:
+    recv PTestShutdownSubsub goto CREATING;
+    recv __delete__;
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/PTestShutdownSubsub.ipdl
@@ -0,0 +1,17 @@
+include protocol "PTestShutdownSub.ipdl";
+
+namespace mozilla {
+namespace _ipdltest {
+
+sync protocol PTestShutdownSubsub {
+    manager PTestShutdownSub;
+
+parent:
+    __delete__();
+
+state LIVE:
+    recv __delete__;
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
--- a/ipc/ipdl/test/cxx/TestArrays.cpp
+++ b/ipc/ipdl/test/cxx/TestArrays.cpp
@@ -1,16 +1,10 @@
 #include "TestArrays.h"
 
-#include "nsIAppShell.h"
-
-#include "nsCOMPtr.h"
-#include "nsServiceManagerUtils.h" // do_GetService()
-#include "nsWidgetsCID.h"       // NS_APPSHELL_CID
-
 #include "IPDLUnitTests.h"      // fail etc.
 
 namespace mozilla {
 namespace _ipdltest {
 
 static const uint32 nactors = 10;
 
 #define test_assert(_cond, _msg) \
@@ -66,22 +60,16 @@ TestArraysParent::DeallocPTestArraysSub(
 {
     test_assert(Cast(actor).mI == Cast(mKids[0]).mI,
                 "dtor sent to wrong actor");
     mKids.RemoveElementAt(0);
     delete actor;
     if (mKids.Length() > 0)
         return true;
 
-    passed("with flying colors");
-
-    static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-    nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
-    appShell->Exit();
-
     return true;
 }
 
 bool TestArraysParent::RecvTest1(
         const nsTArray<int>& ia,
         nsTArray<int>* oa)
 {
     test_assert(5 == ia.Length(), "wrong length");
@@ -320,16 +308,18 @@ TestArraysChild::RecvStart()
     Test8();
     Test9();
     Test10();
 
     for (uint32 i = 0; i < nactors; ++i)
         if (!PTestArraysSubChild::Send__delete__(mKids[i]))
             fail("can't send dtor");
 
+    Close();
+
     return true;
 }
 
 void
 TestArraysChild::Test1()
 {
     nsTArray<int> ia;
 
--- a/ipc/ipdl/test/cxx/TestArrays.h
+++ b/ipc/ipdl/test/cxx/TestArrays.h
@@ -1,11 +1,12 @@
 #ifndef mozilla__ipdltest_TestArrays_h
 #define mozilla__ipdltest_TestArrays_h 1
 
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
 
 #include "mozilla/_ipdltest/PTestArraysParent.h"
 #include "mozilla/_ipdltest/PTestArraysChild.h"
 
 #include "mozilla/_ipdltest/PTestArraysSubParent.h"
 #include "mozilla/_ipdltest/PTestArraysSubChild.h"
 
 namespace mozilla {
@@ -34,106 +35,141 @@ class TestArraysParent :
 {
 public:
     TestArraysParent();
     virtual ~TestArraysParent();
 
     void Main();
 
 protected:
+    NS_OVERRIDE
     virtual PTestArraysSubParent* AllocPTestArraysSub(const int& i)
     {
         PTestArraysSubParent* actor = new TestArraysSub(i);
         mKids.AppendElement(actor);
         return actor;
     }
 
+    NS_OVERRIDE
     virtual bool DeallocPTestArraysSub(PTestArraysSubParent* actor);
 
+    NS_OVERRIDE
     virtual bool RecvTest1(
             const nsTArray<int>& i1,
             nsTArray<int>* o1);
 
+    NS_OVERRIDE
     virtual bool RecvTest2(
             const nsTArray<PTestArraysSubParent*>& i1,
             nsTArray<PTestArraysSubParent*>* o1);
 
+    NS_OVERRIDE
     virtual bool RecvTest3(
             const IntDouble& i1,
             const IntDouble& i2,
             IntDouble* o1,
             IntDouble* o2);
 
+    NS_OVERRIDE
     virtual bool RecvTest4(
             const nsTArray<IntDouble>& i1,
             nsTArray<IntDouble>* o1);
 
+    NS_OVERRIDE
     virtual bool RecvTest5(
             const IntDoubleArrays& i1,
             const IntDoubleArrays& i2,
             const IntDoubleArrays& i3,
             IntDoubleArrays* o1,
             IntDoubleArrays* o2,
             IntDoubleArrays* o3);
 
+    NS_OVERRIDE
     virtual bool RecvTest6(
             const nsTArray<IntDoubleArrays>& i1,
             nsTArray<IntDoubleArrays>* o1);
 
+    NS_OVERRIDE
     virtual bool RecvTest7(
             const Actors& i1,
             const Actors& i2,
             const Actors& i3,
             Actors* o1,
             Actors* o2,
             Actors* o3);
 
+    NS_OVERRIDE
     virtual bool RecvTest8(
             const nsTArray<Actors>& i1,
             nsTArray<Actors>* o1);
+
+    NS_OVERRIDE
     virtual bool RecvTest9(
             const Unions& i1,
             const Unions& i2,
             const Unions& i3,
             const Unions& i4,
             Unions* o1,
             Unions* o2,
             Unions* o3,
             Unions* o4);
+
+    NS_OVERRIDE
     virtual bool RecvTest10(
             const nsTArray<Unions>& i1,
             nsTArray<Unions>* o1);
 
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");  
+        passed("ok");
+        QuitParent();
+    }
+
 private:
     nsTArray<PTestArraysSubParent*> mKids;
 };
 
 
 class TestArraysChild :
     public PTestArraysChild
 {
 public:
     TestArraysChild();
     virtual ~TestArraysChild();
 
 protected:
+    NS_OVERRIDE
     virtual PTestArraysSubChild* AllocPTestArraysSub(const int& i)
     {
         PTestArraysSubChild* actor = new TestArraysSub(i);
         mKids.AppendElement(actor);
         return actor;
     }
+
+    NS_OVERRIDE
     virtual bool DeallocPTestArraysSub(PTestArraysSubChild* actor)
     {
         delete actor;
         return true;
     }
 
+    NS_OVERRIDE
     virtual bool RecvStart();
 
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");
+        QuitChild();
+    }
+
 private:
     void Test1();
     void Test2();
     void Test3();
     void Test4();
     void Test5();
     void Test6();
     void Test7();
--- a/ipc/ipdl/test/cxx/TestDesc.cpp
+++ b/ipc/ipdl/test/cxx/TestDesc.cpp
@@ -1,16 +1,10 @@
 #include "TestDesc.h"
 
-#include "nsIAppShell.h"
-
-#include "nsCOMPtr.h"
-#include "nsServiceManagerUtils.h" // do_GetService()
-#include "nsWidgetsCID.h"       // NS_APPSHELL_CID
-
 #include "IPDLUnitTests.h"      // fail etc.
 
 namespace mozilla {
 namespace _ipdltest {
 
 //-----------------------------------------------------------------------------
 // parent
 void
@@ -29,21 +23,17 @@ TestDescParent::Main()
 }
 
 bool
 TestDescParent::RecvOk(PTestDescSubsubParent* a)
 {
     if (!a)
         fail("didn't receive Subsub");
 
-    passed("ok");
-
-    static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-    nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
-    appShell->Exit();
+    Close();
 
     return true;
 }
 
 
 PTestDescSubParent*
 TestDescParent::AllocPTestDescSub() {
     return new TestDescSubParent();
--- a/ipc/ipdl/test/cxx/TestDesc.h
+++ b/ipc/ipdl/test/cxx/TestDesc.h
@@ -1,11 +1,13 @@
 #ifndef mozilla_ipdltest_TestDesc_h
 #define mozilla_ipdltest_TestDesc_h
 
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
+
 #include "mozilla/_ipdltest/PTestDescParent.h"
 #include "mozilla/_ipdltest/PTestDescChild.h"
 
 #include "mozilla/_ipdltest/PTestDescSubParent.h"
 #include "mozilla/_ipdltest/PTestDescSubChild.h"
 
 #include "mozilla/_ipdltest/PTestDescSubsubParent.h"
 #include "mozilla/_ipdltest/PTestDescSubsubChild.h"
@@ -20,63 +22,93 @@ class TestDescParent :
     public PTestDescParent
 {
 public:
     TestDescParent() { }
     virtual ~TestDescParent() { }
 
     void Main();
 
+    NS_OVERRIDE
     virtual bool RecvOk(PTestDescSubsubParent* a);
 
 protected:
+    NS_OVERRIDE
     virtual PTestDescSubParent* AllocPTestDescSub();
+    NS_OVERRIDE
     virtual bool DeallocPTestDescSub(PTestDescSubParent* actor);
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");  
+        passed("ok");
+        QuitParent();
+    }
 };
 
 
 class TestDescChild :
     public PTestDescChild
 {
 public:
     TestDescChild() { }
     virtual ~TestDescChild() { }
 
 protected:
+    NS_OVERRIDE
     virtual PTestDescSubChild* AllocPTestDescSub();
+
+    NS_OVERRIDE
     virtual bool DeallocPTestDescSub(PTestDescSubChild* actor);
+
+    NS_OVERRIDE
     virtual bool RecvTest(PTestDescSubsubChild* a);
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");
+        QuitChild();
+    }
 };
 
 
 //-----------------------------------------------------------------------------
 // First descendent
 //
 class TestDescSubParent :
     public PTestDescSubParent
 {
 public:
     TestDescSubParent() { }
     virtual ~TestDescSubParent() { }
 
 protected:
+    NS_OVERRIDE
     virtual PTestDescSubsubParent* AllocPTestDescSubsub();
+
+    NS_OVERRIDE
     virtual bool DeallocPTestDescSubsub(PTestDescSubsubParent* actor);
 };
 
 
 class TestDescSubChild :
     public PTestDescSubChild
 {
 public:
     TestDescSubChild() { }
     virtual ~TestDescSubChild() { }
 
 protected:
+    NS_OVERRIDE
     virtual PTestDescSubsubChild* AllocPTestDescSubsub();
+    NS_OVERRIDE
     virtual bool DeallocPTestDescSubsub(PTestDescSubsubChild* actor);
 };
 
 
 //-----------------------------------------------------------------------------
 // Grand-descendent
 //
 class TestDescSubsubParent :
--- a/ipc/ipdl/test/cxx/TestLatency.cpp
+++ b/ipc/ipdl/test/cxx/TestLatency.cpp
@@ -1,21 +1,13 @@
 #include "TestLatency.h"
 
-#include "nsIAppShell.h"
-
-#include "nsCOMPtr.h"
-#include "nsServiceManagerUtils.h" // do_GetService()
-#include "nsWidgetsCID.h"       // NS_APPSHELL_CID
-
 #include "IPDLUnitTests.h"      // fail etc.
 
 
-#define NR_TRIALS 10000
-
 namespace mozilla {
 namespace _ipdltest {
 
 //-----------------------------------------------------------------------------
 // parent
 
 TestLatencyParent::TestLatencyParent() :
     mStart(),
@@ -61,23 +53,17 @@ TestLatencyParent::Ping5Pong5Trial()
     SendPing5();
     SendPing5();
     SendPing5();
 }
 
 void
 TestLatencyParent::Exit()
 {
-    passed("average ping/pong latency: %g sec, average ping5/pong5 latency: %g sec",
-           mPPTimeTotal.ToSeconds() / (double) NR_TRIALS,
-           mPP5TimeTotal.ToSeconds() / (double) NR_TRIALS);
-
-    static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-    nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
-    appShell->Exit();
+    Close();
 }
 
 bool
 TestLatencyParent::RecvPong()
 {
     TimeDuration thisTrial = (TimeStamp::Now() - mStart);
     mPPTimeTotal += thisTrial;
 
--- a/ipc/ipdl/test/cxx/TestLatency.h
+++ b/ipc/ipdl/test/cxx/TestLatency.h
@@ -1,38 +1,55 @@
 #ifndef mozilla__ipdltest_TestLatency_h
 #define mozilla__ipdltest_TestLatency_h 1
 
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
 
 #include "mozilla/_ipdltest/PTestLatencyParent.h"
 #include "mozilla/_ipdltest/PTestLatencyChild.h"
 
 #include "mozilla/TimeStamp.h"
 
+#define NR_TRIALS 10000
+
 namespace mozilla {
 namespace _ipdltest {
 
-
 class TestLatencyParent :
     public PTestLatencyParent
 {
 private:
     typedef mozilla::TimeStamp TimeStamp;
     typedef mozilla::TimeDuration TimeDuration;
 
 public:
     TestLatencyParent();
     virtual ~TestLatencyParent();
 
     void Main();
 
-protected:    
+protected:
+    NS_OVERRIDE
     virtual bool RecvPong();
+    NS_OVERRIDE
     virtual bool RecvPong5();
 
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");  
+
+        passed("average ping/pong latency: %g sec, average ping5/pong5 latency: %g sec",
+               mPPTimeTotal.ToSeconds() / (double) NR_TRIALS,
+               mPP5TimeTotal.ToSeconds() / (double) NR_TRIALS);
+
+        QuitParent();
+    }
+
 private:
     void PingPongTrial();
     void Ping5Pong5Trial();
     void Exit();
 
     TimeStamp mStart;
     TimeDuration mPPTimeTotal;
     TimeDuration mPP5TimeTotal;
@@ -48,18 +65,28 @@ private:
 class TestLatencyChild :
     public PTestLatencyChild
 {
 public:
     TestLatencyChild();
     virtual ~TestLatencyChild();
 
 protected:
+    NS_OVERRIDE
     virtual bool RecvPing();
+    NS_OVERRIDE
     virtual bool RecvPing5();
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");
+        QuitChild();
+    }
 };
 
 
 } // namespace _ipdltest
 } // namespace mozilla
 
 
 #endif // ifndef mozilla__ipdltest_TestLatency_h
--- a/ipc/ipdl/test/cxx/TestManyChildAllocs.cpp
+++ b/ipc/ipdl/test/cxx/TestManyChildAllocs.cpp
@@ -1,16 +1,10 @@
 #include "TestManyChildAllocs.h"
 
-#include "nsIAppShell.h"
-
-#include "nsCOMPtr.h"
-#include "nsServiceManagerUtils.h" // do_GetService()
-#include "nsWidgetsCID.h"       // NS_APPSHELL_CID
-
 #include "IPDLUnitTests.h"      // fail etc.
 
 
 #define NALLOCS 10
 
 namespace mozilla {
 namespace _ipdltest {
 
@@ -31,23 +25,19 @@ TestManyChildAllocsParent::Main()
 {
     if (!SendGo())
         fail("can't send Go()");
 }
 
 bool
 TestManyChildAllocsParent::RecvDone()
 {
-    // should clean up ...
-
-    passed("allocs were successfuly");
-    
-    static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-    nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
-    appShell->Exit();
+    // explicitly *not* cleaning up, so we can sanity-check IPDL's
+    // auto-shutdown/cleanup handling
+    Close();
 
     return true;
 }
 
 bool
 TestManyChildAllocsParent::DeallocPTestManyChildAllocsSub(
     PTestManyChildAllocsSubParent* __a)
 {
--- a/ipc/ipdl/test/cxx/TestManyChildAllocs.h
+++ b/ipc/ipdl/test/cxx/TestManyChildAllocs.h
@@ -1,11 +1,13 @@
 #ifndef mozilla__ipdltest_TestManyChildAllocs_h
 #define mozilla__ipdltest_TestManyChildAllocs_h 1
 
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
+
 #include "mozilla/_ipdltest/PTestManyChildAllocsParent.h"
 #include "mozilla/_ipdltest/PTestManyChildAllocsChild.h"
 
 #include "mozilla/_ipdltest/PTestManyChildAllocsSubParent.h"
 #include "mozilla/_ipdltest/PTestManyChildAllocsSubChild.h"
 
 namespace mozilla {
 namespace _ipdltest {
@@ -17,46 +19,70 @@ class TestManyChildAllocsParent :
 {
 public:
     TestManyChildAllocsParent();
     virtual ~TestManyChildAllocsParent();
 
     void Main();
 
 protected:
+    NS_OVERRIDE
     virtual bool RecvDone();
+    NS_OVERRIDE
     virtual bool DeallocPTestManyChildAllocsSub(PTestManyChildAllocsSubParent* __a);
+    NS_OVERRIDE
     virtual PTestManyChildAllocsSubParent* AllocPTestManyChildAllocsSub();
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");
+        passed("ok");
+        QuitParent();
+    }
 };
 
 
 class TestManyChildAllocsChild :
     public PTestManyChildAllocsChild
 {
 public:
     TestManyChildAllocsChild();
     virtual ~TestManyChildAllocsChild();
 
 protected:
+    NS_OVERRIDE
     virtual bool RecvGo();
+    NS_OVERRIDE
     virtual bool DeallocPTestManyChildAllocsSub(PTestManyChildAllocsSubChild* __a);
+    NS_OVERRIDE
     virtual PTestManyChildAllocsSubChild* AllocPTestManyChildAllocsSub();
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");
+        QuitChild();
+    }
 };
 
 
 // do-nothing sub-protocol actors
 
 class TestManyChildAllocsSubParent :
     public PTestManyChildAllocsSubParent
 {
 public:
     TestManyChildAllocsSubParent() { }
     virtual ~TestManyChildAllocsSubParent() { }
 
 protected:
+    NS_OVERRIDE
     virtual bool RecvHello() { return true; }
 };
 
 
 class TestManyChildAllocsSubChild :
     public PTestManyChildAllocsSubChild
 {
 public:
--- a/ipc/ipdl/test/cxx/TestSanity.cpp
+++ b/ipc/ipdl/test/cxx/TestSanity.cpp
@@ -1,16 +1,10 @@
 #include "TestSanity.h"
 
-#include "nsIAppShell.h"
-
-#include "nsCOMPtr.h"
-#include "nsServiceManagerUtils.h" // do_GetService()
-#include "nsWidgetsCID.h"       // NS_APPSHELL_CID
-
 #include "IPDLUnitTests.h"      // fail etc.
 
 namespace mozilla {
 namespace _ipdltest {
 
 //-----------------------------------------------------------------------------
 // parent
 
@@ -36,32 +30,21 @@ bool
 TestSanityParent::RecvPong(const int& one, const float& zeroPtTwoFive)
 {
     if (1 != one)
         fail("invalid argument `%d', should have been `1'", one);
 
     if (0.25f != zeroPtTwoFive)
         fail("invalid argument `%g', should have been `0.25'", zeroPtTwoFive);
 
-    passed("sent ping/received pong");
-
-    static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-    nsCOMPtr<nsIAppShell> appShell (do_GetService(kAppShellCID));
-    appShell->Exit();
+    Close();
 
     return true;
 }
 
-bool
-TestSanityParent::RecvUNREACHED()
-{
-    fail("unreached");
-    return false;               // not reached
-}
-
 
 //-----------------------------------------------------------------------------
 // child
 
 TestSanityChild::TestSanityChild()
 {
     MOZ_COUNT_CTOR(TestSanityChild);
 }
@@ -80,18 +63,11 @@ TestSanityChild::RecvPing(const int& zer
     if (0.5f != zeroPtFive)
         fail("invalid argument `%g', should have been `0.5'", zeroPtFive);
 
     if (!SendPong(1, 0.25f))
         fail("sending Pong");
     return true;
 }
 
-bool
-TestSanityChild::RecvUNREACHED()
-{
-    fail("unreached");
-    return false;               // not reached
-}
-
 
 } // namespace _ipdltest
 } // namespace mozilla
--- a/ipc/ipdl/test/cxx/TestSanity.h
+++ b/ipc/ipdl/test/cxx/TestSanity.h
@@ -1,11 +1,12 @@
 #ifndef mozilla__ipdltest_TestSanity_h
 #define mozilla__ipdltest_TestSanity_h 1
 
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
 
 #include "mozilla/_ipdltest/PTestSanityParent.h"
 #include "mozilla/_ipdltest/PTestSanityChild.h"
 
 namespace mozilla {
 namespace _ipdltest {
 
 
@@ -14,31 +15,48 @@ class TestSanityParent :
 {
 public:
     TestSanityParent();
     virtual ~TestSanityParent();
 
     void Main();
 
 protected:    
+    NS_OVERRIDE
     virtual bool RecvPong(const int& one, const float& zeroPtTwoFive);
-    virtual bool RecvUNREACHED();
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");  
+        passed("ok");
+        QuitParent();
+    }
 };
 
 
 class TestSanityChild :
     public PTestSanityChild
 {
 public:
     TestSanityChild();
     virtual ~TestSanityChild();
 
 protected:
+    NS_OVERRIDE
     virtual bool RecvPing(const int& zero, const float& zeroPtFive);
-    virtual bool RecvUNREACHED();
+
+    NS_OVERRIDE
+    virtual void ActorDestroy(ActorDestroyReason why)
+    {
+        if (NormalShutdown != why)
+            fail("unexpected destruction!");
+        QuitChild();
+    }
 };
 
 
 } // namespace _ipdltest
 } // namespace mozilla
 
 
 #endif // ifndef mozilla__ipdltest_TestSanity_h
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestShutdown.cpp
@@ -0,0 +1,227 @@
+#include "TestShutdown.h"
+
+namespace mozilla {
+namespace _ipdltest {
+
+//-----------------------------------------------------------------------------
+// Parent side
+void
+TestShutdownParent::Main()
+{
+    SendStart();
+}
+
+void
+TestShutdownParent::ActorDestroy(ActorDestroyReason why)
+{
+    if (AbnormalShutdown != why)
+        fail("should have ended test with crash!");
+
+    passed("ok");
+
+    QuitParent();
+}
+
+void
+TestShutdownSubParent::ActorDestroy(ActorDestroyReason why)
+{
+    nsTArray<PTestShutdownSubParent*> broArr; // grumble grumble
+    Manager()->ManagedPTestShutdownSubParent(broArr);
+    if (broArr.Length() == 0)
+        fail("manager should still have managees!");
+
+    if (mExpectCrash && AbnormalShutdown != why)
+        fail("expected crash!");
+    else if (!mExpectCrash && AbnormalShutdown == why)
+        fail("wasn't expecting crash!");
+
+    nsTArray<PTestShutdownSubsubParent*> kidsArr;
+    ManagedPTestShutdownSubsubParent(kidsArr);
+    if (mExpectCrash && 0 == kidsArr.Length())
+        fail("expected to *still* have kids");
+}
+
+void
+TestShutdownSubsubParent::ActorDestroy(ActorDestroyReason why)
+{
+    nsTArray<PTestShutdownSubsubParent*> broArr;
+    Manager()->ManagedPTestShutdownSubsubParent(broArr);
+    if (broArr.Length() == 0)
+        fail("manager should still have managees!");
+
+    if (mExpectParentDeleted && AncestorDeletion != why)
+        fail("expected ParentDeleted == why");
+    else if (!mExpectParentDeleted && AncestorDeletion == why)
+        fail("wasn't expecting parent delete");
+}
+
+//-----------------------------------------------------------------------------
+// Child side
+
+bool
+TestShutdownChild::RecvStart()
+{
+    // test 1: alloc some actors and subactors, delete in
+    // managee-before-manager order
+    {
+        bool expectCrash = false, expectParentDeleted = false;
+
+        PTestShutdownSubChild* c1 =
+            SendPTestShutdownSubConstructor(expectCrash);
+        if (!c1)
+            fail("problem sending ctor");
+
+        PTestShutdownSubChild* c2 =
+            SendPTestShutdownSubConstructor(expectCrash);
+        if (!c2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild* c1s1 =
+            c1->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c1s1)
+            fail("problem sending ctor");
+        PTestShutdownSubsubChild* c1s2 =
+            c1->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c1s2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild* c2s1 =
+            c2->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c2s1)
+            fail("problem sending ctor");
+        PTestShutdownSubsubChild* c2s2 =
+            c2->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c2s2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild::Send__delete__(c1s1);
+        PTestShutdownSubsubChild::Send__delete__(c1s2);
+        PTestShutdownSubsubChild::Send__delete__(c2s1);
+        PTestShutdownSubsubChild::Send__delete__(c2s2);
+
+        PTestShutdownSubChild::Send__delete__(c1);
+        PTestShutdownSubChild::Send__delete__(c2);
+    }
+
+    // test 2: alloc some actors and subactors, delete managers first
+    {
+        bool expectCrash = false, expectParentDeleted = true;
+
+        PTestShutdownSubChild* c1 =
+            SendPTestShutdownSubConstructor(expectCrash);
+        if (!c1)
+            fail("problem sending ctor");
+
+        PTestShutdownSubChild* c2 =
+            SendPTestShutdownSubConstructor(expectCrash);
+        if (!c2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild* c1s1 =
+            c1->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c1s1)
+            fail("problem sending ctor");
+        PTestShutdownSubsubChild* c1s2 =
+            c1->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c1s2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild* c2s1 =
+            c2->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c2s1)
+            fail("problem sending ctor");
+        PTestShutdownSubsubChild* c2s2 =
+            c2->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c2s2)
+            fail("problem sending ctor");
+
+        // delete parents without deleting kids
+        PTestShutdownSubChild::Send__delete__(c1);
+        PTestShutdownSubChild::Send__delete__(c2);
+    }
+
+    // test 3: alloc some actors and subactors, then crash
+    {
+        bool expectCrash = true, expectParentDeleted = false;
+
+        PTestShutdownSubChild* c1 =
+            SendPTestShutdownSubConstructor(expectCrash);
+        if (!c1)
+            fail("problem sending ctor");
+
+        PTestShutdownSubChild* c2 =
+            SendPTestShutdownSubConstructor(expectCrash);
+        if (!c2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild* c1s1 =
+            c1->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c1s1)
+            fail("problem sending ctor");
+        PTestShutdownSubsubChild* c1s2 =
+            c1->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c1s2)
+            fail("problem sending ctor");
+
+        PTestShutdownSubsubChild* c2s1 =
+            c2->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c2s1)
+            fail("problem sending ctor");
+        PTestShutdownSubsubChild* c2s2 =
+            c2->SendPTestShutdownSubsubConstructor(expectParentDeleted);
+        if (!c2s2)
+            fail("problem sending ctor");
+
+        // make sure the ctors have been processed by the other side;
+        // the write end of the socket may temporarily be unwriteable
+        if (!SendSync())
+            fail("can't synchronize with parent");
+
+        // "crash", but without tripping tinderbox assert/abort
+        // detectors
+        _exit(0);
+    }
+}
+
+void
+TestShutdownChild::ActorDestroy(ActorDestroyReason why)
+{
+    fail("hey wait ... we should have crashed!");
+}
+
+void
+TestShutdownSubChild::ActorDestroy(ActorDestroyReason why)
+{
+    nsTArray<PTestShutdownSubChild*> broArr;
+    Manager()->ManagedPTestShutdownSubChild(broArr);
+    if (broArr.Length() == 0)
+        fail("manager should still have managees!");
+
+    if (mExpectCrash && AbnormalShutdown != why)
+        fail("expected crash!");
+    else if (!mExpectCrash && AbnormalShutdown == why)
+        fail("wasn't expecting crash!");
+
+    nsTArray<PTestShutdownSubsubChild*> kidsArr;
+    ManagedPTestShutdownSubsubChild(kidsArr);
+    if (mExpectCrash && 0 == kidsArr.Length())
+        fail("expected to *still* have kids");
+}
+
+void
+TestShutdownSubsubChild::ActorDestroy(ActorDestroyReason why)
+{
+    nsTArray<PTestShutdownSubsubChild*> broArr;
+    Manager()->ManagedPTestShutdownSubsubChild(broArr);
+    if (broArr.Length() == 0)
+        fail("manager should still have managees!");
+
+    if (mExpectParentDeleted && AncestorDeletion != why)
+        fail("expected ParentDeleted == why");
+    else if (!mExpectParentDeleted && AncestorDeletion == why)
+        fail("wasn't expecting parent delete");
+}
+
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestShutdown.h
@@ -0,0 +1,222 @@
+#ifndef mozilla__ipdltest_TestShutdown_h
+#define mozilla__ipdltest_TestShutdown_h 1
+
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
+
+#include "mozilla/_ipdltest/PTestShutdownParent.h"
+#include "mozilla/_ipdltest/PTestShutdownChild.h"
+
+#include "mozilla/_ipdltest/PTestShutdownSubParent.h"
+#include "mozilla/_ipdltest/PTestShutdownSubChild.h"
+
+#include "mozilla/_ipdltest/PTestShutdownSubsubParent.h"
+#include "mozilla/_ipdltest/PTestShutdownSubsubChild.h"
+
+namespace mozilla {
+namespace _ipdltest {
+
+//-----------------------------------------------------------------------------
+// Parent side
+
+class TestShutdownSubsubParent :
+    public PTestShutdownSubsubParent
+{
+public:
+    TestShutdownSubsubParent(bool expectParentDeleted) :
+        mExpectParentDeleted(expectParentDeleted)
+    {
+    }
+
+    virtual ~TestShutdownSubsubParent()
+    {
+    }
+
+protected:
+    NS_OVERRIDE
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+
+private:
+    bool mExpectParentDeleted;
+};
+
+
+class TestShutdownSubParent :
+    public PTestShutdownSubParent
+{
+public:
+    TestShutdownSubParent(bool expectCrash) : mExpectCrash(expectCrash)
+    {
+    }
+
+    virtual ~TestShutdownSubParent()
+    {
+    }
+
+protected:
+    NS_OVERRIDE
+    virtual PTestShutdownSubsubParent*
+    AllocPTestShutdownSubsub(const bool& expectParentDelete)
+    {
+        return new TestShutdownSubsubParent(expectParentDelete);
+    }
+
+    NS_OVERRIDE
+    virtual bool
+    DeallocPTestShutdownSubsub(PTestShutdownSubsubParent* actor)
+    {
+        delete actor;
+        return true;
+    }
+
+    NS_OVERRIDE
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+
+private:
+    bool mExpectCrash;
+};
+
+
+class TestShutdownParent :
+    public PTestShutdownParent
+{
+public:
+    TestShutdownParent()
+    {
+    }
+    virtual ~TestShutdownParent()
+    {
+    }
+
+    void Main();
+
+protected:
+    NS_OVERRIDE virtual bool RecvSync() { return true; }
+
+    NS_OVERRIDE
+    virtual PTestShutdownSubParent*
+    AllocPTestShutdownSub(const bool& expectCrash)
+    {
+        return new TestShutdownSubParent(expectCrash);
+    }
+
+    NS_OVERRIDE
+    virtual bool
+    DeallocPTestShutdownSub(PTestShutdownSubParent* actor)
+    {
+        delete actor;
+        return true;
+    }
+
+    NS_OVERRIDE
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+};
+
+
+//-----------------------------------------------------------------------------
+// Child side
+
+class TestShutdownSubsubChild :
+    public PTestShutdownSubsubChild
+{
+public:
+    TestShutdownSubsubChild(bool expectParentDeleted) :
+        mExpectParentDeleted(expectParentDeleted)
+    {
+    }
+    virtual ~TestShutdownSubsubChild()
+    {
+    }
+
+protected:
+    NS_OVERRIDE
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+
+private:
+    bool mExpectParentDeleted;
+};
+
+
+class TestShutdownSubChild :
+    public PTestShutdownSubChild
+{
+public:
+    TestShutdownSubChild(bool expectCrash) : mExpectCrash(expectCrash)
+    {
+    }
+
+    virtual ~TestShutdownSubChild()
+    {
+    }
+
+protected:
+    NS_OVERRIDE
+    virtual PTestShutdownSubsubChild*
+    AllocPTestShutdownSubsub(const bool& expectParentDelete)
+    {
+        return new TestShutdownSubsubChild(expectParentDelete);
+    }
+
+    NS_OVERRIDE
+    virtual bool
+    DeallocPTestShutdownSubsub(PTestShutdownSubsubChild* actor)
+    {
+        delete actor;
+        return true;
+    }
+
+    NS_OVERRIDE
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+
+private:
+    bool mExpectCrash;
+};
+
+
+class TestShutdownChild :
+    public PTestShutdownChild
+{
+public:
+    TestShutdownChild()
+    {
+    }
+    virtual ~TestShutdownChild()
+    {
+    }
+
+protected:
+    NS_OVERRIDE
+    virtual bool
+    RecvStart();
+
+    NS_OVERRIDE
+    virtual PTestShutdownSubChild*
+    AllocPTestShutdownSub(
+        const bool& expectCrash)
+    {
+        return new TestShutdownSubChild(expectCrash);
+    }
+
+    NS_OVERRIDE
+    virtual bool
+    DeallocPTestShutdownSub(PTestShutdownSubChild* actor)
+    {
+        delete actor;
+        return true;
+    }
+
+    NS_OVERRIDE
+    virtual void
+    ActorDestroy(ActorDestroyReason why);
+};
+
+
+} // namespace _ipdltest
+} // namespace mozilla
+
+
+#endif // ifndef mozilla__ipdltest_TestShutdown_h
--- a/ipc/ipdl/test/cxx/ipdl.mk
+++ b/ipc/ipdl/test/cxx/ipdl.mk
@@ -3,9 +3,12 @@ IPDLSRCS =					\
   PTestArraysSub.ipdl				\
   PTestDesc.ipdl				\
   PTestDescSub.ipdl				\
   PTestDescSubsub.ipdl				\
   PTestLatency.ipdl				\
   PTestManyChildAllocs.ipdl			\
   PTestManyChildAllocsSub.ipdl			\
   PTestSanity.ipdl				\
+  PTestShutdown.ipdl				\
+  PTestShutdownSub.ipdl				\
+  PTestShutdownSubsub.ipdl			\
   $(NULL)
--- a/ipc/ipdl/test/ipdl/ok/managerProtocol.ipdl
+++ b/ipc/ipdl/test/ipdl/ok/managerProtocol.ipdl
@@ -3,9 +3,11 @@ include protocol "managedProtocol.ipdl";
 // sanity check of managed/manager protocols
 
 protocol managerProtocol {
     manages managedProtocol;
 
 parent:
     managedProtocol(int i);
 
+state CREATING:
+    recv managedProtocol goto CREATING;
 };
--- a/toolkit/library/dlldeps-xul.cpp
+++ b/toolkit/library/dlldeps-xul.cpp
@@ -51,12 +51,12 @@ void xxxNeverCalledXUL()
   XRE_FreeAppData(nsnull);
   XRE_ChildProcessTypeToString(GeckoProcessType_Default);
   XRE_StringToChildProcessType("");
   XRE_GetProcessType();
 #ifdef MOZ_IPC
   XRE_InitChildProcess(0, nsnull, GeckoProcessType_Default);
   XRE_InitParentProcess(0, nsnull, nsnull, nsnull);
   XRE_RunAppShell();
-  XRE_ShutdownChildProcess(nsnull);
+  XRE_ShutdownChildProcess();
   XRE_SendTestShellCommand(nsnull, nsnull, nsnull);
 #endif
 }
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -454,26 +454,35 @@ XRE_RunAppShell()
 template<>
 struct RunnableMethodTraits<ContentProcessChild>
 {
     static void RetainCallee(ContentProcessChild* obj) { }
     static void ReleaseCallee(ContentProcessChild* obj) { }
 };
 
 void
-XRE_ShutdownChildProcess(MessageLoop* aUILoop)
+XRE_ShutdownChildProcess()
 {
-    NS_ASSERTION(aUILoop, "Shouldn't be null!");
-    if (aUILoop) {
-        NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-        if (GeckoProcessType_Content == XRE_GetProcessType())
-            aUILoop->PostTask(
-                FROM_HERE,
-                NewRunnableMethod(ContentProcessChild::GetSingleton(),
-                                  &ContentProcessChild::Quit));
+    NS_ABORT_IF_FALSE(NS_IsMainThread(), "Wrong thread!");
+
+    MessageLoop* uiLoop = MessageLoop::current();
+    MessageLoop* ioLoop = XRE_GetIOMessageLoop();
+
+    NS_ABORT_IF_FALSE(!!ioLoop, "Bad shutdown order");
+    ioLoop->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+
+    NS_ABORT_IF_FALSE(!!uiLoop, "Bad shutdown order");
+    if (GeckoProcessType_Content == XRE_GetProcessType()) {
+        uiLoop->PostTask(
+            FROM_HERE,
+            NewRunnableMethod(ContentProcessChild::GetSingleton(),
+                              &ContentProcessChild::Quit));
+    }
+    else {
+        uiLoop->Quit();
     }
 }
 
 namespace {
 TestShellParent* gTestShellParent = nsnull;
 }
 
 bool
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -473,17 +473,17 @@ XRE_API(nsresult,
         XRE_RunAppShell, ())
 
 XRE_API(nsresult,
         XRE_InitCommandLine, (int aArgc, char* aArgv[]))
 
 class MessageLoop;
 
 XRE_API(void,
-        XRE_ShutdownChildProcess, (MessageLoop* aUILoop))
+        XRE_ShutdownChildProcess, ())
 
 XRE_API(MessageLoop*,
         XRE_GetIOMessageLoop, ())
 
 struct JSContext;
 struct JSString;
 
 XRE_API(bool,