Bug 1103036 - Allow ContentChild to perform tasks during shutdown; r=billm
authorJim Chen <nchen@mozilla.com>
Sat, 10 Jan 2015 13:18:59 -0500
changeset 248959 818164d97da8d620836a5ff023e4a20fbbfae1f6
parent 248958 ee29d84c968a826c02463a76c5a8361ff001ae1f
child 248960 9d50f9865ed636c85de582b8d77b5b5d054fd497
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1103036
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1103036 - Allow ContentChild to perform tasks during shutdown; r=billm
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2484,16 +2484,31 @@ ContentChild::RecvLoadPluginResult(const
 bool
 ContentChild::RecvAssociatePluginId(const uint32_t& aPluginId,
                                     const base::ProcessId& aProcessId)
 {
     plugins::PluginModuleContentParent::AssociatePluginId(aPluginId, aProcessId);
     return true;
 }
 
+bool
+ContentChild::RecvShutdown()
+{
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    if (os) {
+        os->NotifyObservers(this, "content-child-shutdown", nullptr);
+    }
+    // Let the parent know we're done and let it close the channel.
+    // Both sides will clean up even if an error occurs here.
+    MessageLoop::current()->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this, &ContentChild::SendFinishShutdown));
+    return true;
+}
+
 PBrowserOrId
 ContentChild::GetBrowserOrId(TabChild* aTabChild)
 {
     if (!aTabChild ||
         this == aTabChild->Manager()) {
         return PBrowserOrId(aTabChild);
     }
     else {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -372,16 +372,17 @@ public:
                                       const bool& aResult) MOZ_OVERRIDE;
 
     virtual bool RecvStartProfiler(const uint32_t& aEntries,
                                    const double& aInterval,
                                    const nsTArray<nsCString>& aFeatures,
                                    const nsTArray<nsCString>& aThreadNameFilters) MOZ_OVERRIDE;
     virtual bool RecvStopProfiler() MOZ_OVERRIDE;
     virtual bool RecvGetProfile(nsCString* aProfile) MOZ_OVERRIDE;
+    virtual bool RecvShutdown() MOZ_OVERRIDE;
 
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1491,43 +1491,57 @@ ContentParent::TransformPreallocatedInto
 {
     // Reset mAppManifestURL, mIsForBrowser and mOSPrivileges for browser.
     mOpener = aOpener;
     mAppManifestURL.Truncate();
     mIsForBrowser = true;
 }
 
 void
-ContentParent::ShutDownProcess(bool aCloseWithError)
-{
+ContentParent::ShutDownProcess(ShutDownMethod aMethod)
+{
+    // Shutting down by sending a shutdown message works differently than the
+    // other methods. We first call Shutdown() in the child. After the child is
+    // ready, it calls FinishShutdown() on us. Then we close the channel.
+    if (aMethod == SEND_SHUTDOWN_MESSAGE) {
+
+        if (SendShutdown()) {
+            mShutdownPending = true;
+        }
+
+        // If call was not successful, the channel must have been broken
+        // somehow, and we will clean up the error in ActorDestroy.
+        return;
+    }
+
     using mozilla::dom::quota::QuotaManager;
 
     if (QuotaManager* quotaManager = QuotaManager::Get()) {
         quotaManager->AbortCloseStoragesForProcess(this);
     }
 
     // If Close() fails with an error, we'll end up back in this function, but
-    // with aCloseWithError = true.  It's important that we call
+    // with aMethod = CLOSE_CHANNEL_WITH_ERROR.  It's important that we call
     // CloseWithError() in this case; see bug 895204.
 
-    if (!aCloseWithError && !mCalledClose) {
+    if (aMethod == CLOSE_CHANNEL && !mCalledClose) {
         // Close() can only be called once: It kicks off the destruction
         // sequence.
         mCalledClose = true;
         Close();
 #ifdef MOZ_NUWA_PROCESS
         // Kill Nuwa process forcibly to break its IPC channels and finalize
         // corresponding parents.
         if (IsNuwaProcess()) {
             KillHard();
         }
 #endif
     }
 
-    if (aCloseWithError && !mCalledCloseWithError) {
+    if (aMethod == CLOSE_CHANNEL_WITH_ERROR && !mCalledCloseWithError) {
         MessageChannel* channel = GetIPCChannel();
         if (channel) {
             mCalledCloseWithError = true;
             channel->CloseWithError();
         }
     }
 
     const InfallibleTArray<POfflineCacheUpdateParent*>& ocuParents =
@@ -1544,16 +1558,27 @@ ContentParent::ShutDownProcess(bool aClo
 
     // A ContentParent object might not get freed until after XPCOM shutdown has
     // shut down the cycle collector.  But by then it's too late to release any
     // CC'ed objects, so we need to null them out here, while we still can.  See
     // bug 899761.
     ShutDownMessageManager();
 }
 
+bool
+ContentParent::RecvFinishShutdown()
+{
+    // At this point, we already called ShutDownProcess once with
+    // SEND_SHUTDOWN_MESSAGE. To actually close the channel, we call
+    // ShutDownProcess again with CLOSE_CHANNEL.
+    MOZ_ASSERT(mShutdownPending);
+    ShutDownProcess(CLOSE_CHANNEL);
+    return true;
+}
+
 void
 ContentParent::ShutDownMessageManager()
 {
   if (!mMessageManager) {
     return;
   }
 
   mMessageManager->ReceiveMessage(
@@ -1744,17 +1769,30 @@ struct DelayedDeleteContentParentTask : 
 void
 ContentParent::ActorDestroy(ActorDestroyReason why)
 {
     if (mForceKillTask) {
         mForceKillTask->Cancel();
         mForceKillTask = nullptr;
     }
 
-    ShutDownMessageManager();
+    // Signal shutdown completion regardless of error state,
+    // so we can finish waiting in the xpcom-shutdown observer.
+    mShutdownComplete = true;
+
+    if (why == NormalShutdown && !mCalledClose) {
+        // If we shut down normally but haven't called Close, assume somebody
+        // else called Close on us. In that case, we still need to call
+        // ShutDownProcess below to perform other necessary clean up.
+        mCalledClose = true;
+    }
+
+    // Make sure we always clean up.
+    ShutDownProcess(why == NormalShutdown ? CLOSE_CHANNEL
+                                          : CLOSE_CHANNEL_WITH_ERROR);
 
     nsRefPtr<ContentParent> kungFuDeathGrip(this);
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
         size_t length = ArrayLength(sObserverTopics);
         for (size_t i = 0; i < length; ++i) {
             obs->RemoveObserver(static_cast<nsIObserver*>(this),
                                 sObserverTopics[i]);
@@ -1783,18 +1821,16 @@ ContentParent::ActorDestroy(ActorDestroy
         sNuwaPrefUpdates = nullptr;
     }
 #endif
 
     RecvRemoveGeolocationListener();
 
     mConsoleService = nullptr;
 
-    MarkAsDead();
-
     if (obs) {
         nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
 
         props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), mChildID);
 
         if (AbnormalShutdown == why) {
             Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT,
                                   NS_LITERAL_CSTRING("content"), 1);
@@ -1836,21 +1872,16 @@ ContentParent::ActorDestroy(ActorDestroy
             }
 #endif
         }
         obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nullptr);
     }
 
     mIdleListeners.Clear();
 
-    // If the child process was terminated due to a SIGKIL, ShutDownProcess
-    // might not have been called yet.  We must call it to ensure that our
-    // channel is closed, etc.
-    ShutDownProcess(/* closeWithError */ true);
-
     MessageLoop::current()->
         PostTask(FROM_HERE,
                  NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess));
     mSubprocess = nullptr;
 
     // IPDL rules require actors to live on past ActorDestroy, but it
     // may be that the kungFuDeathGrip above is the last reference to
     // |this|.  If so, when we go out of scope here, we're deleted and
@@ -1864,17 +1895,17 @@ ContentParent::ActorDestroy(ActorDestroy
     ContentProcessManager *cpm = ContentProcessManager::GetSingleton();
     nsTArray<ContentParentId> childIDArray =
         cpm->GetAllChildProcessById(this->ChildID());
     for(uint32_t i = 0; i < childIDArray.Length(); i++) {
         ContentParent* cp = cpm->GetContentProcessById(childIDArray[i]);
         MessageLoop::current()->PostTask(
             FROM_HERE,
             NewRunnableMethod(cp, &ContentParent::ShutDownProcess,
-                              /* closeWithError */ false));
+                              CLOSE_CHANNEL));
     }
     cpm->RemoveContentProcess(this->ChildID());
 }
 
 void
 ContentParent::NotifyTabDestroying(PBrowserParent* aTab)
 {
     // There can be more than one PBrowser for a given app process
@@ -1910,20 +1941,22 @@ ContentParent::NotifyTabDestroyed(PBrows
     if (aNotifiedDestroying) {
         --mNumDestroyingTabs;
     }
 
     // There can be more than one PBrowser for a given app process
     // because of popup windows.  When the last one closes, shut
     // us down.
     if (ManagedPBrowserParent().Length() == 1) {
+        // In the case of normal shutdown, send a shutdown message to child to
+        // allow it to perform shutdown tasks.
         MessageLoop::current()->PostTask(
             FROM_HERE,
             NewRunnableMethod(this, &ContentParent::ShutDownProcess,
-                              /* force */ false));
+                              SEND_SHUTDOWN_MESSAGE));
     }
 }
 
 jsipc::JavaScriptShared*
 ContentParent::GetCPOWManager()
 {
     if (ManagedPJavaScriptParent().Length()) {
         return static_cast<JavaScriptParent*>(ManagedPJavaScriptParent()[0]);
@@ -1961,16 +1994,18 @@ ContentParent::InitializeMembers()
     mNumDestroyingTabs = 0;
     mIsAlive = true;
     mSendPermissionUpdates = false;
     mSendDataStoreInfos = false;
     mCalledClose = false;
     mCalledCloseWithError = false;
     mCalledKillHard = false;
     mCreatedPairedMinidumps = false;
+    mShutdownPending = false;
+    mShutdownComplete = false;
 }
 
 ContentParent::ContentParent(mozIApplication* aApp,
                              ContentParent* aOpener,
                              bool aIsForBrowser,
                              bool aIsForPreallocated,
                              ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */,
                              bool aIsNuwaProcess /* = false */)
@@ -2705,17 +2740,26 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP
 ContentParent::Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const char16_t* aData)
 {
     if (!strcmp(aTopic, "xpcom-shutdown") && mSubprocess) {
-        ShutDownProcess(/* closeWithError */ false);
+        if (mShutdownPending) {
+            // Wait for shutdown to complete, so that we receive any shutdown
+            // data (e.g. telemetry) from the child before we quit.
+            while (!mShutdownComplete) {
+                NS_ProcessNextEvent(nullptr, true);
+            }
+        } else {
+            // Just close the channel if we never tried shutting down.
+            ShutDownProcess(CLOSE_CHANNEL);
+        }
         NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
     }
 
     if (!mIsAlive || !mSubprocess)
         return NS_OK;
 
     // listening for memory pressure event
     if (!strcmp(aTopic, "memory-pressure") &&
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -329,16 +329,18 @@ public:
                                        const URIParams& aDocumentURI,
                                        const bool& stickDocument,
                                        const TabId& aTabId) MOZ_OVERRIDE;
     virtual bool
     DeallocPOfflineCacheUpdateParent(POfflineCacheUpdateParent* aActor) MOZ_OVERRIDE;
 
     virtual bool RecvSetOfflinePermission(const IPC::Principal& principal) MOZ_OVERRIDE;
 
+    virtual bool RecvFinishShutdown() MOZ_OVERRIDE;
+
 protected:
     void OnChannelConnected(int32_t pid) MOZ_OVERRIDE;
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
     void OnNuwaForkTimeout();
 
     bool ShouldContinueFromReplyTimeout() MOZ_OVERRIDE;
 
 private:
@@ -429,27 +431,39 @@ private:
 
     /**
      * Mark this ContentParent as dead for the purposes of Get*().
      * This method is idempotent.
      */
     void MarkAsDead();
 
     /**
+     * How we will shut down this ContentParent and its subprocess.
+     */
+    enum ShutDownMethod {
+        // Send a shutdown message and wait for FinishShutdown call back.
+        SEND_SHUTDOWN_MESSAGE,
+        // Close the channel ourselves and let the subprocess clean up itself.
+        CLOSE_CHANNEL,
+        // Close the channel with error and let the subprocess clean up itself.
+        CLOSE_CHANNEL_WITH_ERROR,
+    };
+
+    /**
      * Exit the subprocess and vamoose.  After this call IsAlive()
      * will return false and this ContentParent will not be returned
      * by the Get*() funtions.  However, the shutdown sequence itself
      * may be asynchronous.
      *
-     * If aCloseWithError is true and this is the first call to
-     * ShutDownProcess, then we'll close our channel using CloseWithError()
+     * If aMethod is CLOSE_CHANNEL_WITH_ERROR and this is the first call
+     * to ShutDownProcess, then we'll close our channel using CloseWithError()
      * rather than vanilla Close().  CloseWithError() indicates to IPC that this
      * is an abnormal shutdown (e.g. a crash).
      */
-    void ShutDownProcess(bool aCloseWithError);
+    void ShutDownProcess(ShutDownMethod aMethod);
 
     // Perform any steps necesssary to gracefully shtudown the message
     // manager and null out mMessageManager.
     void ShutDownMessageManager();
 
     PCompositorParent*
     AllocPCompositorParent(mozilla::ipc::Transport* aTransport,
                            base::ProcessId aOtherProcess) MOZ_OVERRIDE;
@@ -793,16 +807,18 @@ private:
     bool mIsNuwaProcess;
 
     // These variables track whether we've called Close(), CloseWithError()
     // and KillHard() on our channel.
     bool mCalledClose;
     bool mCalledCloseWithError;
     bool mCalledKillHard;
     bool mCreatedPairedMinidumps;
+    bool mShutdownPending;
+    bool mShutdownComplete;
 
     friend class CrashReporterParent;
 
     nsRefPtr<nsConsoleService>  mConsoleService;
     nsConsoleService* GetConsoleService();
 
     nsDataHashtable<nsUint64HashKey, nsRefPtr<ParentIdleListener> > mIdleListeners;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -537,16 +537,23 @@ child:
      */
     async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures,
                         nsCString[] aThreadNameFilters);
     async StopProfiler();
     prio(high) sync GetProfile()
       returns (nsCString aProfile);
 
     NuwaFreeze();
+
+    /**
+     * Notify the child to shutdown. The child will in turn call FinishShutdown
+     * and let the parent close the channel.
+     */
+    async Shutdown();
+
 parent:
     /**
      * Tell the parent process a new accessible document has been created.
      * aParentDoc is the accessible document it was created in if any, and
      * aParentAcc is the id of the accessible in that document the new document
      * is a child of.
      */
     PDocAccessible(nullable PDocAccessible aParentDoc, uint64_t aParentAcc);
@@ -872,15 +879,21 @@ parent:
 
     /**
      * Sets "offline-app" permission for the principal.  Called when we hit
      * a web app with the manifest attribute in <html> and
      * offline-apps.allow_by_default is set to true.
      */
     SetOfflinePermission(Principal principal);
 
+    /**
+     * Notifies the parent to continue shutting down after the child performs
+     * its shutdown tasks.
+     */
+    async FinishShutdown();
+
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData,
                   CpowEntry[] aCpows, Principal aPrincipal);
 };
 
 }
 }