diff --git a/dom/tests/browser/perfmetrics/browser.ini b/dom/tests/browser/perfmetrics/browser.ini --- a/dom/tests/browser/perfmetrics/browser.ini +++ b/dom/tests/browser/perfmetrics/browser.ini @@ -1,12 +1,19 @@ [DEFAULT] -prefs = dom.performance.enable_scheduler_timing=true +prefs = + dom.performance.enable_scheduler_timing=true + dom.performance.children_results_ipc_timeout=500 + support-files = dummy.html ping_worker.html ping_worker2.html ping_worker.js setinterval.html settimeout.html + unresponsive.html [browser_test_performance_metrics.js] skip-if = verify + +[browser_test_unresponsive.js] +skip-if = verify diff --git a/dom/tests/browser/perfmetrics/browser_test_unresponsive.js b/dom/tests/browser/perfmetrics/browser_test_unresponsive.js new file mode 100644 --- /dev/null +++ b/dom/tests/browser/perfmetrics/browser_test_unresponsive.js @@ -0,0 +1,31 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ROOT_URL = "http://example.com/browser/dom/tests/browser/perfmetrics"; +const PAGE_URL = ROOT_URL + "/unresponsive.html"; + +add_task(async function test() { + // dom.performance.enable_scheduler_timing is set to true in browser.ini + waitForExplicitFinish(); + + await BrowserTestUtils.withNewTab({ gBrowser, url: PAGE_URL }, + async function(browser) { + let dataBack = 0; + let tabId = gBrowser.selectedBrowser.outerWindowID; + + function exploreResults(data, filterByWindowId) { + for (let entry of data) { + if (entry.windowId == tabId && entry.host != "about:blank") { + dataBack += 1; + } + } + } + let results = await ChromeUtils.requestPerformanceMetrics(); + exploreResults(results); + Assert.ok(dataBack == 0); + }); + +}); diff --git a/dom/tests/browser/perfmetrics/unresponsive.html b/dom/tests/browser/perfmetrics/unresponsive.html new file mode 100644 --- /dev/null +++ b/dom/tests/browser/perfmetrics/unresponsive.html @@ -0,0 +1,21 @@ + + + + + + + +

An unresponsive page

+ + diff --git a/modules/libpref/init/StaticPrefList.h b/modules/libpref/init/StaticPrefList.h --- a/modules/libpref/init/StaticPrefList.h +++ b/modules/libpref/init/StaticPrefList.h @@ -209,10 +209,16 @@ "dom.performance.enable_scheduler_timing", dom_performance_enable_scheduler_timing, RelaxedAtomicBool, true ) +VARCACHE_PREF( + "dom.performance.children_results_ipc_timeout", + dom_performance_children_results_ipc_timeout, + uint32_t, 1000 +) + // If true. then the service worker interception and the ServiceWorkerManager // will live in the parent process. This only takes effect on browser start. // Note, this is not currently safe to use for normal browsing yet. PREF("dom.serviceWorkers.parent_intercept", bool, false) diff --git a/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h --- a/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h +++ b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.h @@ -4,10 +4,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef PerformanceMetricsCollector_h #define PerformanceMetricsCollector_h +#include "nsIObserver.h" +#include "nsITimer.h" #include "nsID.h" #include "mozilla/dom/ChromeUtilsBinding.h" // defines PerformanceInfoDictionary #include "mozilla/dom/DOMTypes.h" // defines PerformanceInfo namespace mozilla { @@ -15,10 +17,27 @@ namespace dom { class Promise; } class PerformanceMetricsCollector; +class AggregatedResults; + +class IPCTimeout final: public nsIObserver +{ +public: + NS_DECL_NSIOBSERVER + NS_DECL_ISUPPORTS + static IPCTimeout* CreateInstance(AggregatedResults* aResults); + void Cancel(); + +private: + IPCTimeout(AggregatedResults* aResults, uint32_t aDelay); + ~IPCTimeout(); + + nsCOMPtr mTimer; + AggregatedResults* mResults; +}; // AggregatedResults receives PerformanceInfo results that are collected // via IPDL from all content processes and the main process. They // are converted into an array of PerformanceInfoDictionary dictionaries (webidl) // @@ -36,12 +55,14 @@ dom::Promise* aPromise); ~AggregatedResults() = default; void AppendResult(const nsTArray& aMetrics); void SetNumResultsRequired(uint32_t aNumResultsRequired); void Abort(nsresult aReason); + void ResolveNow(); private: + RefPtr mIPCTimeout; RefPtr mPromise; uint32_t mPendingResults; FallibleTArray mData; // AggregatedResults keeps a reference on the collector diff --git a/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp --- a/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp +++ b/toolkit/components/perfmonitoring/PerformanceMetricsCollector.cpp @@ -6,10 +6,11 @@ #include "nsThreadUtils.h" #include "mozilla/Logging.h" #include "mozilla/PerformanceUtils.h" #include "mozilla/PerformanceMetricsCollector.h" +#include "mozilla/StaticPrefs.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WorkerDebugger.h" #include "mozilla/dom/WorkerDebuggerManager.h" @@ -23,10 +24,66 @@ #define LOG(args) MOZ_LOG(sPerfLog, mozilla::LogLevel::Debug, args) namespace mozilla { // +// class IPCTimeout +// +NS_IMPL_ISUPPORTS(IPCTimeout, nsIObserver) + + +// static +IPCTimeout* +IPCTimeout::CreateInstance(AggregatedResults* aResults) +{ + MOZ_ASSERT(aResults); + uint32_t delay = StaticPrefs::dom_performance_children_results_ipc_timeout(); + if (delay == 0) { + return nullptr; + } + return new IPCTimeout(aResults, delay); +} + + +IPCTimeout::IPCTimeout(AggregatedResults* aResults, uint32_t aDelay): + mResults(aResults) +{ + MOZ_ASSERT(aResults); + MOZ_ASSERT(aDelay > 0); + mozilla::DebugOnly rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), + this, + aDelay, + nsITimer::TYPE_ONE_SHOT); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + LOG(("IPCTimeout timer created")); +} + +IPCTimeout::~IPCTimeout() +{ + Cancel(); +} + +void +IPCTimeout::Cancel() +{ + if (mTimer) { + LOG(("IPCTimeout timer canceled")); + mTimer->Cancel(); + mTimer = nullptr; + } +} + +NS_IMETHODIMP +IPCTimeout::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) +{ + MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0); + LOG(("IPCTimeout timer triggered")); + mResults->ResolveNow(); + return NS_OK; +} + +// // class AggregatedResults // AggregatedResults::AggregatedResults(nsID aUUID, PerformanceMetricsCollector* aCollector, dom::Promise* aPromise) @@ -35,23 +92,38 @@ , mCollector(aCollector) , mUUID(aUUID) { MOZ_ASSERT(aCollector); MOZ_ASSERT(aPromise); + mIPCTimeout = IPCTimeout::CreateInstance(this); } void AggregatedResults::Abort(nsresult aReason) { MOZ_ASSERT(mPromise); MOZ_ASSERT(NS_FAILED(aReason)); + if (mIPCTimeout) { + mIPCTimeout->Cancel(); + mIPCTimeout = nullptr; + } mPromise->MaybeReject(aReason); mPromise = nullptr; mPendingResults = 0; } void +AggregatedResults::ResolveNow() +{ + MOZ_ASSERT(mPromise); + LOG(("[%s] Early resolve", nsIDToCString(mUUID).get())); + mPromise->MaybeResolve(mData); + mIPCTimeout = nullptr; + mCollector->ForgetAggregatedResults(mUUID); +} + +void AggregatedResults::AppendResult(const nsTArray& aMetrics) { if (!mPromise) { // A previous call failed and the promise was already rejected return; @@ -95,10 +167,14 @@ if (mPendingResults) { return; } LOG(("[%s] All data collected, resolving promise", nsIDToCString(mUUID).get())); + if (mIPCTimeout) { + mIPCTimeout->Cancel(); + mIPCTimeout = nullptr; + } mPromise->MaybeResolve(mData); mCollector->ForgetAggregatedResults(mUUID); } void @@ -195,11 +271,17 @@ // static nsresult PerformanceMetricsCollector::DataReceived(const nsID& aUUID, const nsTArray& aMetrics) { - MOZ_ASSERT(gInstance); + // If some content process were unresponsive on shutdown, we may get called + // here with late data received from children - so instead of asserting + // that gInstance is available, we just return. + if (!gInstance) { + LOG(("[%s] gInstance is gone", nsIDToCString(aUUID).get())); + return NS_OK; + } MOZ_ASSERT(XRE_IsParentProcess()); return gInstance->DataReceivedInternal(aUUID, aMetrics); } nsresult @@ -207,10 +289,11 @@ const nsTArray& aMetrics) { MOZ_ASSERT(gInstance == this); UniquePtr* results = mAggregatedResults.GetValue(aUUID); if (!results) { + LOG(("[%s] UUID is gone from mAggregatedResults", nsIDToCString(aUUID).get())); return NS_ERROR_FAILURE; } LOG(("[%s] Received one PerformanceInfo array", nsIDToCString(aUUID).get())); AggregatedResults* aggregatedResults = results->get();