import { createServer } from "node:net"; import { derivePaperclipViteHmrPort } from "../../packages/shared/src/runtime-exposure/ports.js"; export const RUNNER_E2E_EMBEDDED_POSTGRES_PORT = 54_329; export interface LoopbackPortReservation { port: number; close(): Promise; } export type OpenLoopbackPort = ( requestedPort: number, ) => Promise; async function openLoopbackPort( requestedPort: number, ): Promise { const server = createServer(); await new Promise((resolve, reject) => { server.once("error", reject); server.listen(requestedPort, "127.0.0.1", resolve); }); const address = server.address(); if (!address || typeof address === "string") { server.close(); throw new Error("Failed to reserve a loopback port"); } return { port: address.port, close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())), ), }; } export function runnerE2EServerPortConflictsWithDatabase( serverPort: number, databasePort = RUNNER_E2E_EMBEDDED_POSTGRES_PORT, ) { return ( serverPort === databasePort || derivePaperclipViteHmrPort(serverPort) === databasePort ); } function isAddressInUse(error: unknown) { return (error as NodeJS.ErrnoException | undefined)?.code === "EADDRINUSE"; } export async function reserveRunnerE2EServerPort( options: { databasePort?: number; maxAttempts?: number; openPort?: OpenLoopbackPort; } = {}, ) { const databasePort = options.databasePort ?? RUNNER_E2E_EMBEDDED_POSTGRES_PORT; const maxAttempts = options.maxAttempts ?? 32; const openPort = options.openPort ?? openLoopbackPort; for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { const serverReservation = await openPort(0); let hmrReservation: LoopbackPortReservation | undefined; try { if ( runnerE2EServerPortConflictsWithDatabase( serverReservation.port, databasePort, ) ) { continue; } const hmrPort = derivePaperclipViteHmrPort(serverReservation.port); try { hmrReservation = await openPort(hmrPort); } catch (error) { if (isAddressInUse(error)) continue; throw error; } return serverReservation.port; } finally { await hmrReservation?.close(); await serverReservation.close(); } } throw new Error( `Failed to reserve a conflict-free Paperclip/Vite port pair after ${maxAttempts} attempts`, ); } /** Hold an OS-assigned database port until the isolated server is ready to spawn. * Using ephemeral allocation avoids concurrent cells scanning the same default * Postgres fallback range. The child cannot inherit this socket, so an unrelated * process can still claim it in the short close-to-spawn interval. */ export async function reserveRunnerE2EDatabasePort( serverPort: number, options: { maxAttempts?: number; openPort?: OpenLoopbackPort } = {}, ): Promise { const openPort = options.openPort ?? openLoopbackPort; const maxAttempts = options.maxAttempts ?? 32; for (let attempt = 0; attempt < maxAttempts; attempt += 1) { const reservation = await openPort(0); if ( !runnerE2EServerPortConflictsWithDatabase(serverPort, reservation.port) ) { return reservation; } await reservation.close(); } throw new Error( `Failed to reserve an isolated database port after ${maxAttempts} attempts`, ); }