Skip to content

Commit dd2b703

Browse files
committed
feat: add support for skipping repositories with no trainable sources and cleanup outputs on deduplication failures.
1 parent 1dfbbd9 commit dd2b703

4 files changed

Lines changed: 284 additions & 37 deletions

File tree

scripts/streaming_conveyor.py

Lines changed: 89 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
wait,
6969
)
7070
from pathlib import Path
71-
from typing import Callable, Sequence
71+
from typing import Callable, Iterable, Sequence
7272

7373
# Reuse the proven machinery. streaming_reindex_commits already re-exports the
7474
# shared primitives from streaming_reindex (MLX_ROOT, Manifest, RepoFailure, ...)
@@ -1362,16 +1362,40 @@ def run_code_half(
13621362
stage_db = sr.code_stage_db(work_root / repo, repo) if dedup_db is not None else None
13631363
promoted = False
13641364
try:
1365-
info = sr.process_one_repo(
1366-
repo, repo_dir, lengths_code, work_root, dedup_db, dedup_near,
1367-
global_symbol_index, memory_limit_gb, parse_workers, index_timeout_s,
1368-
index_stall_timeout_s,
1369-
promote_dedup_on_success=False,
1370-
)
1365+
try:
1366+
info = sr.process_one_repo(
1367+
repo, repo_dir, lengths_code, work_root, dedup_db, dedup_near,
1368+
global_symbol_index, memory_limit_gb, parse_workers, index_timeout_s,
1369+
index_stall_timeout_s,
1370+
promote_dedup_on_success=False,
1371+
)
1372+
except RepoFailure as exc:
1373+
if is_no_trainable_source_failure(exc):
1374+
promoted = True
1375+
return {
1376+
"source": "code",
1377+
"repo": repo,
1378+
"skipped": True,
1379+
"skip_reason": "no_trainable_source",
1380+
"lengths": {},
1381+
"stage_timings_s": {},
1382+
"detail": exc.detail,
1383+
}
1384+
raise
13711385
if info.get("skipped"):
13721386
return info
1373-
# zstd-max the per-length code parquet files this repo just wrote.
13741387
timings = dict(info.get("stage_timings_s", {}))
1388+
try:
1389+
timings.update(sr.promote_dedup_stage(dedup_db, stage_id, stage_db))
1390+
except Exception as exc:
1391+
remove_code_outputs(repo, info.get("lengths", {}).keys())
1392+
raise RepoFailure(
1393+
repo,
1394+
"dedup_promote",
1395+
f"{type(exc).__name__}: {exc}",
1396+
) from exc
1397+
promoted = True
1398+
# zstd-max the per-length code parquet files this repo just wrote.
13751399
started = time.monotonic()
13761400
deferred = 0
13771401
for L in info.get("lengths", {}):
@@ -1385,9 +1409,7 @@ def run_code_half(
13851409
timings["recompress_s"] = round(time.monotonic() - started, 6)
13861410
if deferred:
13871411
timings["recompress_deferred"] = float(deferred)
1388-
timings.update(sr.promote_dedup_stage(dedup_db, stage_id, stage_db))
13891412
info["stage_timings_s"] = timings
1390-
promoted = True
13911413
return info
13921414
finally:
13931415
if not promoted:
@@ -1413,6 +1435,20 @@ def run_code_half(
14131435
]
14141436

14151437

1438+
def remove_code_outputs(repo: str, lengths: Iterable[str | int]) -> None:
1439+
for length in lengths:
1440+
dest = sr.OUTPUT_ROOT / str(length) / f"{repo}.parquet"
1441+
if dest.exists():
1442+
dest.unlink()
1443+
1444+
1445+
def is_no_trainable_source_failure(exc: RepoFailure) -> bool:
1446+
return (
1447+
exc.stage == "index_project"
1448+
and "no training docs (no_trainable_source)" in exc.detail.lower()
1449+
)
1450+
1451+
14161452
def is_retryable_index_project_failure(exc: RepoFailure) -> bool:
14171453
detail = exc.detail.lower()
14181454
return (
@@ -2303,33 +2339,50 @@ def process_one_repo(
23032339
code_index_stall_timeout_s,
23042340
recompressor=code_recompressor,
23052341
)
2306-
with manifest_lock:
2307-
manifest.mark_done(ck, cinfo)
2308-
totals = _length_totals(cinfo)
2309-
with manifest_lock:
2310-
cumulative["valid"] += totals["valid_tokens"]
2311-
cumulative_valid = cumulative["valid"]
2312-
if progress is not None:
2313-
progress.emit(
2314-
"unit_done",
2315-
stream="code",
2316-
repo=repo,
2317-
unit=ck,
2318-
rows=totals["rows"],
2319-
valid_tokens=totals["valid_tokens"],
2320-
capacity_tokens=totals["capacity_tokens"],
2321-
**_primary_bucket_progress(cinfo, lengths_code),
2322-
lengths=cinfo.get("lengths", {}),
2323-
stage_timings_s=cinfo.get("stage_timings_s", {}),
2324-
cumulative_valid_tokens=cumulative_valid,
2342+
if cinfo.get("skipped"):
2343+
with manifest_lock:
2344+
manifest.mark_done(ck, cinfo)
2345+
if progress is not None:
2346+
progress.emit(
2347+
"unit_skipped",
2348+
stream="code",
2349+
repo=repo,
2350+
unit=ck,
2351+
reason=cinfo.get("skip_reason", "skipped"),
2352+
detail=str(cinfo.get("detail", ""))[:2000],
2353+
)
2354+
result["code"] = cinfo
2355+
_log(
2356+
f"SKIP {ck}: {cinfo.get('skip_reason', 'skipped')}"
2357+
)
2358+
else:
2359+
with manifest_lock:
2360+
manifest.mark_done(ck, cinfo)
2361+
totals = _length_totals(cinfo)
2362+
with manifest_lock:
2363+
cumulative["valid"] += totals["valid_tokens"]
2364+
cumulative_valid = cumulative["valid"]
2365+
if progress is not None:
2366+
progress.emit(
2367+
"unit_done",
2368+
stream="code",
2369+
repo=repo,
2370+
unit=ck,
2371+
rows=totals["rows"],
2372+
valid_tokens=totals["valid_tokens"],
2373+
capacity_tokens=totals["capacity_tokens"],
2374+
**_primary_bucket_progress(cinfo, lengths_code),
2375+
lengths=cinfo.get("lengths", {}),
2376+
stage_timings_s=cinfo.get("stage_timings_s", {}),
2377+
cumulative_valid_tokens=cumulative_valid,
2378+
)
2379+
if checkpoint is not None:
2380+
checkpoint.maybe_checkpoint(cumulative_valid)
2381+
result["code"] = cinfo
2382+
_log(
2383+
f"DONE {ck}: buckets={sorted(cinfo['lengths'].keys())} "
2384+
f"(cum_all={cumulative['valid']})"
23252385
)
2326-
if checkpoint is not None:
2327-
checkpoint.maybe_checkpoint(cumulative_valid)
2328-
result["code"] = cinfo
2329-
_log(
2330-
f"DONE {ck}: buckets={sorted(cinfo['lengths'].keys())} "
2331-
f"(cum_all={cumulative['valid']})"
2332-
)
23332386
except RepoFailure as exc:
23342387
_log(f"FAIL {ck}: {exc}")
23352388
with manifest_lock:

tests/test_macro_scan_selection.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
6+
def test_macro_scan_roots_are_limited_to_trainable_code_and_indexed_headers(tmp_path: Path) -> None:
7+
from tools.clang_indexer import index_project
8+
9+
src = tmp_path / "src"
10+
include = tmp_path / "include"
11+
src.mkdir()
12+
include.mkdir()
13+
used_cpp = src / "used.cpp"
14+
unused_cpp = src / "unused.cpp"
15+
header = include / "api.hpp"
16+
unused_header = include / "unused.hpp"
17+
used_cpp.write_text("int used() { return 1; }\n", encoding="utf-8")
18+
unused_cpp.write_text("// no trainable declarations here\n", encoding="utf-8")
19+
header.write_text("template <class T> struct Box { T value; };\n", encoding="utf-8")
20+
unused_header.write_text("#define UNUSED_VALUE 1\n", encoding="utf-8")
21+
22+
index = index_project.ProjectIndex()
23+
index.add_function(
24+
index_project.FunctionDef(
25+
name="used",
26+
qualified_name="used",
27+
file="src/used.cpp",
28+
line=1,
29+
text="int used() { return 1; }",
30+
callees=[],
31+
is_definition=True,
32+
)
33+
)
34+
index.add_typedef(
35+
index_project.TypeDef(
36+
name="Box",
37+
qualified_name="Box",
38+
file="include/api.hpp",
39+
line=1,
40+
text="template <class T> struct Box { T value; };",
41+
kind=4,
42+
)
43+
)
44+
45+
roots = index_project.select_macro_scan_files(
46+
[str(used_cpp), str(unused_cpp), str(header), str(unused_header)],
47+
index,
48+
[str(header), str(unused_header)],
49+
project_dir=str(tmp_path),
50+
)
51+
52+
rel_roots = {Path(path).relative_to(tmp_path).as_posix() for path in roots}
53+
assert rel_roots == {"include/api.hpp", "src/used.cpp"}

tests/test_streaming_conveyor_progress.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,73 @@ def runner(
465465
assert info["lengths"]["1024"]["valid_tokens"] == 1024
466466

467467

468+
def test_run_code_half_removes_outputs_when_dedup_promote_fails(tmp_path, monkeypatch):
469+
import streaming_conveyor
470+
471+
repo = "repo"
472+
output_root = tmp_path / "out"
473+
dest = output_root / "1024" / f"{repo}.parquet"
474+
dest.parent.mkdir(parents=True)
475+
dest.write_bytes(b"partial parquet already appended")
476+
monkeypatch.setattr(streaming_conveyor.sr, "OUTPUT_ROOT", output_root)
477+
478+
def fake_process_one_repo(*_args, **_kwargs):
479+
return {
480+
"source": "code",
481+
"lengths": {"1024": _stat(rows=1, valid=1024, target_length=1024)},
482+
"stage_timings_s": {},
483+
}
484+
485+
def fail_promote(*_args, **_kwargs):
486+
raise sqlite3.OperationalError("unable to open database file")
487+
488+
monkeypatch.setattr(streaming_conveyor.sr, "process_one_repo", fake_process_one_repo)
489+
monkeypatch.setattr(streaming_conveyor.sr, "promote_dedup_stage", fail_promote)
490+
491+
try:
492+
streaming_conveyor.run_code_half(
493+
repo,
494+
tmp_path / "src",
495+
(1024,),
496+
tmp_path / "work",
497+
tmp_path / "dedup.sqlite",
498+
True,
499+
)
500+
except streaming_conveyor.RepoFailure as exc:
501+
assert exc.stage == "dedup_promote"
502+
assert "OperationalError" in exc.detail
503+
else:
504+
raise AssertionError("run_code_half should fail when dedup promote fails")
505+
506+
assert not dest.exists()
507+
508+
509+
def test_run_code_half_skips_no_trainable_source(tmp_path, monkeypatch):
510+
import streaming_conveyor
511+
512+
def no_trainable_source(*_args, **_kwargs):
513+
raise streaming_conveyor.RepoFailure(
514+
"repo",
515+
"index_project",
516+
"no training docs (no_trainable_source): empty enriched jsonl",
517+
)
518+
519+
monkeypatch.setattr(streaming_conveyor.sr, "process_one_repo", no_trainable_source)
520+
521+
info = streaming_conveyor.run_code_half(
522+
"repo",
523+
tmp_path / "src",
524+
(1024,),
525+
tmp_path / "work",
526+
tmp_path / "dedup.sqlite",
527+
True,
528+
)
529+
530+
assert info["skipped"] is True
531+
assert info["skip_reason"] == "no_trainable_source"
532+
assert info["lengths"] == {}
533+
534+
468535
def test_failed_code_unit_was_index_memory_reads_manifest_receipt(tmp_path):
469536
import streaming_conveyor
470537

0 commit comments

Comments
 (0)