Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

  • This test gets skipped with pattern: verify OR asan OR tsan OR debug OR os != 'linux' OR bits != 64
  • Manifest: dom/ipc/tests/browser.toml
/* 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 =
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);
info("Tabs ready, Asking for GCs");
var waits = [];
// Start a GC on tab 0 to consume the scheduler's "token". Zeal mode 10
// will cause it to run in many slices.
var tab0Waits = startNextCollection(tabs[0], 0, waits, () => {
if (SpecialPowers.Cu.getJSTestingFunctions().gczeal) {
SpecialPowers.Cu.getJSTestingFunctions().gczeal(10);
}
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);
isnot(state, "NotActive", "GC is active in tab 0");
// 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)
);
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);
info("Tabs ready");
var waits = [];
// Start a GC on tab 0 to consume the scheduler's first "token". Zeal mode 10
// will cause it to run in many slices.
info("Force a JS-initiated GC in tab 0");
var tab0Waits = startNextCollection(tabs[0], 0, waits, () => {
if (SpecialPowers.Cu.getJSTestingFunctions().gczeal) {
SpecialPowers.Cu.getJSTestingFunctions().gczeal(10);
}
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);
isnot(state, "NotActive", "GC is active in tab 0");
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();
});