| 1 | """Cache-state discipline for measured rows.
|
| 2 |
|
| 3 | Cache state is a measurement, not a wish (ADR-0002). A row may claim "cold"
|
| 4 | only when an OS page-cache purge verifiably succeeded, and "warm" only when
|
| 5 | the caller explicitly performed the warming. Everything else is reported as
|
| 6 | "unknown" with a reason — never a silently fabricated state.
|
| 7 | """
|
| 8 |
|
| 9 | from __future__ import annotations
|
| 10 |
|
| 11 | import os
|
| 12 | import platform
|
| 13 | import subprocess
|
| 14 | from pathlib import Path
|
| 15 | from typing import Any
|
| 16 |
|
| 17 | PURGE_TIMEOUT_S = 30
|
| 18 | DARWIN_PURGE = "/usr/sbin/purge"
|
| 19 | LINUX_DROP_CACHES = Path("/proc/sys/vm/drop_caches")
|
| 20 |
|
| 21 |
|
| 22 | def _purge_result(attempted: bool, ok: bool, method: str | None, error: str | None) -> dict[str, Any]:
|
| 23 | return {"attempted": attempted, "ok": ok, "method": method, "error": error}
|
| 24 |
|
| 25 |
|
| 26 | def _run_purge_command(command: list[str]) -> str | None:
|
| 27 | """Run a purge command; return None on success, an error string on failure."""
|
| 28 | try:
|
| 29 | completed = subprocess.run(command, capture_output=True, text=True, timeout=PURGE_TIMEOUT_S)
|
| 30 | except Exception as error:
|
| 31 | return f"{type(error).__name__}: {error}"
|
| 32 | if completed.returncode != 0:
|
| 33 | detail = (completed.stderr or completed.stdout or "").strip()
|
| 34 | return f"exit {completed.returncode}" + (f": {detail}" if detail else "")
|
| 35 | return None
|
| 36 |
|
| 37 |
|
| 38 | def _drop_linux_caches(path: Path = LINUX_DROP_CACHES) -> str | None:
|
| 39 | """Write to /proc/sys/vm/drop_caches; return None on success, error string on failure."""
|
| 40 | try:
|
| 41 | os.sync()
|
| 42 | path.write_text("3\n")
|
| 43 | except Exception as error:
|
| 44 | return f"{type(error).__name__}: {error}"
|
| 45 | return None
|
| 46 |
|
| 47 |
|
| 48 | def purge_fs_caches() -> dict[str, Any]:
|
| 49 | """Best-effort OS page-cache purge. Never raises; honesty over success."""
|
| 50 | system = platform.system()
|
| 51 | if system == "Darwin":
|
| 52 | # purge(8) needs root on current macOS. Try it directly, then a
|
| 53 | # non-interactive sudo (works only with a NOPASSWD sudoers entry for
|
| 54 | # /usr/sbin/purge); never prompt, never fake success.
|
| 55 | error = _run_purge_command([DARWIN_PURGE])
|
| 56 | if error is None:
|
| 57 | return _purge_result(True, True, "darwin_purge", None)
|
| 58 | sudo_error = _run_purge_command(["sudo", "-n", DARWIN_PURGE])
|
| 59 | if sudo_error is None:
|
| 60 | return _purge_result(True, True, "darwin_sudo_purge", None)
|
| 61 | return _purge_result(True, False, "darwin_purge", f"{error}; sudo -n: {sudo_error}")
|
| 62 | if system == "Linux":
|
| 63 | error = _drop_linux_caches()
|
| 64 | return _purge_result(True, error is None, "linux_drop_caches", error)
|
| 65 | return _purge_result(False, False, None, f"unsupported_platform: {system}")
|
| 66 |
|
| 67 |
|
| 68 | def cache_fields(
|
| 69 | requested: str | None,
|
| 70 | purge_result: dict[str, Any] | None,
|
| 71 | *,
|
| 72 | warmed_by_scenario: bool = False,
|
| 73 | ) -> dict[str, Any]:
|
| 74 | """Honest ``cache_state``/``cache_state_reason`` row fields.
|
| 75 |
|
| 76 | ``warmed_by_scenario`` is the caller's explicit claim that it ran a warming
|
| 77 | pass; without that claim a "warm" request still reports "unknown".
|
| 78 | """
|
| 79 | if requested is None:
|
| 80 | return {"cache_state": "unknown", "cache_state_reason": "cache_state_unset"}
|
| 81 | if requested == "cold":
|
| 82 | if purge_result and purge_result.get("ok"):
|
| 83 | method = purge_result.get("method") or "unknown_method"
|
| 84 | return {"cache_state": "cold", "cache_state_reason": f"fs_caches_purged: {method}"}
|
| 85 | if purge_result and purge_result.get("attempted"):
|
| 86 | error = purge_result.get("error") or "unknown_error"
|
| 87 | return {"cache_state": "unknown", "cache_state_reason": f"purge_failed: {error}"}
|
| 88 | return {"cache_state": "unknown", "cache_state_reason": "purge_not_attempted"}
|
| 89 | if requested == "warm":
|
| 90 | if warmed_by_scenario:
|
| 91 | return {"cache_state": "warm", "cache_state_reason": "explicitly_warmed_by_scenario"}
|
| 92 | return {"cache_state": "unknown", "cache_state_reason": "warm_requested_without_warming_claim"}
|
| 93 | return {"cache_state": "unknown", "cache_state_reason": f"unrecognized_cache_state_request: {requested}"}
|