[pending-judge] fix(r4/polish1): server-assigned port — no freePort release-rebind race
The e2e child now calls app.listen(0) and reports the OS-assigned port on stdout (PORT:<n>); the parent reads it. The probe-then-release helper is deleted, so no other process can take the port between probe and bind.
This commit is contained in:
1 file changed
+11
-20
@@ -19,7 +19,6 @@
|
|||||||
* assertion compares against that live 404 baseline on the same instance.
|
* assertion compares against that live 404 baseline on the same instance.
|
||||||
*/
|
*/
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const net = require('net');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@@ -30,21 +29,9 @@ let child;
|
|||||||
let PORT;
|
let PORT;
|
||||||
let JWT;
|
let JWT;
|
||||||
|
|
||||||
// OS-assigned ephemeral port (judge polish: not a random 30000-49999 guess).
|
|
||||||
function freePort() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const s = net.createServer();
|
|
||||||
s.listen(0, '127.0.0.1', () => {
|
|
||||||
const p = s.address().port;
|
|
||||||
s.close(() => resolve(p));
|
|
||||||
});
|
|
||||||
s.on('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boot the REAL app via its REAL entry contract (createApp + listen). The
|
// Boot the REAL app via its REAL entry contract (createApp + listen). The
|
||||||
// child mints a JWT through the same authManager singleton the app wires.
|
// child mints a JWT through the same authManager singleton the app wires.
|
||||||
function boot(port) {
|
function boot() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const child = spawn(process.execPath, ['-e', `
|
const child = spawn(process.execPath, ['-e', `
|
||||||
process.env.DATA_DIR = ${JSON.stringify(DATA_DIR)};
|
process.env.DATA_DIR = ${JSON.stringify(DATA_DIR)};
|
||||||
@@ -60,8 +47,9 @@ function boot(port) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
jwt = '';
|
jwt = '';
|
||||||
}
|
}
|
||||||
const server = app.listen(${port}, '127.0.0.1', () => {
|
const server = app.listen(0, '127.0.0.1', () => {
|
||||||
console.log('JWT:' + jwt);
|
console.log('JWT:' + jwt);
|
||||||
|
console.log('PORT:' + server.address().port);
|
||||||
console.log('READY');
|
console.log('READY');
|
||||||
});
|
});
|
||||||
process.on('SIGTERM', () => server.close(() => process.exit(0)));
|
process.on('SIGTERM', () => server.close(() => process.exit(0)));
|
||||||
@@ -80,11 +68,13 @@ function boot(port) {
|
|||||||
}, 30000);
|
}, 30000);
|
||||||
child.stdout.on('data', (d) => {
|
child.stdout.on('data', (d) => {
|
||||||
buf += String(d);
|
buf += String(d);
|
||||||
const m = buf.match(/JWT:(\S*)/);
|
const mj = buf.match(/JWT:(\S*)/);
|
||||||
if (!settled && m && buf.includes('READY')) {
|
const mp = buf.match(/PORT:(\d+)/);
|
||||||
|
if (!settled && mj && mp && buf.includes('READY')) {
|
||||||
settled = true;
|
settled = true;
|
||||||
clearTimeout(to);
|
clearTimeout(to);
|
||||||
child.jwt = m[1];
|
child.jwt = mj[1];
|
||||||
|
child.port = Number(mp[1]);
|
||||||
resolve(child);
|
resolve(child);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -117,9 +107,10 @@ async function expectMounted(p, allowed) {
|
|||||||
|
|
||||||
describe('composition-root e2e (real createApp on ephemeral port)', () => {
|
describe('composition-root e2e (real createApp on ephemeral port)', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
PORT = await freePort();
|
child = await boot(); // child calls listen(0); no probe/rebind race
|
||||||
child = await boot(PORT);
|
PORT = child.port;
|
||||||
JWT = child.jwt || '';
|
JWT = child.jwt || '';
|
||||||
|
if (!Number.isInteger(PORT) || PORT <= 0) throw new Error('child did not report a bound port');
|
||||||
const u = await get('/api/v1/__definitely_not_mounted__');
|
const u = await get('/api/v1/__definitely_not_mounted__');
|
||||||
unknownStatus = u.status;
|
unknownStatus = u.status;
|
||||||
}, 45000);
|
}, 45000);
|
||||||
|
|||||||
Reference in new issue
Block a user