diff --git a/headroom/proxy/savings_tracker.py b/headroom/proxy/savings_tracker.py index 649e364c8..600b86bbc 100644 --- a/headroom/proxy/savings_tracker.py +++ b/headroom/proxy/savings_tracker.py @@ -21,19 +21,6 @@ from typing import Any from headroom import paths as _paths -# fcntl is Unix-only; on Windows file locking is skipped (best-effort). -# Typed Any so attribute access on flock/LOCK_EX/LOCK_UN doesn't fail on -# platforms where the type stub is absent. -_fcntl: Any = None -_HAS_FCNTL = False -try: - import fcntl as _fcntl_impl # noqa: E402 - - _fcntl = _fcntl_impl - _HAS_FCNTL = True -except ImportError: - pass - logger = logging.getLogger(__name__) HEADROOM_SAVINGS_PATH_ENV_VAR = _paths.HEADROOM_SAVINGS_PATH_ENV @@ -825,18 +812,7 @@ class SavingsTracker: f.write(json_data) f.flush() os.fsync(f.fileno()) - # Acquire an exclusive cross-process lock on the target file - # before the atomic rename so concurrent workers don't clobber - # each other's increments. Unix only; Windows skips gracefully. - if _HAS_FCNTL and self._path.exists(): - with open(self._path, "r+b") as lock_fh: - _fcntl.flock(lock_fh, _fcntl.LOCK_EX) - try: - Path(tmp_path).replace(self._path) - finally: - _fcntl.flock(lock_fh, _fcntl.LOCK_UN) - else: - Path(tmp_path).replace(self._path) + Path(tmp_path).replace(self._path) except Exception: try: Path(tmp_path).unlink() diff --git a/tests/test_proxy_savings_history.py b/tests/test_proxy_savings_history.py index e4874f5a6..8e95c05f4 100644 --- a/tests/test_proxy_savings_history.py +++ b/tests/test_proxy_savings_history.py @@ -211,6 +211,42 @@ def test_record_compression_savings_skips_empty_updates_and_normalizes_timestamp assert persisted["history"][-1]["timestamp"] == "2026-03-27T12:34:00Z" +def test_savings_tracker_save_does_not_flock_target_inode_before_replace(tmp_path, monkeypatch): + path = tmp_path / "proxy_savings.json" + tracker = SavingsTracker(path=str(path)) + + tracker.record_request( + model="gpt-4o", + input_tokens=120, + tokens_saved=10, + timestamp="2026-03-27T09:00:00Z", + ) + assert path.exists() + + flock_calls: list[int] = [] + + class _FcntlSpy: + LOCK_EX = 1 + LOCK_UN = 2 + + def flock(self, _fh, operation: int) -> None: + flock_calls.append(operation) + + monkeypatch.setattr(savings_tracker_module, "_HAS_FCNTL", True, raising=False) + monkeypatch.setattr(savings_tracker_module, "_fcntl", _FcntlSpy(), raising=False) + + tracker.record_request( + model="gpt-4o", + input_tokens=80, + tokens_saved=5, + timestamp="2026-03-27T09:10:00Z", + ) + + assert flock_calls == [] + persisted = json.loads(path.read_text(encoding="utf-8")) + assert persisted["lifetime"]["tokens_saved"] == 15 + + def test_litellm_resolution_and_savings_estimation_fallbacks(monkeypatch): def fake_cost_per_token(*, model, prompt_tokens, completion_tokens): if model in {"gpt-4o", "anthropic/claude-sonnet-4-6"}: