Skip to content

gateway: indexing blocks the event loop — /health and all routes stop responding during grammar download and import pre-scan #1745

Closed

Description

@CSchneiderHav

Summary

Once jobs run (see #1744), indexing a repository whose grammars are not cached makes the
whole gateway unresponsive, including /health. Without internet access every download attempt
blocks for 60 s (tree-sitter-language-pack's HTTP timeout), and CGC retries it for every file, so
a liveness probe kills the process. With grammars present, the synchronous phases still block the
loop for seconds per repository.

Cause

  • run_tree_sitter_index_async calls pre_scan_for_imports(...) synchronously on the loop
    (tools/indexing/pipeline.py L124; also scip_pipeline.py L110 and L285). The pre-scan builds
    parsers via get_parser → TreeSitterParser → get_language_safe →
    tree_sitter_language_pack.get_language, which downloads the platform bundle from GitHub on
    a cache miss.
  • The first parser init always calls get_language('python') (utils/tree_sitter_manager.py L58),
    whatever language the repo uses.
  • A failed parser init is not cached (get_parser, graph_builder.py L142-146), so the download is
    retried for each pre-scan extension and then for each file. Locally, with a silent endpoint, a
    1-file repo froze the loop for 120 s and a 3-file repo for 240 s. Both jobs ended completed
    with no definitions.
  • Moving the pre-scan to asyncio.to_thread is not enough on its own:
    tree-sitter-language-pack's Python get_language does its network I/O without releasing the GIL
    (see bug: Python get_language() holds the GIL during on-demand download xberg-io/tree-sitter-language-pack#203). parse_file, which already runs in a thread, froze the loop for 60 s per
    call in the same test.
  • Even with grammars present, discovery, the import pre-scan and all post-processing/graph writes
    (pipeline.py L271-420) run on the loop. The pre-scan alone took 3–6 s for a repo with 371
    Python files.

Environment

codegraphcontext 0.6.13 (same on main), tree-sitter-language-pack 1.20.0, Python 3.12, Linux
container without internet egress.

Suggestions

  1. Run the pre-scan (and ideally the post-processing phases) via asyncio.to_thread.
  2. Before the pre-scan, call tree_sitter_language_pack.prefetch(langs) in a
    thread. It releases the GIL: in the same test the loop's largest gap was 0.05 s. Remember a
    failed grammar so it is not retried for every file.
  3. Offline deployments: document fetching grammars at build time with prefetch([...]) or the
    release bundle (download([...]) is currently a no-op, see bug: download([...]) is a no-op for known languages and returns the requested count xberg-io/tree-sitter-language-pack#201), and a
    TREE_SITTER_LANGUAGE_PACK_MANIFEST_URL that refuses connections (e.g. a closed local port)
    so a missing grammar fails immediately. A blackholed address still waits 60 s. A cached
    manifest.json overrides MANIFEST_URL.

Disclosure: the investigation and this report were prepared with AI assistance (Claude Code) and reviewed by a human before posting.

Activity

  1. github-actions commented on Sep 29, 2026

    @github-actions
    Contributor

    Hi! 👋 Join our CodeGraphContext Discord channel to collaborate: https://discord.gg/dR4QY32uYQ

  2. Shashankss1205 commented on Oct 9, 2026

    @Shashankss1205
    Collaborator

    #1761 removes the unrelated Python grammar probe and stops hollow graphs from reporting success, but does not solve the gateway responsiveness issue. Remaining acceptance: a deterministic /health responsiveness test during uncached grammar loading, GIL-releasing grammar prefetch, and offloading synchronous discovery/import pre-scan/post-processing. Keeping this separate and open.

  3. Shashankss1205 commented on Oct 9, 2026

    @Shashankss1205
    Collaborator

    Published #1763 for the gateway-liveness work, stacked on the parser-initialization fix #1761. It moves Tree-sitter discovery, pre-scans, resolution and graph-write phases to workers, and runs blocking SCIP CLI/augmentation work in one worker. The language loader uses language-pack's GIL-releasing prefetch before get_language; the upstream v1.20.0 binding confirms that distinction. New tests call the real /health route while indexing phases are deliberately blocked, and cover cold-fetch failure without per-file retries plus cooperative SCIP cancellation. Parser/API/indexing regressions pass 520 tests; graph-write/resilience/convergence regressions pass 64 with 1 skip. Main-target CI and independent approval are still needed after #1761 merges. This does not provide rollback or interruption of an in-flight native write (#829), so those cancellation guarantees remain separate.

  4. moved this from Backlog tasks to In progress in CGC Progress Boardon Oct 15, 2026
  5. Shashankss1205 commented on Oct 15, 2026

    @Shashankss1205
    Collaborator

    Maintenance review — 15 October 2026

    #1763 targeted its dependency branch; #1765/#1766 carry the same liveness changes onto main. Blocking discovery/pre-scan/SCIP/write phases move to workers, with sequential writes and documented native-call cancellation limits.

  6. moved this from In progress to Done in CGC Progress Boardon Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions