[engine] Bound the global parsed-AST cache - #348
Conversation
parsed_ast_root_cache was an unbounded module-global dict and the lock cache a per-Source defaultdict, neither of which ever evicted -- so every distinct (path, contents) parsed over the process lifetime leaked its AST and a Semaphore forever. The per-query UI-API parse path made this unbounded and user-driven. Replace the dict with a size-bounded LRU cache and the per-Source lock defaultdict with a single global parse lock (parsing is non-yielding CPU work that only runs on a cache miss). Add regression tests for eviction and for the module wiring a bounded cache.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Why the bounded (LRU) cache mattersThe cache is keyed on
The old Why LRU specifically: we still want the cache ( Honest caveat: this is a gradual leak, not a crash. A worker that loads rules once and never reloads was effectively fine under the old code; the bound matters because of reload churn and the per-query UI path, which trend toward OOM in processes meant to run for weeks. |
Summary
parsed_ast_root_cachewas an unbounded module-global dict and the lock cache a per-Sourcedefaultdict, neither of which ever evicted — so every distinct(path, contents)parsed over the process lifetime leaked its AST and aSemaphoreforever. The per-query UI-API parse path made this unbounded and user-driven.Related Issues/Tasks
Found via an SML-engine bug hunt. No GitHub issue (N/A).
Changes Made
_BoundedASTRootCache, stdlibOrderedDict; no new dependency).Sourcelockdefaultdictwith a single global parse lock (parsing is non-yielding CPU work that only runs on a cache miss).traceback.print_stack()debug path tied to the old per-Sourcelocking.Confidence Level
Confidence Level: Claude
Testing
uv run pytest osprey_worker/src/osprey/engine/ast/tests/test_ast_root_cache.pypasses.uv run ruff check .,uv run mypy ast/grammar.py, andfawltydepspass.Checklist
uv run ruff check .passes (no unused imports or other lint errors)uv tool run fawltydeps --check-unused --pyenv .venvpasses (no unused dependencies)CHANGELOG.mdwith my changes, if applicable