fix: improve Windows managed OpenCode shutdown and launch behavior (#844)
* fix: windows shutdown and restart orphaned cleanup * fix: launch managed OpenCode directly on Windows Unwrap OpenCode wrappers to launch directly on Windows, improving shutdown reliability and avoid orphans. * fix: restore desktopNotifyEnabled in health snapshot --------- Signed-off-by: Dr. Zed <142888684+DocterZed@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
a3d73a8b67
commit
636dcd5314
+112
-31
@@ -1784,6 +1784,99 @@ function isProcessRunning(pid) {
|
||||
}
|
||||
}
|
||||
|
||||
function waitForProcessExit(pid, timeoutMs) {
|
||||
if (!Number.isFinite(pid) || pid <= 0) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
return new Promise((resolve) => {
|
||||
const check = () => {
|
||||
if (!isProcessRunning(pid)) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
if (Date.now() >= deadline) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
setTimeout(check, 150);
|
||||
};
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
async function terminateProcessTree(pid, options = {}) {
|
||||
if (!Number.isFinite(pid) || pid <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const gracefulTimeoutMs = Number.isFinite(options.gracefulTimeoutMs) && options.gracefulTimeoutMs >= 0
|
||||
? Math.trunc(options.gracefulTimeoutMs)
|
||||
: 2500;
|
||||
const forceTimeoutMs = Number.isFinite(options.forceTimeoutMs) && options.forceTimeoutMs >= 0
|
||||
? Math.trunc(options.forceTimeoutMs)
|
||||
: 3000;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
spawnSync('taskkill', ['/pid', String(pid), '/t'], {
|
||||
stdio: 'ignore',
|
||||
timeout: 3000,
|
||||
windowsHide: true,
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
|
||||
if (await waitForProcessExit(pid, gracefulTimeoutMs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
spawnSync('taskkill', ['/pid', String(pid), '/f', '/t'], {
|
||||
stdio: 'ignore',
|
||||
timeout: 5000,
|
||||
windowsHide: true,
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
|
||||
return waitForProcessExit(pid, forceTimeoutMs);
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
} catch {
|
||||
}
|
||||
|
||||
if (await waitForProcessExit(pid, gracefulTimeoutMs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
} catch {
|
||||
}
|
||||
|
||||
return waitForProcessExit(pid, forceTimeoutMs);
|
||||
}
|
||||
|
||||
async function stopInstanceProcess(pid, options = {}) {
|
||||
if (!Number.isFinite(pid) || pid <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const shutdownWaitMs = Number.isFinite(options.shutdownWaitMs) && options.shutdownWaitMs >= 0
|
||||
? Math.trunc(options.shutdownWaitMs)
|
||||
: 5000;
|
||||
|
||||
if (await waitForProcessExit(pid, shutdownWaitMs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return terminateProcessTree(pid, options);
|
||||
}
|
||||
|
||||
async function requestServerShutdown(port) {
|
||||
if (!Number.isFinite(port) || port <= 0) return false;
|
||||
const controller = new AbortController();
|
||||
@@ -3060,18 +3153,11 @@ const commands = {
|
||||
const requested = await requestServerShutdown(options.port);
|
||||
|
||||
if (Number.isFinite(systemInfo.pid) && isProcessRunning(systemInfo.pid)) {
|
||||
try {
|
||||
process.kill(systemInfo.pid, 'SIGTERM');
|
||||
let attempts = 0;
|
||||
while (isProcessRunning(systemInfo.pid) && attempts < 20) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
attempts++;
|
||||
}
|
||||
if (isProcessRunning(systemInfo.pid)) {
|
||||
process.kill(systemInfo.pid, 'SIGKILL');
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
await stopInstanceProcess(systemInfo.pid, {
|
||||
shutdownWaitMs: requested ? 5000 : 0,
|
||||
gracefulTimeoutMs: 2500,
|
||||
forceTimeoutMs: 3000,
|
||||
}).catch(() => false);
|
||||
}
|
||||
|
||||
const stopped = await isPortAvailable(options.port);
|
||||
@@ -3142,15 +3228,14 @@ const commands = {
|
||||
}
|
||||
stopSpin?.start(`Stopping OpenChamber on port ${instance.port}...`);
|
||||
try {
|
||||
await requestServerShutdown(instance.port);
|
||||
process.kill(instance.pid, 'SIGTERM');
|
||||
let attempts = 0;
|
||||
while (isProcessRunning(instance.pid) && attempts < 20) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
attempts++;
|
||||
}
|
||||
if (isProcessRunning(instance.pid)) {
|
||||
process.kill(instance.pid, 'SIGKILL');
|
||||
const requested = await requestServerShutdown(instance.port);
|
||||
const stopped = await stopInstanceProcess(instance.pid, {
|
||||
shutdownWaitMs: requested ? 5000 : 0,
|
||||
gracefulTimeoutMs: 2500,
|
||||
forceTimeoutMs: 3000,
|
||||
});
|
||||
if (!stopped && isProcessRunning(instance.pid)) {
|
||||
throw new Error(`Timed out stopping pid ${instance.pid}`);
|
||||
}
|
||||
removePidFile(instance.pidFilePath);
|
||||
removeInstanceFile(instance.instanceFilePath);
|
||||
@@ -4667,16 +4752,12 @@ const commands = {
|
||||
updateSpin?.message(`Stopping ${runningInstances.length} running instance(s)...`);
|
||||
for (const instance of runningInstances) {
|
||||
try {
|
||||
await requestServerShutdown(instance.port);
|
||||
process.kill(instance.pid, 'SIGTERM');
|
||||
let attempts = 0;
|
||||
while (isProcessRunning(instance.pid) && attempts < 20) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
attempts++;
|
||||
}
|
||||
if (isProcessRunning(instance.pid)) {
|
||||
process.kill(instance.pid, 'SIGKILL');
|
||||
}
|
||||
const requested = await requestServerShutdown(instance.port);
|
||||
await stopInstanceProcess(instance.pid, {
|
||||
shutdownWaitMs: requested ? 5000 : 0,
|
||||
gracefulTimeoutMs: 2500,
|
||||
forceTimeoutMs: 3000,
|
||||
});
|
||||
removePidFile(instance.pidFilePath);
|
||||
} catch {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user