Rework windows start and stop methods so that command windows dont appear. Stop with signal since we cannot signint.
This commit is contained in:
@@ -6,6 +6,43 @@ import { promisify } from 'util';
|
||||
const execAsync = promisify(exec);
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// How long a stopping job gets to run its graceful shutdown (KeyboardInterrupt
|
||||
// -> on_error -> final DB write) before we assume it is hung and force-kill it.
|
||||
const GRACEFUL_STOP_TIMEOUT_MS = 60_000;
|
||||
const BACKSTOP_POLL_MS = 2_000;
|
||||
|
||||
const isProcessAlive = (pid: number): boolean => {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
// EPERM means it exists but belongs to someone else, which still counts.
|
||||
return e?.code === 'EPERM';
|
||||
}
|
||||
};
|
||||
|
||||
// Windows: the trainer's stop watcher sees the `stop` flag and raises SIGINT
|
||||
// inside the process, so the graceful path runs without us sending anything.
|
||||
// This backstop only exists for a job that is too hung to notice the flag.
|
||||
// Poll (rather than one long timer) so we stop watching the moment the pid
|
||||
// dies and never touch a recycled pid.
|
||||
const scheduleForceKillBackstop = (pid: number, jobID: string) => {
|
||||
const startedAt = Date.now();
|
||||
const timer = setInterval(() => {
|
||||
if (!isProcessAlive(pid)) {
|
||||
clearInterval(timer);
|
||||
return;
|
||||
}
|
||||
if (Date.now() - startedAt < GRACEFUL_STOP_TIMEOUT_MS) return;
|
||||
clearInterval(timer);
|
||||
console.warn(`Job ${jobID} (pid ${pid}) still alive ${GRACEFUL_STOP_TIMEOUT_MS / 1000}s after stop request, force killing`);
|
||||
execAsync(`taskkill /PID ${pid} /T /F`, { windowsHide: true }).catch(() => {
|
||||
// already gone
|
||||
});
|
||||
}, BACKSTOP_POLL_MS);
|
||||
timer.unref?.();
|
||||
};
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: { jobID: string } }) {
|
||||
const { jobID } = await params;
|
||||
|
||||
@@ -30,9 +67,12 @@ export async function GET(request: NextRequest, { params }: { params: { jobID: s
|
||||
console.log(`Attempting to stop job ${jobID} with PID ${job.pid}`);
|
||||
try {
|
||||
if (isWindows) {
|
||||
// Windows doesn't support SIGINT for arbitrary processes.
|
||||
// Use taskkill with /T (tree) to send a CTRL+C-like termination.
|
||||
await execAsync(`taskkill /PID ${job.pid} /T /F`, { windowsHide: true });
|
||||
// No external SIGINT possible on Windows (the job runs under pythonw
|
||||
// with no console), and none is needed: the `stop` flag written above
|
||||
// is the signal. The trainer's stop watcher polls it and raises
|
||||
// SIGINT in-process, which runs the same KeyboardInterrupt shutdown
|
||||
// as Linux -- final DB write and the closing log lines.
|
||||
scheduleForceKillBackstop(job.pid, jobID);
|
||||
} else {
|
||||
process.kill(job.pid, 'SIGINT');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user