fix(cutover): track direct detached listener pid
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { spawn, execFile } from 'node:child_process';
|
import { execFile, execFileSync } from 'node:child_process';
|
||||||
import { constants } from 'node:fs';
|
import { constants } from 'node:fs';
|
||||||
import {
|
import {
|
||||||
access,
|
access,
|
||||||
@@ -159,14 +159,22 @@ export const nodeCutoverSystem: CutoverSystem = {
|
|||||||
throw new Error(`PID ${pid} did not exit after SIGTERM`);
|
throw new Error(`PID ${pid} did not exit after SIGTERM`);
|
||||||
},
|
},
|
||||||
start(argv, environment) {
|
start(argv, environment) {
|
||||||
const child = spawn(argv[0], argv.slice(1), {
|
const output = execFileSync(
|
||||||
detached: true,
|
'/usr/bin/python3',
|
||||||
stdio: 'ignore',
|
[
|
||||||
|
'-c',
|
||||||
|
'import os,sys; pid=os.fork(); print(pid, flush=True); pid and sys.exit(0); os.setsid(); os.execve(sys.argv[1], sys.argv[1:], dict(os.environ))',
|
||||||
|
...argv,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
encoding: 'utf8',
|
||||||
env: environment ?? process.env,
|
env: environment ?? process.env,
|
||||||
});
|
stdio: ['ignore', 'pipe', 'ignore'],
|
||||||
child.unref();
|
},
|
||||||
if (!child.pid) throw new Error('Failed to start command');
|
);
|
||||||
return child.pid;
|
const pid = Number(output.trim());
|
||||||
|
if (!Number.isSafeInteger(pid) || pid <= 1) throw new Error('Failed to start command');
|
||||||
|
return pid;
|
||||||
},
|
},
|
||||||
delay: (milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)),
|
delay: (milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds)),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ async function fixture(name = 'cutover'): Promise<CutoverPlan> {
|
|||||||
drillEvidence: drill,
|
drillEvidence: drill,
|
||||||
drillEvidenceSha256: createHash('sha256').update(drillContent).digest('hex'),
|
drillEvidenceSha256: createHash('sha256').update(drillContent).digest('hex'),
|
||||||
legacyPid: 41,
|
legacyPid: 41,
|
||||||
legacyCommand: '/usr/bin/true legacy',
|
legacyCommand: '/usr/local/bin/node /absolute/legacy/server.js',
|
||||||
legacyIdentity: 'legacy-start-1',
|
legacyIdentity: 'legacy-start-1',
|
||||||
legacyStart: ['/usr/bin/true', 'legacy'],
|
legacyStart: ['/usr/local/bin/node', '/absolute/legacy/server.js'],
|
||||||
stateDir: join(root, 'state'),
|
stateDir: join(root, 'state'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ describe('cutover validation and reversible orchestration', () => {
|
|||||||
expect(await readFile(statePath, 'utf8')).not.toContain(token);
|
expect(await readFile(statePath, 'utf8')).not.toContain(token);
|
||||||
expect(await rollback(plan, system)).toBe(43);
|
expect(await rollback(plan, system)).toBe(43);
|
||||||
expect(signals).toEqual([41, 42]);
|
expect(signals).toEqual([41, 42]);
|
||||||
expect(starts[1]?.argv).toEqual(['/usr/bin/true', 'legacy']);
|
expect(starts[1]?.argv).toEqual(['/usr/local/bin/node', '/absolute/legacy/server.js']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('automatically restarts legacy if the gateway fails to own authenticated 8788', async () => {
|
it('automatically restarts legacy if the gateway fails to own authenticated 8788', async () => {
|
||||||
@@ -149,7 +149,7 @@ describe('cutover validation and reversible orchestration', () => {
|
|||||||
resolve(process.cwd(), 'node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/dist/loader.mjs'),
|
resolve(process.cwd(), 'node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/dist/loader.mjs'),
|
||||||
resolve(process.cwd(), 'apps/api/src/production-gateway-cli.ts'),
|
resolve(process.cwd(), 'apps/api/src/production-gateway-cli.ts'),
|
||||||
],
|
],
|
||||||
['/usr/bin/true', 'legacy'],
|
['/usr/local/bin/node', '/absolute/legacy/server.js'],
|
||||||
]);
|
]);
|
||||||
const state = JSON.parse(await readFile(join(plan.stateDir, 'cutover-state.json'), 'utf8')) as {
|
const state = JSON.parse(await readFile(join(plan.stateDir, 'cutover-state.json'), 'utf8')) as {
|
||||||
phase: string;
|
phase: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user