Bug 1491816 - deal with unresponsive content processes in ChromeUtils.requestPerformanceMetrics() - r=baku

Adds a timout that will resolve the promise to return even if we did not get an answer from
all children.

MozReview-Commit-ID: FFLwAUkkYos

Differential Revision: https://phabricator.services.mozilla.com/D7265

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Tarek Ziadé 2018-10-11 09:40:23 +00:00
Родитель 1c08e312af
Коммит 06ff704ff3
6 изменённых файлов: 171 добавлений и 2 удалений

Просмотреть файл

@ -1,5 +1,8 @@
[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
@ -7,6 +10,10 @@ support-files =
ping_worker.js
setinterval.html
settimeout.html
unresponsive.html
[browser_test_performance_metrics.js]
skip-if = verify
[browser_test_unresponsive.js]
skip-if = verify

Просмотреть файл

@ -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);
});
});

Просмотреть файл

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<script type="text/javascript">
function fn() {
let start = Date.now();
while (Date.now() - start < 5000)
; // do nothing
setTimeout(fn, 0);
}
setTimeout(fn, 10);
</script>
</head>
<body>
<h1>An unresponsive page</h1>
</body>
</html>

Просмотреть файл

@ -211,6 +211,12 @@ VARCACHE_PREF(
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.

Просмотреть файл

@ -8,6 +8,7 @@
#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"
@ -24,6 +25,62 @@ static mozilla::LazyLogModule sPerfLog("PerformanceMetricsCollector");
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<nsresult> 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
//
@ -37,6 +94,7 @@ AggregatedResults::AggregatedResults(nsID aUUID,
{
MOZ_ASSERT(aCollector);
MOZ_ASSERT(aPromise);
mIPCTimeout = IPCTimeout::CreateInstance(this);
}
void
@ -44,11 +102,25 @@ 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<dom::PerformanceInfo>& aMetrics)
{
@ -97,6 +169,10 @@ AggregatedResults::AppendResult(const nsTArray<dom::PerformanceInfo>& aMetrics)
}
LOG(("[%s] All data collected, resolving promise", nsIDToCString(mUUID).get()));
if (mIPCTimeout) {
mIPCTimeout->Cancel();
mIPCTimeout = nullptr;
}
mPromise->MaybeResolve(mData);
mCollector->ForgetAggregatedResults(mUUID);
}
@ -197,7 +273,13 @@ nsresult
PerformanceMetricsCollector::DataReceived(const nsID& aUUID,
const nsTArray<PerformanceInfo>& 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);
}
@ -209,6 +291,7 @@ PerformanceMetricsCollector::DataReceivedInternal(const nsID& aUUID,
MOZ_ASSERT(gInstance == this);
UniquePtr<AggregatedResults>* results = mAggregatedResults.GetValue(aUUID);
if (!results) {
LOG(("[%s] UUID is gone from mAggregatedResults", nsIDToCString(aUUID).get()));
return NS_ERROR_FAILURE;
}

Просмотреть файл

@ -6,6 +6,8 @@
#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
@ -17,6 +19,23 @@ namespace dom {
}
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<nsITimer> mTimer;
AggregatedResults* mResults;
};
// AggregatedResults receives PerformanceInfo results that are collected
// via IPDL from all content processes and the main process. They
@ -38,8 +57,10 @@ public:
void AppendResult(const nsTArray<dom::PerformanceInfo>& aMetrics);
void SetNumResultsRequired(uint32_t aNumResultsRequired);
void Abort(nsresult aReason);
void ResolveNow();
private:
RefPtr<IPCTimeout> mIPCTimeout;
RefPtr<dom::Promise> mPromise;
uint32_t mPendingResults;
FallibleTArray<dom::PerformanceInfoDictionary> mData;