Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os == 'linux'
- Manifest: ipc/glue/test/browser/browser.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
// This test is a simple check that terminated processes are cleaned
// up. It's currently Linux only, and only launches one content
// process (no other types, no use of multiple processes to try to
// provoke race conditions, etc.), then causes it to exit and waits
// until it no longer exists.
//
// If the process continues to exist, including as a zombie, the test
// will time out. The test repeatedly polls the process status and
// logs it, so it will be obvious if it was a regular hang or a
// process management failure.
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
const libc = ctypes.open("libc.so.6");
const { O_PATH, O_CLOEXEC } = ChromeUtils.getLibcConstants();
const c_open = libc.declare(
"open",
ctypes.default_abi,
ctypes.int,
ctypes.char.ptr,
ctypes.int
);
const c_close = libc.declare(
"close",
ctypes.default_abi,
ctypes.int,
ctypes.int
);
async function millisleep(ms) {
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, ms));
}
// pidfd is a fd for the /proc/{pid} subdir, to avoid the small
// possibility of spurious test failures if the pid is reused.
async function getProcStatus(pidfd) {
let bytes;
try {
bytes = await IOUtils.read(`/proc/self/fd/${pidfd}/stat`, {
maxBytes: 4096,
});
} catch (ex) {
if (!DOMException.isInstance(ex)) {
throw ex;
}
// Hopefully this is ENOENT
return "dead";
}
let rparen = bytes.lastIndexOf(41 /* ')' */);
if (rparen < 0 || rparen >= bytes.length - 2) {
throw new Error(`bad proc_pid_stat line`);
}
let state = String.fromCodePoint(bytes[rparen + 2]);
if (state == "Z") {
return "zombie";
}
return "running";
}
add_task(async function () {
let pidfd = -1;
let pid = -1;
try {
// 1. Launch content process
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "https://example.com/",
forceNewProcess: true,
},
async function (browser) {
pid = browser.frameLoader.remoteTab.osPid;
if (pid <= 0) {
throw new Error(`bad pid: ${pid}`);
}
ok(true, `got content process pid ${pid}`);
// 2. Open procfs dir
pidfd = c_open(`/proc/${pid}`, O_PATH | O_CLOEXEC);
if (pidfd < 0) {
throw new Error(`failed to open pidfd: errno ${ctypes.errno}`);
}
ok(true, `opened pidfd ${pidfd}`);
Assert.equal(await getProcStatus(pidfd), "running");
// 3. Shut down content process
}
);
// 4. Check the process status until it's gone
while (true) {
let state = await getProcStatus(pidfd);
dump(`process status: ${state}\n`);
if (state == "dead") {
break;
}
await millisleep(333); // 3 Hz
}
ok(true, "process eventually exited");
} finally {
if (pidfd >= 0) {
let rv = c_close(pidfd);
Assert.greaterOrEqual(rv, 0, "closed pidfd");
}
}
});