Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,29 @@ with the original grammar or cache error included. A per-file initialization
failure contributes to failed_files and the job's error list. Dependency
loading no longer downloads Python's grammar when another language is requested.
Check the grammar cache ownership and network access before reinstalling packages.

### Gateway health stalls during indexing

Indexing runs discovery, parser initialization, import pre-scans, graph writes
and resolution in workers so the serving event loop can answer `/health` and
job-status requests. SCIP's blocking CLI and augmentation phases also run in a
worker. Writes remain sequential within each indexing job.

On a cold cache, language-pack 1.20's `get_language` can hold Python's interpreter
lock during network I/O. CGC uses `prefetch` before loading each requested
grammar; that download path releases the lock. A pre-scan initialization failure
fails the job before per-file parsing, with the original error included.

For offline deployment, populate the cache at build time using the same pinned
language-pack version and supported platform, for example:

```bash
python -c 'from tree_sitter_language_pack import prefetch; prefetch(["python", "typescript"])'
```

Choose the languages your deployment uses and make the resulting cache available
to the runtime user. A missing grammar can still wait for the downloader's
network timeout, but the serving loop remains available. Cancelling a worker
cannot interrupt an in-flight native/subprocess call or roll back graph writes;
SCIP stops subsequent phases when that call returns. Transactional cancellation
is tracked separately in #829.
109 changes: 66 additions & 43 deletions src/codegraphcontext/tools/indexing/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,18 @@ async def run_tree_sitter_index_async(
job_manager.update_job(job_id, status=JobStatus.RUNNING)

repo_root = path if path.is_dir() else path.parent.resolve()
writer.add_repository_to_graph(repo_root, is_dependency)
await asyncio.to_thread(writer.add_repository_to_graph, repo_root, is_dependency)
repo_name = repo_root.name

files, _ignore_root = discover_files_to_index(path, cgcignore_path, supported_extensions=set(parsers.keys()))
files, _ignore_root = await asyncio.to_thread(
discover_files_to_index, path, cgcignore_path, supported_extensions=set(parsers.keys())
)

if job_id:
job_manager.update_job(job_id, total_files=len(files))

debug_log("Starting pre-scan to build imports map...")
imports_map = pre_scan_for_imports(files, parsers.keys(), get_parser)
imports_map = await asyncio.to_thread(pre_scan_for_imports, files, parsers.keys(), get_parser)
debug_log(f"Pre-scan complete. Found {len(imports_map)} definitions.")

all_file_data: List[Dict[str, Any]] = []
Expand Down Expand Up @@ -270,32 +272,45 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
if job_id:
job_manager.update_job(job_id, status_message="Resolving inheritance links...")
info_logger(f"[INHERITS] Resolving inheritance links across {len(all_file_data)} files...")
inheritance_batch, csharp_files = build_inheritance_and_csharp_files(all_file_data, imports_map)
implements_batch = build_go_implements_links(all_file_data)
implements_batch.extend(build_haskell_implements_links(all_file_data))
implements_batch.extend(build_elixir_implements_links(all_file_data))
writer.write_inheritance_links(inheritance_batch, csharp_files, imports_map)
writer.write_implements_links(implements_batch)
writer.write_embeds_links(build_embeds_links(all_file_data))
writer.write_companion_of_links(build_companion_of_links(all_file_data))
writer.write_partial_of_links(build_partial_of_links(all_file_data))
writer.write_part_of_links(build_part_of_links(all_file_data))
writer.write_metaclass_links(build_metaclass_links(all_file_data, imports_map))
writer.write_decorated_by_links(build_decorated_by_links(all_file_data, imports_map))
writer.write_binds_links(build_binds_links(all_file_data, imports_map))
writer.write_previews_links(build_previews_links(all_file_data, imports_map))
inheritance_batch, csharp_files = await asyncio.to_thread(
build_inheritance_and_csharp_files, all_file_data, imports_map
)
implements_batch = await asyncio.to_thread(build_go_implements_links, all_file_data)
implements_batch.extend(await asyncio.to_thread(build_haskell_implements_links, all_file_data))
implements_batch.extend(await asyncio.to_thread(build_elixir_implements_links, all_file_data))
await asyncio.to_thread(writer.write_inheritance_links, inheritance_batch, csharp_files, imports_map)
await asyncio.to_thread(writer.write_implements_links, implements_batch)
# Keep graph writes sequential. Each await yields to health/status requests
# and prevents scheduling the next phase after coroutine cancellation.
for build_links, write_links in (
(build_embeds_links, writer.write_embeds_links),
(build_companion_of_links, writer.write_companion_of_links),
(build_partial_of_links, writer.write_partial_of_links),
(build_part_of_links, writer.write_part_of_links),
):
links = await asyncio.to_thread(build_links, all_file_data)
await asyncio.to_thread(write_links, links)
for build_links, write_links in (
(build_metaclass_links, writer.write_metaclass_links),
(build_decorated_by_links, writer.write_decorated_by_links),
(build_binds_links, writer.write_binds_links),
(build_previews_links, writer.write_previews_links),
):
links = await asyncio.to_thread(build_links, all_file_data, imports_map)
await asyncio.to_thread(write_links, links)
t1 = time.time()
info_logger(f"Inheritance links created in {t1 - t0:.1f}s. Starting function calls...")

resolved_calls = build_function_call_groups(
resolved_calls = await asyncio.to_thread(
build_function_call_groups,
all_file_data,
imports_map,
None,
diagnostics=call_resolution_diagnostics,
)
if job_id:
job_manager.update_job(job_id, status_message="Writing function CALLS edges...")
writer.write_function_call_groups(*resolved_calls)
await asyncio.to_thread(writer.write_function_call_groups, *resolved_calls)
t2 = time.time()
info_logger(f"Function calls created in {t2 - t1:.1f}s. Total post-processing: {t2 - t0:.1f}s")

Expand All @@ -306,12 +321,12 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
if job_id:
job_manager.update_job(job_id, status_message="Linking C++ class-function edges...")
info_logger("[CPP] Linking C++ out-of-line method definitions to their classes...")
writer.write_cpp_class_function_links(resolved_repo_path_str)
await asyncio.to_thread(writer.write_cpp_class_function_links, resolved_repo_path_str)

# ── File->CONTAINS invariant (empty-index race self-heal) ────────────────
if job_id:
job_manager.update_job(job_id, status_message="Verifying File->CONTAINS linking...")
repaired_contains = writer.repair_missing_contains_links(resolved_repo_path_str)
repaired_contains = await asyncio.to_thread(writer.repair_missing_contains_links, resolved_repo_path_str)
if repaired_contains:
warning_logger(
f"[INVARIANT] Back-filled File-[:CONTAINS] edges for: {repaired_contains}"
Expand All @@ -327,7 +342,7 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
spring_inject_batch.extend(injections)
if spring_inject_batch:
info_logger(f"[SPRING] Writing {len(spring_inject_batch)} Spring injection edges...")
writer.write_spring_inject_links(spring_inject_batch)
await asyncio.to_thread(writer.write_spring_inject_links, spring_inject_batch)

# Also collect Spring endpoint properties from functions and write them
endpoint_batch = []
Expand All @@ -342,27 +357,27 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
"http_path": fn.get("http_path"),
})
if endpoint_batch:
writer.write_spring_endpoint_properties(endpoint_batch)
await asyncio.to_thread(writer.write_spring_endpoint_properties, endpoint_batch)

# ── Maven / Gradle build graph (#888) ────────────────────────────────────
if not is_dependency and path.is_dir():
if job_id:
job_manager.update_job(job_id, status_message="Processing Maven build graph...")
try:
from ...tools.languages.maven import parse_repo_maven
maven_data = parse_repo_maven(path.resolve())
maven_data = await asyncio.to_thread(parse_repo_maven, path.resolve())
if maven_data.get("modules"):
writer.write_maven_build_graph(maven_data, str(path.resolve()))
await asyncio.to_thread(writer.write_maven_build_graph, maven_data, str(path.resolve()))
except Exception as _me:
info_logger(f"[MAVEN] Build graph failed (skipping): {_me}")

if job_id:
job_manager.update_job(job_id, status_message="Processing Gradle build graph...")
try:
from ...tools.languages.gradle import parse_repo_gradle
gradle_data = parse_repo_gradle(path.resolve())
gradle_data = await asyncio.to_thread(parse_repo_gradle, path.resolve())
if gradle_data.get("modules"):
writer.write_gradle_build_graph(gradle_data, str(path.resolve()))
await asyncio.to_thread(writer.write_gradle_build_graph, gradle_data, str(path.resolve()))
except Exception as _ge:
info_logger(f"[GRADLE] Build graph failed (skipping): {_ge}")

Expand All @@ -380,19 +395,19 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
info_logger(
f"[ORM] Writing {class_table_count} class→table mappings and {query_count} query links..."
)
writer.write_orm_mappings(orm_batch)
writer.write_query_links(orm_batch)
writer.write_spring_data_repo_links(orm_batch)
await asyncio.to_thread(writer.write_orm_mappings, orm_batch)
await asyncio.to_thread(writer.write_query_links, orm_batch)
await asyncio.to_thread(writer.write_spring_data_repo_links, orm_batch)

# ── MyBatis XML mapper READS / WRITES edges ───────────────────────────────
if not is_dependency and path.is_dir():
if job_id:
job_manager.update_job(job_id, status_message="Processing MyBatis XML mappers...")
try:
from ...tools.languages.mybatis import find_and_parse_mybatis_mappers
mybatis_batch = find_and_parse_mybatis_mappers(path.resolve())
mybatis_batch = await asyncio.to_thread(find_and_parse_mybatis_mappers, path.resolve())
if mybatis_batch:
writer.write_mybatis_links(mybatis_batch)
await asyncio.to_thread(writer.write_mybatis_links, mybatis_batch)
except Exception as _me:
info_logger(f"[MYBATIS] Mapper parsing failed (skipping): {_me}")

Expand All @@ -405,7 +420,7 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
# so 'vector resolve is working' and 'vector resolve has never run'
# were indistinguishable (#1597).
from .embeddings import probe_embedding_backend
_ok, _detail = probe_embedding_backend()
_ok, _detail = await asyncio.to_thread(probe_embedding_backend)
if not _ok:
embed_warning = f"ENABLE_VECTOR_RESOLVE=true but embeddings were NOT generated: {_detail}"
error_logger(f"[EMBED] {embed_warning}")
Expand All @@ -416,7 +431,10 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
from .embeddings import EmbeddingPipeline
repo_path_str = path.resolve().as_posix()
info_logger("[EMBED] Starting embedding pipeline...")
EmbeddingPipeline(writer.driver).run(repo_path_str)
def generate_embeddings():
EmbeddingPipeline(writer.driver).run(repo_path_str)

await asyncio.to_thread(generate_embeddings)
info_logger("[EMBED] Embedding pipeline complete.")
except Exception as _ee:
embed_warning = f"ENABLE_VECTOR_RESOLVE=true but the embedding pipeline failed: {_ee}"
Expand All @@ -428,23 +446,28 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]:
job_manager.update_job(job_id, status_message="Running inheritance re-resolution...")
try:
from .resolution.post_resolution import run_inheritance_reresolve
vector_resolver = None
if (_gcv("ENABLE_VECTOR_RESOLVE") or "false").lower() == "true":
try:
from .vector_resolver import VectorResolver
vector_resolver = VectorResolver(writer.driver)
except Exception as _ve:
info_logger(f"[VECTOR] Resolver unavailable: {_ve}")
repo_path_str = path.resolve().as_posix()
improved = run_inheritance_reresolve(writer.driver, repo_path_str, vector_resolver)

def reresolve_inheritance():
vector_resolver = None
if (_gcv("ENABLE_VECTOR_RESOLVE") or "false").lower() == "true":
try:
from .vector_resolver import VectorResolver
vector_resolver = VectorResolver(writer.driver)
except Exception as _ve:
info_logger(f"[VECTOR] Resolver unavailable: {_ve}")
return run_inheritance_reresolve(writer.driver, repo_path_str, vector_resolver)

improved = await asyncio.to_thread(reresolve_inheritance)
info_logger(f"[INHERIT-RESOLVE] Post-resolution complete: {improved} edges improved")
except Exception as _ie:
info_logger(f"[INHERIT-RESOLVE] Post-resolution failed (skipping): {_ie}")

if index_summary is not None:
index_summary.clear()
index_summary.update(
build_index_summary(
await asyncio.to_thread(
build_index_summary,
files,
parsers,
all_file_data,
Expand Down
Loading
Loading