shutdown_codesys: kill orphan CODESYS.exe when launcher has no tracked PID
Real bug surfaced after a VSC restart left a CODESYS.exe alive that
the new MCP server didn't spawn (state=stopped/error, this.pid=null).
The previous shutdown() early-returned at the top -- the launcher
was 'stopped' so it considered itself done -- which:
1. Left the orphan CODESYS.exe alive (couldn't run the project).
2. Made the refuse-on-duplicate guard (95a884b) block every
subsequent launch_codesys with 'CODESYS already running'.
The launcher was effectively bricked: shutdown said success but
did nothing, launch refused. Hit during today's release-pipeline
test loop.
Fix: when shutdown() is called with state=stopped/error AND this.pid
is null AND findRunningCodesysPids() returns non-empty, taskkill the
orphans before the early-return. Graceful WM_CLOSE first, then 2s
grace, then -F force-kill anything still alive.
Doesn't change the happy-path (launcher tracks its own PID, state
ready -> stopping -> stopped) -- that flow is untouched. The new
code only runs when the launcher would otherwise have been a no-op
on something the OS still has active.
This commit is contained in:
parent
a570132c9c
commit
9fb569bdb0
1 changed files with 29 additions and 1 deletions
|
|
@ -188,7 +188,35 @@ export class CodesysLauncher implements ScriptExecutor {
|
|||
|
||||
/** Graceful shutdown */
|
||||
async shutdown(): Promise<void> {
|
||||
if (this.state === 'stopped' || this.state === 'stopping') return;
|
||||
// Orphan-killing fallback: if the launcher itself has no tracked PID
|
||||
// (state stopped/error after a fresh MCP server start) but a CODESYS.exe
|
||||
// is alive on the box from a previous session, the previous early-return
|
||||
// would say "shutdown_codesys success" and do nothing. This left the
|
||||
// launcher's refuse-on-duplicate guard permanently blocking new spawns.
|
||||
// Now we taskkill any orphans we can find before the early-return so the
|
||||
// tool actually does something useful in this state.
|
||||
if (this.state === 'stopped' || this.state === 'stopping') {
|
||||
if (this.pid === null) {
|
||||
const orphans = findRunningCodesysPids();
|
||||
if (orphans.length > 0) {
|
||||
launcherLog.info(`shutdown_codesys: launcher has no tracked PID but found ${orphans.length} orphan CODESYS.exe (PIDs: ${orphans.join(', ')}). Force-killing.`);
|
||||
for (const pid of orphans) {
|
||||
try {
|
||||
execSync(`taskkill /PID ${pid}`, { timeout: 5000, stdio: 'ignore' });
|
||||
} catch { /* ignore graceful failures, force-kill below */ }
|
||||
}
|
||||
// Give them a moment to close gracefully
|
||||
await this.sleep(2_000);
|
||||
const stillAlive = findRunningCodesysPids();
|
||||
for (const pid of stillAlive) {
|
||||
try {
|
||||
execSync(`taskkill /F /PID ${pid}`, { timeout: 5000, stdio: 'ignore' });
|
||||
} catch { /* nothing else to try */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState('stopping');
|
||||
this.stopHealthMonitor();
|
||||
|
|
|
|||
Loading…
Reference in a new issue