Tolerate concurrent PM2 profile cleanup

This commit is contained in:
2026-07-25 14:13:24 +00:00
parent 62b6e20c46
commit 43ae163038
2 changed files with 20 additions and 2 deletions
@@ -273,6 +273,9 @@ const parseInstallOptions = (
const buildProcessName = (profileName: string, role: 'api' | 'daemon'): string => const buildProcessName = (profileName: string, role: 'api' | 'daemon'): string =>
`sammo:${profileName}:${role === 'api' ? 'game-api' : 'turn-daemon'}`; `sammo:${profileName}:${role === 'api' ? 'game-api' : 'turn-daemon'}`;
const isMissingProcessError = (error: unknown): boolean =>
error instanceof Error && /process or namespace not found/i.test(error.message);
export const buildProcessDefinitions = ( export const buildProcessDefinitions = (
profile: GatewayProfileRecord, profile: GatewayProfileRecord,
config: GatewayProcessConfig config: GatewayProcessConfig
@@ -980,7 +983,9 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
try { try {
await this.processManager.delete(name); await this.processManager.delete(name);
} catch (error) { } catch (error) {
failures.push(`${name}: ${error instanceof Error ? error.message : String(error)}`); if (!isMissingProcessError(error)) {
failures.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
}
} }
} }
if (failures.length > 0) { if (failures.length > 0) {
@@ -40,7 +40,8 @@ const createHarness = (
operation: GatewayOperationRecord, operation: GatewayOperationRecord,
failStart = false, failStart = false,
failStop = false, failStop = false,
processesPresent = true processesPresent = true,
missingOnDelete = false
) => { ) => {
let nextOperation: GatewayOperationRecord | null = operation; let nextOperation: GatewayOperationRecord | null = operation;
const statuses: string[] = []; const statuses: string[] = [];
@@ -103,6 +104,9 @@ const createHarness = (
}, },
delete: async (name) => { delete: async (name) => {
deleted.push(name); deleted.push(name);
if (missingOnDelete) {
throw new Error('process or namespace not found');
}
if (failStop) { if (failStop) {
throw new Error('pm2 delete failed'); throw new Error('pm2 delete failed');
} }
@@ -168,6 +172,15 @@ describe('GatewayOrchestrator first-class operations', () => {
expect(harness.completions).toEqual(['SUCCEEDED']); expect(harness.completions).toEqual(['SUCCEEDED']);
}); });
it('treats a process removed concurrently as a successful stop', async () => {
const harness = createHarness(buildOperation('STOP'), false, false, true, true);
await harness.orchestrator.runOperationsNow();
expect(harness.deleted).toEqual(['sammo:che:2:game-api', 'sammo:che:2:turn-daemon']);
expect(harness.completions).toEqual(['SUCCEEDED']);
});
it('records a failed start instead of reporting a false success', async () => { it('records a failed start instead of reporting a false success', async () => {
const harness = createHarness(buildOperation('START'), true); const harness = createHarness(buildOperation('START'), true);