Skip to content

Post-index CPU busy-loop: per-batch known-path stat sweeps and per-event git check-ignore spawnSync keep the daemon hot after indexing #23

Description

@thenotoriousllama

Field report (nectar 0.1.3, Windows, ~85k-file repo)

After the index walk finishes (memory plateaus then declines to ~670MB), CPU keeps climbing past 900s with flat/declining memory: a busy-loop, not indexing. /health stays unresponsive throughout.

Root-cause investigation (current main, 2026-07-04): two named suspects

  1. Per-batch known-path sweeps scale with store size, not batch size. Every registration cycle batch recomputes knownPaths() and missingPaths() (src/registration/service.ts:303-312 snapshot comment), and missingPaths performs an existsOnDisk stat for EVERY known path. With ~85k rows in the store, a single settled watch event (one file save) triggers ~85k synchronous statSync calls. Any trickle of events (editor autosave, build output, log write) re-runs the sweep, keeping the CPU pegged indefinitely while the event loop starves. This matches the flat-memory, climbing-CPU signature exactly.

  2. Shared-ignore cache misses spawn git synchronously per path. createSharedIgnore's gitignore leg resolves a snapshot miss via runGitCheckIgnore = spawnSync("git", ["check-ignore", ...]) per path (src/registration/ignore.ts:187-199, wired at :282-291). Any watch-event storm over paths not in the ls-files snapshot (untracked build artifacts, temp files) spawns a synchronous git process per observation; on Windows each spawn is tens to hundreds of ms of blocked loop.

Compounding: the fs.watch recursive intake on Windows can deliver high event volumes on large trees, and each settled event feeds suspect 1; each raw observation can feed suspect 2 before debouncing (the ignore test runs pre-debounce, src/registration/fs-watch.ts:165-173).

Ask

  • Make the known/missing-path reconciliation incremental (indexes/dirty sets) instead of per-batch full sweeps; a single-path settle must be O(1)-ish, never O(store).
  • Batch or cache negative check-ignore resolutions (and/or refresh the snapshot instead of per-path spawnSync); never spawn a process per watch event.
  • Add a soak test: sustained single-file churn on a large synthetic store must keep CPU bounded and /health responsive.

Related: the /health event-loop starvation during indexing is filed separately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions