401 lines
10 KiB
JavaScript
401 lines
10 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
const TEST_PAGE =
|
|
"http://mochi.test:8888/browser/dom/ipc/tests/file_dummy.html";
|
|
|
|
async function waitForGCBegin() {
|
|
var waitTopic = "garbage-collector-begin";
|
|
var observer = {};
|
|
|
|
info("Waiting for " + waitTopic);
|
|
// This fixes a ReferenceError for Date, it's weird.
|
|
ok(Date.now(), "Date.now()");
|
|
var when = await new Promise(resolve => {
|
|
observer.observe = function () {
|
|
resolve(Date.now());
|
|
};
|
|
|
|
Services.obs.addObserver(observer, waitTopic);
|
|
});
|
|
|
|
Services.obs.removeObserver(observer, waitTopic);
|
|
|
|
// This delay attempts to make the time stamps unique.
|
|
do {
|
|
var now = Date.now();
|
|
} while (when + 5 > now);
|
|
|
|
return when;
|
|
}
|
|
|
|
async function waitForGCEnd() {
|
|
var waitTopic = "garbage-collector-end";
|
|
var observer = {};
|
|
|
|
info("Waiting for " + waitTopic);
|
|
// This fixes a ReferenceError for Date, it's weird.
|
|
ok(Date.now(), "Date.now()");
|
|
let when = await new Promise(resolve => {
|
|
observer.observe = function () {
|
|
resolve(Date.now());
|
|
};
|
|
|
|
Services.obs.addObserver(observer, waitTopic);
|
|
});
|
|
|
|
Services.obs.removeObserver(observer, waitTopic);
|
|
|
|
do {
|
|
var now = Date.now();
|
|
} while (when + 5 > now);
|
|
|
|
return when;
|
|
}
|
|
|
|
function getProcessID() {
|
|
return Services.appinfo.processID;
|
|
}
|
|
|
|
async function resolveInOrder(promisesAndStates) {
|
|
var order = [];
|
|
var promises = [];
|
|
|
|
for (let p of promisesAndStates) {
|
|
promises.push(
|
|
p.promise.then(when => {
|
|
info(`Tab: ${p.tab} did ${p.state}`);
|
|
order.push({ tab: p.tab, state: p.state, when });
|
|
})
|
|
);
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
|
|
return order;
|
|
}
|
|
|
|
// Check that the list of events returned by resolveInOrder are in a
|
|
// sensible order.
|
|
function checkOneAtATime(events) {
|
|
var cur = null;
|
|
var lastWhen = null;
|
|
|
|
info("Checking order of events");
|
|
for (const e of events) {
|
|
ok(e.state === "begin" || e.state === "end", "event.state is good");
|
|
Assert.notStrictEqual(e.tab, undefined, "event.tab exists");
|
|
|
|
if (lastWhen) {
|
|
// We need these in sorted order so that the other checks here make
|
|
// sense.
|
|
Assert.lessOrEqual(
|
|
lastWhen,
|
|
e.when,
|
|
`Unsorted events, last: ${lastWhen}, this: ${e.when}`
|
|
);
|
|
}
|
|
lastWhen = e.when;
|
|
|
|
if (e.state === "begin") {
|
|
is(cur, null, `GC can begin on tab ${e.tab}`);
|
|
cur = e.tab;
|
|
} else {
|
|
is(e.tab, cur, `GC can end on tab ${e.tab}`);
|
|
cur = null;
|
|
}
|
|
}
|
|
|
|
is(cur, null, "No GC left running");
|
|
}
|
|
|
|
function checkAllCompleted(events, expectTabsCompleted) {
|
|
var tabsCompleted = events.filter(e => e.state === "end").map(e => e.tab);
|
|
|
|
for (var t of expectTabsCompleted) {
|
|
ok(tabsCompleted.includes(t), `Tab ${t} did a GC`);
|
|
}
|
|
}
|
|
|
|
async function setupTabsAndOneForForeground(num_tabs) {
|
|
++num_tabs;
|
|
var pids = [];
|
|
|
|
const parent_pid = getProcessID();
|
|
info("Parent process PID is " + parent_pid);
|
|
|
|
const tabs = await Promise.all(
|
|
Array(num_tabs)
|
|
.fill()
|
|
.map(_ => {
|
|
return BrowserTestUtils.openNewForegroundTab({
|
|
gBrowser,
|
|
opening: TEST_PAGE,
|
|
forceNewProcess: true,
|
|
});
|
|
})
|
|
);
|
|
|
|
for (const [i, tab] of Object.entries(tabs)) {
|
|
const tab_pid = await SpecialPowers.spawn(
|
|
tab.linkedBrowser,
|
|
[],
|
|
getProcessID
|
|
);
|
|
|
|
info(`Tab ${i} pid is ${tab_pid}`);
|
|
isnot(parent_pid, tab_pid, `Tab ${i} is in content process`);
|
|
ok(!pids.includes(tab_pid), `Tab ${i} is in a distinct process`);
|
|
|
|
pids.push(tab_pid);
|
|
}
|
|
|
|
// Since calling openNewForegroundTab several times in a row doesn't update
|
|
// process priorities correctly, we need to explicitly switch tabs.
|
|
for (let tab of tabs) {
|
|
await BrowserTestUtils.switchTab(gBrowser, tab);
|
|
}
|
|
|
|
return tabs;
|
|
}
|
|
|
|
function doContentRunNextCollectionTimer() {
|
|
content.windowUtils.pokeGC("PAGE_HIDE");
|
|
content.windowUtils.runNextCollectorTimer("PAGE_HIDE");
|
|
}
|
|
|
|
function startNextCollection(
|
|
tab,
|
|
tab_num,
|
|
waits,
|
|
fn = doContentRunNextCollectionTimer
|
|
) {
|
|
var browser = tab.linkedBrowser;
|
|
|
|
// Finish any currently running GC.
|
|
SpecialPowers.spawn(browser, [], () => {
|
|
SpecialPowers.Cu.getJSTestingFunctions().finishgc();
|
|
});
|
|
|
|
if (tab.selected) {
|
|
// One isn't expected to use the return value with foreground tab!
|
|
return {};
|
|
}
|
|
|
|
var waitBegin = SpecialPowers.spawn(browser, [], waitForGCBegin);
|
|
var waitEnd = SpecialPowers.spawn(browser, [], waitForGCEnd);
|
|
waits.push({ promise: waitBegin, tab: tab_num, state: "begin" });
|
|
waits.push({ promise: waitEnd, tab: tab_num, state: "end" });
|
|
|
|
SpecialPowers.spawn(browser, [], fn);
|
|
|
|
// Return these so that the abort GC test can wait for the begin.
|
|
return { waitBegin, waitEnd };
|
|
}
|
|
|
|
add_task(async function gcOneAtATime() {
|
|
SpecialPowers.pushPrefEnv({
|
|
set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]],
|
|
});
|
|
|
|
const num_tabs = 12;
|
|
var tabs = await setupTabsAndOneForForeground(num_tabs);
|
|
|
|
info("Tabs ready, Asking for GCs");
|
|
var waits = [];
|
|
for (var i = 0; i < num_tabs; i++) {
|
|
startNextCollection(tabs[i], i, waits);
|
|
}
|
|
|
|
let order = await resolveInOrder(waits);
|
|
// We need these in the order they actually occurred, so far that's how
|
|
// they're returned, but we'll sort them to be sure.
|
|
order.sort((e1, e2) => e1.when - e2.when);
|
|
checkOneAtATime(order);
|
|
checkAllCompleted(
|
|
order,
|
|
Array.from({ length: num_tabs }, (_, n) => n)
|
|
);
|
|
|
|
for (var tab of tabs) {
|
|
BrowserTestUtils.removeTab(tab);
|
|
}
|
|
|
|
SpecialPowers.popPrefEnv();
|
|
});
|
|
|
|
add_task(async function gcAbort() {
|
|
SpecialPowers.pushPrefEnv({
|
|
set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]],
|
|
});
|
|
|
|
const num_tabs = 2;
|
|
var tabs = await setupTabsAndOneForForeground(num_tabs);
|
|
|
|
info("Tabs ready, Asking for GCs");
|
|
var waits = [];
|
|
|
|
var tab0Waits = startNextCollection(tabs[0], 0, waits, () => {
|
|
SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
|
|
});
|
|
await tab0Waits.waitBegin;
|
|
|
|
// Tab 0 has started a GC. Now we schedule a GC in tab one. It must not
|
|
// begin yet (but we don't check that, gcOneAtATime is assumed to check
|
|
// this.
|
|
startNextCollection(tabs[1], 1, waits);
|
|
|
|
// Request that tab 0 abort, this test checks that tab 1 can now begin.
|
|
SpecialPowers.spawn(tabs[0].linkedBrowser, [], () => {
|
|
SpecialPowers.Cu.getJSTestingFunctions().abortgc();
|
|
});
|
|
|
|
let order = await resolveInOrder(waits);
|
|
// We need these in the order they actually occurred, so far that's how
|
|
// they're returned, but we'll sort them to be sure.
|
|
order.sort((e1, e2) => e1.when - e2.when);
|
|
checkOneAtATime(order);
|
|
checkAllCompleted(
|
|
order,
|
|
Array.from({ length: num_tabs }, (_, n) => n)
|
|
);
|
|
|
|
for (var tab of tabs) {
|
|
BrowserTestUtils.removeTab(tab);
|
|
}
|
|
|
|
SpecialPowers.popPrefEnv();
|
|
});
|
|
|
|
add_task(async function gcJSInitiatedDuring() {
|
|
SpecialPowers.pushPrefEnv({
|
|
set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]],
|
|
});
|
|
|
|
const num_tabs = 3;
|
|
var tabs = await setupTabsAndOneForForeground(num_tabs);
|
|
|
|
let completed;
|
|
let retry = 0;
|
|
const maxRetries = 3;
|
|
do {
|
|
completed = true;
|
|
retry++;
|
|
|
|
info("Tabs ready, Asking for GCs");
|
|
var waits = [];
|
|
|
|
// Start a GC on tab 0 to consume the scheduler's "token".
|
|
var tab0Waits = startNextCollection(tabs[0], 0, waits, () => {
|
|
SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
|
|
});
|
|
await tab0Waits.waitBegin;
|
|
info("GC on tab 0 has begun");
|
|
|
|
// Request a GC in tab 1, this will be blocked by the ongoing GC in tab 0.
|
|
var tab1Waits = startNextCollection(tabs[1], 1, waits);
|
|
|
|
// Force a GC to start in tab 1. This won't wait for tab 0.
|
|
SpecialPowers.spawn(tabs[1].linkedBrowser, [], () => {
|
|
SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
|
|
});
|
|
|
|
await tab1Waits.waitBegin;
|
|
info("GC on tab 1 has begun");
|
|
|
|
// The GC in tab 0 should still be running.
|
|
var state = await SpecialPowers.spawn(tabs[0].linkedBrowser, [], () => {
|
|
return SpecialPowers.Cu.getJSTestingFunctions().gcstate();
|
|
});
|
|
|
|
info("State of Tab 0 GC is " + state);
|
|
if (state == "NotActive") {
|
|
// The GC finished earlier than expected. The test must be retried.
|
|
info("GC finished in tab 0");
|
|
completed = false;
|
|
}
|
|
|
|
// Let the GCs complete, verify that a GC in a 3rd tab can acquire a token.
|
|
startNextCollection(tabs[2], 2, waits);
|
|
|
|
let order = await resolveInOrder(waits);
|
|
info("All GCs finished");
|
|
checkAllCompleted(
|
|
order,
|
|
Array.from({ length: num_tabs }, (_, n) => n)
|
|
);
|
|
} while (!completed && retry <= maxRetries);
|
|
|
|
ok(completed, "GC in tab 0 finished sooner than expected");
|
|
|
|
for (var tab of tabs) {
|
|
BrowserTestUtils.removeTab(tab);
|
|
}
|
|
|
|
SpecialPowers.popPrefEnv();
|
|
});
|
|
|
|
add_task(async function gcJSInitiatedBefore() {
|
|
SpecialPowers.pushPrefEnv({
|
|
set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]],
|
|
});
|
|
|
|
const num_tabs = 8;
|
|
var tabs = await setupTabsAndOneForForeground(num_tabs);
|
|
|
|
let completed;
|
|
let retry = 0;
|
|
const maxRetries = 3;
|
|
do {
|
|
completed = true;
|
|
retry++;
|
|
|
|
info("Tabs ready");
|
|
var waits = [];
|
|
|
|
// Start a GC on tab 0 to consume the scheduler's first "token".
|
|
info("Force a JS-initiated GC in tab 0");
|
|
var tab0Waits = startNextCollection(tabs[0], 0, waits, () => {
|
|
SpecialPowers.Cu.getJSTestingFunctions().gcslice(1);
|
|
});
|
|
await tab0Waits.waitBegin;
|
|
|
|
info("Request GCs in remaining tabs");
|
|
for (var i = 1; i < num_tabs; i++) {
|
|
startNextCollection(tabs[i], i, waits);
|
|
}
|
|
|
|
// The GC in tab 0 should still be running.
|
|
var state = await SpecialPowers.spawn(tabs[0].linkedBrowser, [], () => {
|
|
return SpecialPowers.Cu.getJSTestingFunctions().gcstate();
|
|
});
|
|
|
|
info("State is " + state);
|
|
if (state == "NotActive") {
|
|
// The GC finished earlier than expected. The test must be retried.
|
|
info("GC finished in tab 0");
|
|
completed = false;
|
|
}
|
|
|
|
let order = await resolveInOrder(waits);
|
|
// We need these in the order they actually occurred, so far that's how
|
|
// they're returned, but we'll sort them to be sure.
|
|
order.sort((e1, e2) => e1.when - e2.when);
|
|
checkOneAtATime(order);
|
|
checkAllCompleted(
|
|
order,
|
|
Array.from({ length: num_tabs }, (_, n) => n)
|
|
);
|
|
} while (!completed && retry <= maxRetries);
|
|
|
|
ok(completed, "GC in tab 0 finished sooner than expected");
|
|
|
|
for (var tab of tabs) {
|
|
BrowserTestUtils.removeTab(tab);
|
|
}
|
|
|
|
SpecialPowers.popPrefEnv();
|
|
});
|