Skip to content

Commit d561fe5

Browse files
gadievronclaude
andauthored
fix(parsers): bound the JS and Go parse subprocesses with a timeout (#135)
Co-authored-by: Gadi Evron <gadievron@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 98f5504 commit d561fe5

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

libs/openant-core/core/parser_adapter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ def _parse_javascript(repo_path: str, output_dir: str, processing_level: str, sk
579579
stdout=sys.stderr,
580580
stderr=sys.stderr,
581581
cwd=str(_CORE_ROOT),
582+
timeout=1800, # 30 min — parity with the C/Ruby/PHP/Zig parse subprocesses
582583
)
583584

584585
if result.returncode != 0:
@@ -637,6 +638,7 @@ def _parse_go(repo_path: str, output_dir: str, processing_level: str, skip_tests
637638
stdout=sys.stderr,
638639
stderr=sys.stderr,
639640
cwd=str(_CORE_ROOT),
641+
timeout=1800, # 30 min — parity with the C/Ruby/PHP/Zig parse subprocesses
640642
)
641643

642644
if result.returncode != 0:
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Regression lock: the JS and Go parse subprocesses must pass a wall-clock timeout,
2+
so a hung inner Node/Go parser fails cleanly instead of blocking the Python parent forever.
3+
4+
C/Ruby/PHP/Zig already pass timeout=1800; JS and Go did not, so a direct-Python caller of
5+
parse_repository / _parse_javascript / _parse_go had no bound. These tests fail on master
6+
(no timeout) and pass with the fix.
7+
"""
8+
import inspect
9+
import tempfile
10+
11+
from core import parser_adapter
12+
13+
14+
class _FakeCompleted:
15+
returncode = 0
16+
17+
18+
def _capture_run_calls(monkeypatch):
19+
calls = []
20+
21+
def fake_run(cmd, **kwargs):
22+
calls.append((cmd, kwargs))
23+
return _FakeCompleted()
24+
25+
monkeypatch.setattr(parser_adapter.subprocess, "run", fake_run)
26+
return calls
27+
28+
29+
def _parse_call_timeout(calls, script_name):
30+
"""timeout kwarg of the subprocess.run whose cmd invokes <script_name> (the parse step)."""
31+
for cmd, kwargs in calls:
32+
if any(script_name in str(part) for part in cmd):
33+
return kwargs.get("timeout")
34+
return "NO-PARSE-CALL"
35+
36+
37+
def test_js_parse_subprocess_has_timeout(monkeypatch):
38+
calls = _capture_run_calls(monkeypatch)
39+
d = tempfile.mkdtemp()
40+
try:
41+
parser_adapter._parse_javascript(d, d, "reachable")
42+
except Exception:
43+
pass # we only care that the parse subprocess.run received a timeout
44+
assert _parse_call_timeout(calls, "test_pipeline.py") == 1800
45+
46+
47+
def test_go_parse_subprocess_has_timeout(monkeypatch):
48+
calls = _capture_run_calls(monkeypatch)
49+
d = tempfile.mkdtemp()
50+
try:
51+
parser_adapter._parse_go(d, d, "reachable")
52+
except Exception:
53+
pass
54+
assert _parse_call_timeout(calls, "test_pipeline.py") == 1800
55+
56+
57+
def test_all_subprocess_parsers_are_uniformly_timed():
58+
"""Every language whose *parse step* runs as a subprocess must carry a timeout — not just
59+
4 of them. (Python parses in-process, so it is intentionally excluded; the npm-install
60+
dependency bootstrap in _ensure_js_parser_dependencies is a separate concern, not a parse.)"""
61+
for lang in ("_parse_javascript", "_parse_go", "_parse_c",
62+
"_parse_ruby", "_parse_php", "_parse_zig"):
63+
src = inspect.getsource(getattr(parser_adapter, lang))
64+
assert "timeout=" in src, f"{lang} subprocess.run is missing a timeout"

0 commit comments

Comments
 (0)