Bug 1103036 - Allow ContentChild to perform tasks during shutdown; r=billm
--- 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);
};
}
}