taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e430a6f837ccf424a5a2b822811c67f5613904cf
parent 41c5d3e781ccf1151a130a94a34a3252641a86bf
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 31 Oct 2026 11:14:12 +0100

never reserve the same TCP port twice

Diffstat:
Mpackages/taler-harness/src/integrationtests/wallet-web-ui-test-support.ts | 51+++++++++++++++++++++++++++++++++++++++------------
1 file changed, 39 insertions(+), 12 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/wallet-web-ui-test-support.ts b/packages/taler-harness/src/integrationtests/wallet-web-ui-test-support.ts @@ -73,20 +73,47 @@ export function walletWebUiArtifactPath( return path.join(walletWebUiDirectory, "dist/harness", variant, ...entries); } +/** + * Ports already handed out by this worker. A reservation only binds port 0 and + * closes the socket again, so without this the kernel is free to return the + * same ephemeral port to the very next call. A test that reserves two ports + * before starting anything then runs both of its services on one port: the + * second one dies with EADDRINUSE while its URL still answers, which looks like + * a product bug instead of a port clash. + */ +const reservedTcpPorts = new Set<number>(); + export async function reserveTcpPort(): Promise<number> { - const server = net.createServer(); - await new Promise<void>((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", resolve); - }); - const address = server.address(); - if (!address || typeof address === "string") { - throw Error("could not reserve a TCP port"); + // Candidates that turned out to be already reserved stay bound until we have + // a fresh one, so that the kernel has to pick a different port for the next + // attempt. + const heldServers: net.Server[] = []; + try { + for (let attempt = 0; attempt < 64; attempt++) { + const server = net.createServer(); + await new Promise<void>((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + heldServers.push(server); + const address = server.address(); + if (!address || typeof address === "string") { + throw Error("could not reserve a TCP port"); + } + if (reservedTcpPorts.has(address.port)) { + continue; + } + reservedTcpPorts.add(address.port); + return address.port; + } + throw Error("could not reserve an unused TCP port"); + } finally { + for (const server of heldServers) { + await new Promise<void>((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ); + } } - await new Promise<void>((resolve, reject) => - server.close((error) => (error ? reject(error) : resolve())), - ); - return address.port; } export async function waitForPwa(