tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mnetwerk/test/unit/test_servers.js | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtesting/xpcshell/moz-http2/moz-http2-child.js | 13+++++++------
Mtesting/xpcshell/moz-http2/moz-http2.js | 40+++++++++++++++++++++++++---------------
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);