Skip to content

Commit b99b4e1

Browse files
committed
perf(cache): reduce cache load transient memory
1 parent 46856c8 commit b99b4e1

3 files changed

Lines changed: 54 additions & 7 deletions

File tree

codeclone/cache/integrity.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from collections.abc import Mapping
1212
from pathlib import Path
1313

14-
from ..utils.json_io import json_text as _json_text
14+
import orjson
15+
1516
from ..utils.json_io import read_json_document as _read_json_document
1617
from ..utils.json_io import (
1718
write_json_document_atomically as _write_json_document_atomically,
@@ -39,12 +40,11 @@ def as_str_dict(value: object) -> dict[str, object] | None:
3940

4041

4142
def canonical_json(data: object) -> str:
42-
return _json_text(data, sort_keys=True)
43+
return _canonical_json_bytes(data).decode("utf-8")
4344

4445

4546
def sign_cache_payload(data: Mapping[str, object]) -> str:
46-
canonical = canonical_json(data)
47-
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
47+
return hashlib.sha256(_canonical_json_bytes(data)).hexdigest()
4848

4949

5050
def verify_cache_payload_signature(
@@ -54,6 +54,10 @@ def verify_cache_payload_signature(
5454
return hmac.compare_digest(signature, sign_cache_payload(payload))
5555

5656

57+
def _canonical_json_bytes(data: object) -> bytes:
58+
return orjson.dumps(data, option=orjson.OPT_SORT_KEYS)
59+
60+
5761
def read_json_document(path: Path, *, max_bytes: int | None = None) -> object:
5862
if max_bytes is None:
5963
return _read_json_document(path)

codeclone/cache/store.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,11 +406,17 @@ def _load_and_validate(self, raw_obj: object) -> CacheData | None:
406406
files_dict = _as_str_dict(payload.get("files"))
407407
if files_dict is None:
408408
return self._reject_invalid_cache_format(schema_version=version)
409+
segment_projection_obj = payload.get("sr")
410+
payload.pop("files", None)
411+
payload.clear()
412+
raw.clear()
409413

410414
parsed_files: dict[str, CacheEntry] = {}
411415
with span(name="cache.decode_entries") as decode_span:
412416
decode_span.set_counter("cache_entries", len(files_dict))
413-
for wire_path, file_entry_obj in files_dict.items():
417+
while files_dict:
418+
wire_path = next(iter(files_dict))
419+
file_entry_obj = files_dict.pop(wire_path)
414420
runtime_path = runtime_filepath_from_wire(wire_path, root=self.root)
415421
parsed_entry = self._decode_entry(file_entry_obj, runtime_path)
416422
if parsed_entry is None:
@@ -419,7 +425,7 @@ def _load_and_validate(self, raw_obj: object) -> CacheData | None:
419425
decode_span.set_counter("decoded_entries", len(parsed_files))
420426
with span(name="cache.segment_projection"):
421427
self.segment_report_projection = decode_segment_report_projection(
422-
payload.get("sr"),
428+
segment_projection_obj,
423429
root=self.root,
424430
)
425431

tests/test_cache.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
import hashlib
910
import json
1011
import os
1112
from collections.abc import Callable
@@ -81,7 +82,7 @@
8182
_unit_dict_from_model,
8283
)
8384
from codeclone.cache.integrity import as_str_dict as _as_str_dict
84-
from codeclone.cache.integrity import sign_cache_payload
85+
from codeclone.cache.integrity import canonical_json, sign_cache_payload
8586
from codeclone.cache.projection import (
8687
runtime_filepath_from_wire,
8788
wire_filepath_from_runtime,
@@ -1452,6 +1453,42 @@ def test_cache_signature_validation_ignores_json_whitespace(tmp_path: Path) -> N
14521453
assert loaded.get_file_entry("x.py") is not None
14531454

14541455

1456+
def test_cache_signature_matches_legacy_string_digest_for_unicode_payload() -> None:
1457+
cache = Cache(Path("cache.json"))
1458+
payload = _analysis_payload(
1459+
cache,
1460+
files={
1461+
"unicodé.py": {
1462+
"st": [1, 10],
1463+
"rn": ["Ω", "é"],
1464+
}
1465+
},
1466+
)
1467+
legacy_digest = hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest()
1468+
1469+
assert sign_cache_payload(payload) == legacy_digest
1470+
1471+
1472+
def test_cache_load_accepts_legacy_string_signed_unicode_payload(
1473+
tmp_path: Path,
1474+
) -> None:
1475+
cache_path = tmp_path / "cache.json"
1476+
cache = Cache(cache_path)
1477+
cache.put_file_entry("unicodé.py", {"mtime_ns": 1, "size": 10}, [], [], [])
1478+
cache.save()
1479+
1480+
raw = json.loads(cache_path.read_text("utf-8"))
1481+
payload = cast(dict[str, object], raw["payload"])
1482+
raw["sig"] = hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest()
1483+
cache_path.write_text(json.dumps(raw, ensure_ascii=False, indent=2), "utf-8")
1484+
1485+
loaded = Cache(cache_path)
1486+
loaded.load()
1487+
1488+
assert loaded.load_warning is None
1489+
assert loaded.get_file_entry("unicodé.py") is not None
1490+
1491+
14551492
def test_decode_wire_file_and_name_section_helpers_cover_valid_and_invalid() -> None:
14561493
encoded = _encode_wire_file_entry(
14571494
{

0 commit comments

Comments
 (0)