commit 913290f972673b925027966f702bb80e49dc8b36
parent 0920a6e1c993e555490d97282b470130a1a10287
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date: Wed, 22 Oct 2025 08:01:11 +0000
Bug 1995053 - Make each call to NodeServer.execute have a unique ID r=necko-reviewers,jesup
Previously forked servers only remembered the resolve and reject handlers
for the most recent call to /execute.
That caused `forked process without handler` messages when you forgot
to await a command, but also meant you couldn't have two async executions
pending at the same time.
This patch generates a unique id for each call to /execute that the
child script passes back with the result allowing us to identify the
correct message handler.
Differential Revision: https://phabricator.services.mozilla.com/D269096
Diffstat:
3 files changed, 90 insertions(+), 21 deletions(-)
diff --git a/netwerk/test/unit/test_servers.js b/netwerk/test/unit/test_servers.js
@@ -305,3 +305,61 @@ add_task(async function test_proxy_with_redirects() {
await proxy.stop();
}
});
+
+add_task(async function test_async_event() {
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.execute(`new Promise(r => setTimeout(r, 500))`);
+
+ await server.stop();
+});
+
+add_task(async function test_async_state_management() {
+ let server = new NodeHTTP2Server();
+ await server.start();
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ await server.execute(`global.asyncResults = [];`);
+
+ await server.execute(`
+ global.asyncCounter = 0;
+ global.performAsyncOperation = function(delay, value) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ global.asyncCounter++;
+ global.asyncResults.push({ counter: global.asyncCounter, value });
+ resolve({ counter: global.asyncCounter, value });
+ }, delay);
+ });
+ };
+ `);
+
+ let op1 = server.execute(`performAsyncOperation(100, "first")`);
+ let op2 = server.execute(`performAsyncOperation(50, "second")`);
+
+ let result1 = await op1;
+ let result2 = await op2;
+ // This ran after 100 ms, so it comes in second
+ equal(result1.counter, 2);
+ equal(result1.value, "first");
+
+ // this rand after 50 ms, so it comes in first.
+ equal(result2.counter, 1);
+ equal(result2.value, "second");
+
+ let results = await server.execute(`global.asyncResults`);
+ equal(results.length, 2);
+ equal(results[0].value, "second");
+ equal(results[1].value, "first");
+
+ let counter = await server.execute(`global.asyncCounter`);
+ equal(counter, 2);
+
+ await server.stop();
+});
diff --git a/testing/xpcshell/moz-http2/moz-http2-child.js b/testing/xpcshell/moz-http2/moz-http2-child.js
@@ -4,8 +4,8 @@
/* eslint-env node */
-function sendBackResponse(evalResult, e) {
- const output = { result: evalResult, error: "", errorStack: "" };
+function sendBackResponse(messageId, evalResult, e) {
+ const output = { result: evalResult, error: "", errorStack: "", messageId };
if (e) {
output.error = e.toString();
output.errorStack = e.stack;
@@ -15,19 +15,20 @@ function sendBackResponse(evalResult, e) {
process.on("message", msg => {
const code = msg.code;
+ const messageId = msg.messageId;
let evalResult = null;
try {
// eslint-disable-next-line no-eval
evalResult = eval(code);
if (evalResult instanceof Promise) {
evalResult
- .then(x => sendBackResponse(x))
- .catch(e => sendBackResponse(undefined, e));
+ .then(x => sendBackResponse(messageId, x))
+ .catch(e => sendBackResponse(messageId, undefined, e));
return;
}
} catch (e) {
- sendBackResponse(undefined, e);
+ sendBackResponse(messageId, undefined, e);
return;
}
- sendBackResponse(evalResult);
+ sendBackResponse(messageId, evalResult);
});
diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js
@@ -1486,10 +1486,10 @@ let httpServer = http.createServer((req, res) => {
return;
}
+ let messageId = makeid(6);
new Promise((resolve, reject) => {
- forked.resolve = resolve;
- forked.reject = reject;
- forked.send({ code });
+ forked.messageHandlers[messageId] = { resolve, reject };
+ forked.send({ code, messageId });
})
.then(x => sendBackResponse(x))
.catch(e => computeAndSendBackResponse(undefined, e));
@@ -1538,11 +1538,13 @@ function forkProcess() {
function forkProcessInternal(forked) {
let id = makeid(6);
forked.errors = "";
+ forked.messageHandlers = {};
globalObjects[id] = forked;
forked.on("message", msg => {
- if (forked.resolve) {
- forked.resolve(msg);
- forked.resolve = null;
+ if (msg.messageId && forked.messageHandlers[msg.messageId]) {
+ let handler = forked.messageHandlers[msg.messageId];
+ delete forked.messageHandlers[msg.messageId];
+ handler.resolve(msg);
} else {
console.log(
`forked process without handler sent: ${JSON.stringify(msg)}`
@@ -1561,22 +1563,30 @@ function forkProcessInternal(forked) {
return;
}
- if (!forked.reject) {
+ let errorMsg = `child process exit closing code: ${code} signal: ${signal}`;
+ if (forked.errors != "") {
+ errorMsg = forked.errors;
+ forked.errors = "";
+ }
+
+ // Handle /kill/ case where forked.reject is set
+ if (forked.reject) {
+ forked.reject(errorMsg);
+ forked.reject = null;
+ forked.resolve = null;
+ }
+
+ if (Object.keys(forked.messageHandlers).length === 0) {
console.log(
`child process ${id} closing code: ${code} signal: ${signal}`
);
return;
}
- if (forked.errors != "") {
- forked.reject(forked.errors);
- forked.errors = "";
- forked.reject = null;
- return;
+ for (let messageId in forked.messageHandlers) {
+ forked.messageHandlers[messageId].reject(errorMsg);
}
-
- forked.reject(`child process exit closing code: ${code} signal: ${signal}`);
- forked.reject = null;
+ forked.messageHandlers = {};
};
forked.on("error", exitFunction);