// A gentle stop for s3d on Windows. child.kill() there is TerminateProcess: // s3d dies wherever it is, and one stopped in the middle of an upload had // lost buffered files whose database rows still said "waiting". s3d shuts // down cleanly on an interrupt, so this attaches a short PowerShell to s3d's // own (hidden) console and sends Ctrl+Break there. // // Ctrl+Break, not Ctrl+C: Windows disables Ctrl+C for a process started in // a new process group, and Go reads both as os.Interrupt. A spawned console // program gets a console of its own (Node passes CREATE_NO_WINDOW), so the // event reaches s3d and not Pithos. It ends the PowerShell too, which is // why only "could not attach" (2) and "could not send" (3) count as failure. import { execFile } from 'node:child_process'; const SCRIPT = (pid) => [ "Add-Type -Name K -Namespace PithosCtrl -MemberDefinition '", '[DllImport("kernel32.dll")] public static extern bool FreeConsole();', '[DllImport("kernel32.dll")] public static extern bool AttachConsole(uint pid);', '[DllImport("kernel32.dll")] public static extern bool GenerateConsoleCtrlEvent(uint ev, uint group);', "';", '[PithosCtrl.K]::FreeConsole() | Out-Null;', `if (-not [PithosCtrl.K]::AttachConsole(${Number(pid) >>> 0})) { exit 2 };`, 'if ([PithosCtrl.K]::GenerateConsoleCtrlEvent(1, 0)) { exit 0 } else { exit 3 }', ].join(' '); // Resolves true when Ctrl+Break was sent to the console of `pid`. export function sendBreak(pid) { if (process.platform !== 'win32' || !pid) return Promise.resolve(false); return new Promise((resolve) => { execFile('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', SCRIPT(pid)], { windowsHide: true, timeout: 20_000 }, (err) => resolve(!err || (err.code !== 2 && err.code !== 3 && !err.killed))); }); }