|
2 | 2 |
|
3 | 3 | All notable changes to jcodemunch-mcp are documented here. |
4 | 4 |
|
| 5 | +## [1.108.172] - 2026-07-25 - idle index-cache TTL (opt-in) + process presence registry |
| 6 | + |
| 7 | +### Added |
| 8 | + |
| 9 | +- **`get_session_stats` now reports how many jCodeMunch processes share this |
| 10 | + index store.** Follow-up to |
| 11 | + [#375](https://github.com/jgravelle/jcodemunch-mcp/issues/375), where a user |
| 12 | + found **25+ live instances** on one box against a single ~140MB store, ages to |
| 13 | + 1d15h, mostly stdio servers their MCP client never reaped at session end. |
| 14 | + Reaping the day-plus-old ones freed **~17 GB of RAM**. Nobody could see it. |
| 15 | + |
| 16 | + We cannot reap another program's children. New `storage/process_registry.py` |
| 17 | + makes the sprawl visible instead: each server writes one small |
| 18 | + `~/.code-index/_processes/<pid>.json` (pid, client, transport, version, start |
| 19 | + time — no repos, paths, or queries) and removes it on exit. Readers filter by |
| 20 | + PID liveness and prune what they find dead, so a hard kill leaves nothing |
| 21 | + behind and there is no daemon keeping the registry honest. Surfaced as a |
| 22 | + `processes` block; a hint naming the memory lever appears only past 5 live |
| 23 | + processes, so a normal setup stays quiet. Wrapped so a registry failure can |
| 24 | + never fail a stats call. Disclosed in the README background-behavior section. |
| 25 | + |
| 26 | +- **`JCODEMUNCH_INDEX_CACHE_TTL`: opt-in idle eviction for the in-memory index |
| 27 | + cache.** A hydrated `CodeIndex` is large and was released only under LRU |
| 28 | + pressure (`_CACHE_MAX_SIZE = 32`), never on idle, so an abandoned process sat |
| 29 | + on every index it had touched. The same fact appeared in #370 as one worker at |
| 30 | + ~16 GiB. |
| 31 | + |
| 32 | + ⚠ **Disabled by default, and it must stay that way.** Cold hydration of that |
| 33 | + 665k-symbol index was measured at 7.5 to 11.4 minutes, so a TTL that evicts |
| 34 | + during a quiet spell hands the next query that bill. Defaulting it on would |
| 35 | + fix the leaking box by breaking the large-index one. Unset, `0`, negative, or |
| 36 | + unparseable all mean disabled, which is byte-identical to previous behavior. |
| 37 | + Swept on access rather than by a timer thread: a leaked process makes no |
| 38 | + calls, so it needs no timer to stay small. |
| 39 | + |
| 40 | +### Fixed |
| 41 | + |
| 42 | +- **`_is_pid_alive` reported dead Windows processes as alive.** Uncovered while |
| 43 | + testing the registry. A successful `OpenProcess` does not mean the process is |
| 44 | + running: while any handle to it remains open the PID stays queryable after |
| 45 | + exit, and the parent that spawned it normally holds exactly such a handle. So |
| 46 | + a killed child read as alive. |
| 47 | + |
| 48 | + This is a shared primitive. `process_locks.inspect` treats a dead holder's |
| 49 | + lock as stale and ignorable, so a crashed server's lock file could look |
| 50 | + permanently held. Now checks `GetExitCodeProcess` for `STILL_ACTIVE`, and |
| 51 | + falls back to the old answer if the call fails rather than declaring a |
| 52 | + possibly-live process dead. Also sets the `argtypes`/`restype` that were |
| 53 | + missing: `OpenProcess` returns a pointer-sized HANDLE and ctypes defaults the |
| 54 | + return type to `c_int`, truncating it on 64-bit — the same trap already |
| 55 | + documented for `GetProcessTimes`. On POSIX, a zombie now reads as dead via |
| 56 | + `/proc/<pid>/stat` (best effort; reap state is only visible for our own |
| 57 | + children). |
| 58 | + |
| 59 | +New `tests/test_v1_108_172.py` (21), including a real spawn-and-kill check that |
| 60 | +fails against the pre-fix liveness logic. NO schema, tool-count, or |
| 61 | +INDEX_VERSION change. |
| 62 | + |
5 | 63 | ## [1.108.171] - 2026-07-25 - provider discovery no longer scans in quadratic time |
6 | 64 |
|
7 | 65 | ### Fixed |
|
0 commit comments