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:
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(