mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
511d86e94d
A daemon keeps running the code it started with. The 1.6.1 daemon in #2335 outlived an upgrade, kept loading grammars from the install the upgrade removed and wrote every file it re-indexed as empty, while every session from the new install served itself in-process, read-only, because the old daemon held the writer lock. Daemons from before this change cannot notice an upgrade, so the launcher acts: - A launcher that finds a daemon of an older plain X.Y.Z release stops it the way `codegraph daemon` does (daemon.pid and the socket hello must agree; SIGTERM, SIGKILL only if it still answers) and starts one from its own install. It finds the daemon by its hello on the launcher's own socket, or by the project lock when it listens elsewhere: on Windows, daemons before #2278 named their pipe after the root as typed. A daemon of the same, a newer, a prerelease or an unknown version is never touched, so two installs cannot take turns stopping each other's. - The writer slot never falls free on the way. The launcher swaps writer.pid to itself (mode `handover`) before the signal, clears the old daemon's leftovers while it holds the slot, and the daemon it spawns takes the slot over (CODEGRAPH_DAEMON_HANDOVER). Sessions of the old daemon that fall back in-process find the slot held and serve reads only, instead of claiming it with the removed install's code. A daemon that does not exit gets the slot back, and a successor outwaits a racing candidate that took daemon.pid first. Validated on Windows with real npm upgrades from the published 1.6.1 and 1.6.2 to this build packed as 1.6.3, with the old session calling throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
1.6 KiB
JavaScript
37 lines
1.6 KiB
JavaScript
/**
|
|
* A daemon of an older release, listening where a current launcher does not
|
|
* look (#2335). On Windows, daemons before #2278 named their pipe after the
|
|
* project root as typed, so a newer launcher's probe never reaches them and
|
|
* only the project lock says where they are. Like a real daemon it holds the
|
|
* project's daemon and writer locks and answers the hello; on SIGTERM it lets
|
|
* go of whichever of the two locks still names it and exits, the shape of a
|
|
* real daemon's shutdown. (On Windows SIGTERM is TerminateProcess, and the
|
|
* stop clears what is left.)
|
|
*
|
|
* node older-daemon.cjs <project root> <version> <socket path>
|
|
*/
|
|
const fs = require('fs');
|
|
const net = require('net');
|
|
const path = require('path');
|
|
|
|
const [root, version, socketPath] = process.argv.slice(2);
|
|
const dir = path.join(root, '.codegraph');
|
|
const writeLock = (name, record) => fs.writeFileSync(path.join(dir, name), JSON.stringify(record) + '\n');
|
|
|
|
const server = net.createServer((socket) => {
|
|
socket.end(JSON.stringify({ codegraph: version, pid: process.pid, socketPath, protocol: 1 }) + '\n');
|
|
});
|
|
server.listen(socketPath, () => {
|
|
writeLock('writer.pid', { pid: process.pid, mode: 'daemon', startedAt: Date.now(), ready: true });
|
|
writeLock('daemon.pid', { pid: process.pid, version, socketPath, startedAt: Date.now() });
|
|
});
|
|
process.on('SIGTERM', () => {
|
|
for (const name of ['writer.pid', 'daemon.pid']) {
|
|
const file = path.join(dir, name);
|
|
try {
|
|
if (JSON.parse(fs.readFileSync(file, 'utf8')).pid === process.pid) fs.unlinkSync(file);
|
|
} catch { /* already gone */ }
|
|
}
|
|
server.close(() => process.exit(0));
|
|
});
|