fix(resolution): stop "Resolving refs" wedge on theme-vendoring repos; add exclude config + index watchdogs (#999) (#1009)
Three fixes for a repo that commits a large JS/TS theme/SDK (Metronic under static/, ~1,600 tracked files): 1. A SECOND "Resolving refs" quadratic that #915 didn't cover. #915 capped import-name collisions; this caps method-name collisions (init/update/render re-declared on every widget), which flow through matchMethodCall Strategy 3 and findBestMatch instead. New AMBIGUOUS_NAME_CEILING (default 500, env CODEGRAPH_AMBIGUOUS_NAME_CEILING): above it the fuzzy strategies decline rather than score K candidates — no proximity score can pick the one true target among thousands anyway. Resolving drops from O(K^2) to linear in refs (e.g. 900-file synthetic: 28.7s -> 3.4s), edge counts unchanged, and the cap never fires on normal repos (max real method-collision ~40). 2. A new `exclude` array in codegraph.json keeps git-TRACKED paths out of the index, which .gitignore can't do (enumeration is `git ls-files`). Mirrors the existing includeIgnored plumbing across the git, sync, and non-git-walk paths. 3. `index`/`init` now install the #850 liveness + #277 ppid watchdogs (which were serve-only), so a wedged or orphaned indexer self-terminates instead of pinning a core. The --liftoff-only relaunch's spawnSync can't forward signals, so killing the parent shim used to orphan the worker. Tests: ubiquitous-name ceiling, exclude (incl. tracked-file exclusion on git + non-git), orphan self-termination (POSIX), and ppid-parser units. Shared the ppid parsers out of mcp/index.ts into mcp/ppid-watchdog.ts. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d3179f5004
commit
45d3293c6a
+1
-36
@@ -50,18 +50,11 @@ import {
|
||||
import { connectWithHello, runLocalHandshakeProxy } from './proxy';
|
||||
import { getDaemonSocketPath } from './daemon-paths';
|
||||
import { getTelemetry } from '../telemetry';
|
||||
import { supervisionLostReason } from './ppid-watchdog';
|
||||
import { supervisionLostReason, parsePpidPollMs, parseHostPpid } from './ppid-watchdog';
|
||||
import { installMainThreadWatchdog, WatchdogHandle } from './liveness-watchdog';
|
||||
import { treatStdinFailureAsShutdown } from './stdin-teardown';
|
||||
import { HOST_PPID_ENV } from '../extraction/wasm-runtime-flags';
|
||||
|
||||
/**
|
||||
* How often to poll `process.ppid` to detect parent process death (see #277).
|
||||
* 5s is a deliberate trade-off: the failure mode being guarded against is rare
|
||||
* (parent SIGKILL'd), and longer poll = less wakeup overhead while idle.
|
||||
*/
|
||||
const DEFAULT_PPID_POLL_MS = 5000;
|
||||
|
||||
/**
|
||||
* Env var that marks a process as the *detached daemon* itself (set by
|
||||
* {@link spawnDetachedDaemon} when it re-invokes the CLI). Without it a
|
||||
@@ -94,34 +87,6 @@ const TAKEOVER_RETRY_DELAY_MS = 100;
|
||||
const DAEMON_CONNECT_MAX_RETRIES = 240;
|
||||
const DAEMON_CONNECT_RETRY_DELAY_MS = 25;
|
||||
|
||||
/**
|
||||
* Resolve the PPID watchdog poll interval from an env override. A value of
|
||||
* `0` disables the watchdog entirely (escape hatch for embedded scenarios
|
||||
* where the parent legitimately re-parents the server on purpose). Anything
|
||||
* non-numeric or negative falls back to the default.
|
||||
*/
|
||||
function parsePpidPollMs(raw: string | undefined): number {
|
||||
if (raw === undefined || raw === '') return DEFAULT_PPID_POLL_MS;
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isFinite(parsed)) return DEFAULT_PPID_POLL_MS;
|
||||
if (parsed < 0) return DEFAULT_PPID_POLL_MS;
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the host PID propagated across the `--liftoff-only` re-exec
|
||||
* ({@link HOST_PPID_ENV}). Returns a positive integer PID, or null when
|
||||
* unset/invalid — the direct-launch path, where the watchdog falls back to
|
||||
* `process.ppid` divergence. PIDs of 0/1 are rejected (0 = unknown, 1 = init,
|
||||
* i.e. already orphaned), so the watchdog doesn't latch onto init.
|
||||
*/
|
||||
function parseHostPpid(raw: string | undefined): number | null {
|
||||
if (raw === undefined || raw === '') return null;
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isInteger(parsed) || parsed <= 1) return null;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/** Whether `CODEGRAPH_NO_DAEMON` was set to a truthy value. */
|
||||
function daemonOptOutSet(): boolean {
|
||||
const raw = process.env.CODEGRAPH_NO_DAEMON;
|
||||
|
||||
@@ -61,3 +61,35 @@ export function supervisionLostReason(state: SupervisionState): string | null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Default PPID poll cadence (ms). Shared by the MCP server and CLI commands. */
|
||||
export const DEFAULT_PPID_POLL_MS = 5000;
|
||||
|
||||
/**
|
||||
* Resolve the PPID watchdog poll interval from an env override
|
||||
* (`CODEGRAPH_PPID_POLL_MS`). A value of `0` disables the watchdog entirely
|
||||
* (escape hatch for embedded scenarios where the parent legitimately re-parents
|
||||
* the process on purpose). Anything non-numeric or negative falls back to the
|
||||
* default.
|
||||
*/
|
||||
export function parsePpidPollMs(raw: string | undefined): number {
|
||||
if (raw === undefined || raw === '') return DEFAULT_PPID_POLL_MS;
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isFinite(parsed)) return DEFAULT_PPID_POLL_MS;
|
||||
if (parsed < 0) return DEFAULT_PPID_POLL_MS;
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the host PID propagated across the `--liftoff-only` re-exec
|
||||
* (`CODEGRAPH_HOST_PPID`). Returns a positive integer PID, or null when
|
||||
* unset/invalid — the direct-launch path, where the watchdog falls back to
|
||||
* `process.ppid` divergence. PIDs of 0/1 are rejected (0 = unknown, 1 = init,
|
||||
* i.e. already orphaned), so the watchdog doesn't latch onto init.
|
||||
*/
|
||||
export function parseHostPpid(raw: string | undefined): number | null {
|
||||
if (raw === undefined || raw === '') return null;
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isInteger(parsed) || parsed <= 1) return null;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user