mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 22:21:20 +00:00
refactor(daemon): share process ownership guard for safe shutdown
This commit is contained in:
@@ -12,7 +12,7 @@ import * as http from 'http';
|
||||
import { CopilotDaemonStatus } from './types';
|
||||
import { CopilotConfig } from '../config/unified-config-types';
|
||||
import { getCopilotDir, getCopilotApiBinPath } from './copilot-package-manager';
|
||||
import { verifyCopilotDaemonOwnership } from './daemon-process-ownership';
|
||||
import { verifyProcessOwnership } from '../cursor/daemon-process-ownership';
|
||||
|
||||
const DAEMON_HEALTH_MARKER = 'server running';
|
||||
|
||||
@@ -258,7 +258,11 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
|
||||
}
|
||||
|
||||
try {
|
||||
const ownership = verifyCopilotDaemonOwnership(pid);
|
||||
const ownership = verifyProcessOwnership(pid, (commandLine) => {
|
||||
const lower = commandLine.toLowerCase();
|
||||
// copilot-api is launched as `... copilot-api start --port <n>`
|
||||
return lower.includes('copilot-api') && lower.includes(' start');
|
||||
});
|
||||
if (ownership === 'not-running') {
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export type DaemonOwnershipStatus = 'owned' | 'not-owned' | 'not-running' | 'unknown';
|
||||
|
||||
function getProcessCommandLine(pid: number): string | null {
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
// /proc cmdline uses null separators between arguments.
|
||||
return fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8').replace(/\0/g, ' ').trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
try {
|
||||
const result = spawnSync('ps', ['-p', String(pid), '-o', 'command='], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (result.error || result.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
return result.stdout.trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const command = `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}" | Select-Object -ExpandProperty CommandLine)`;
|
||||
const shells = ['powershell.exe', 'powershell', 'pwsh.exe', 'pwsh'];
|
||||
for (const shell of shells) {
|
||||
try {
|
||||
const result = spawnSync(shell, ['-NoProfile', '-Command', command], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (result.error) {
|
||||
continue;
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
return result.stdout.trim();
|
||||
} catch {
|
||||
// Try next shell candidate
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function verifyCopilotDaemonOwnership(pid: number): DaemonOwnershipStatus {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
} catch (err) {
|
||||
const error = err as NodeJS.ErrnoException;
|
||||
if (error.code === 'ESRCH') {
|
||||
return 'not-running';
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const commandLine = getProcessCommandLine(pid);
|
||||
if (!commandLine) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
// copilot-api is launched as `... copilot-api start --port <n>`
|
||||
const lower = commandLine.toLowerCase();
|
||||
const looksLikeCopilotDaemon = lower.includes('copilot-api') && lower.includes(' start');
|
||||
|
||||
return looksLikeCopilotDaemon ? 'owned' : 'not-owned';
|
||||
}
|
||||
@@ -52,7 +52,10 @@ function getProcessCommandLine(pid: number): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function verifyDaemonOwnership(pid: number): DaemonOwnershipStatus {
|
||||
export function verifyProcessOwnership(
|
||||
pid: number,
|
||||
ownershipMatcher: (commandLine: string) => boolean
|
||||
): DaemonOwnershipStatus {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
} catch (err) {
|
||||
@@ -68,8 +71,13 @@ export function verifyDaemonOwnership(pid: number): DaemonOwnershipStatus {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const looksLikeCursorDaemon =
|
||||
commandLine.includes('--ccs-daemon') && commandLine.includes('cursor-daemon-entry');
|
||||
return ownershipMatcher(commandLine) ? 'owned' : 'not-owned';
|
||||
}
|
||||
|
||||
return looksLikeCursorDaemon ? 'owned' : 'not-owned';
|
||||
export function verifyDaemonOwnership(pid: number): DaemonOwnershipStatus {
|
||||
return verifyProcessOwnership(
|
||||
pid,
|
||||
(commandLine) =>
|
||||
commandLine.includes('--ccs-daemon') && commandLine.includes('cursor-daemon-entry')
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user