Skip to content

Commit d2593ec

Browse files
fix(analyzer,filter): use forkserver to avoid deadlock with PyO3 Rust extensions and match excludes on relative path
Co-authored-by: Koru Agent <agent@coru.dev>
1 parent bbf5254 commit d2593ec

2 files changed

Lines changed: 32 additions & 9 deletions

File tree

‎code2llm/core/analyzer.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
# Results from worker processes are plain dicts; _merge_results rebuilds dataclasses.
3838

3939
import logging
40+
import multiprocessing as mp
4041
import os
4142
import time
4243
from concurrent.futures import ProcessPoolExecutor, as_completed
@@ -390,7 +391,14 @@ def _analyze_parallel(self, files: List[Tuple[str, str]]) -> List[Dict]:
390391
"output_dir": self.config.output_dir,
391392
}
392393

393-
with ProcessPoolExecutor(max_workers=workers) as executor:
394+
# Use forkserver if available (Linux) to prevent deadlocks with native C/Rust extensions
395+
# and multi-threaded runtimes when forking.
396+
try:
397+
mp_context = mp.get_context("forkserver")
398+
except ValueError:
399+
mp_context = None
400+
401+
with ProcessPoolExecutor(max_workers=workers, mp_context=mp_context) as executor:
394402
# Submit all jobs
395403
future_to_file = {
396404
executor.submit(

‎code2llm/core/file_filter.py‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,22 @@ def _passes_gitignore(self, file_path: str) -> bool:
115115
return True
116116
return not self._gitignore_parser.is_ignored(Path(file_path), self.project_path)
117117

118-
def _passes_excludes(self, path_lower: str, basename_lower: str) -> bool:
118+
def _passes_excludes(
119+
self, path_lower: str, basename_lower: str, rel_lower: str | None = None
120+
) -> bool:
119121
"""Check if file passes exclude patterns (True = pass, False = excluded)."""
120122
# Fast substring excludes
121123
for pattern in self._simple_excludes:
122-
if pattern in path_lower:
124+
if pattern in path_lower or (rel_lower and pattern in rel_lower):
123125
return False
124126

125127
# Pre-compiled wildcard excludes
126128
for regex in self._regex_excludes:
127-
if regex.match(path_lower) or regex.match(basename_lower):
129+
if (
130+
regex.match(path_lower)
131+
or regex.match(basename_lower)
132+
or (rel_lower and regex.match(rel_lower))
133+
):
128134
return False
129135

130136
return True
@@ -146,11 +152,20 @@ def should_process(self, file_path: str) -> bool:
146152
if is_generated_artifact(file_path, self.project_path):
147153
return False
148154

149-
return (
150-
self._passes_gitignore(file_path)
151-
and self._passes_excludes(path_lower, basename_lower)
152-
and self._passes_includes(path_lower)
153-
)
155+
if not self._passes_gitignore(file_path):
156+
return False
157+
158+
rel_lower = None
159+
if self.project_path:
160+
try:
161+
rel_lower = str(Path(file_path).relative_to(self.project_path)).lower()
162+
except ValueError:
163+
pass
164+
165+
if not self._passes_excludes(path_lower, basename_lower, rel_lower):
166+
return False
167+
168+
return self._passes_includes(path_lower)
154169

155170
def _passes_line_count(self, line_count: int) -> bool:
156171
"""Check if function passes line count threshold."""

0 commit comments

Comments
 (0)