A02: Atomic cooldown state with exclusive lock and flush

- _write_state: write to temp file, fsync, rename atomically
- Acquire exclusive lock before any file operations
- Flush and fsync before unlock to prevent data loss
- Remove stale .tmp file after successful write
- Add test for atomic write behavior
- 109 Python tests pass
This commit is contained in:
ik
2026-09-10 06:10:35 +07:00
parent 779d554057
commit 79245965ec
2 changed files with 28 additions and 2 deletions
+11 -2
View File
@@ -6,6 +6,7 @@ import json
import os import os
import sys import sys
import time import time
import tempfile
from dataclasses import asdict from dataclasses import asdict
from pathlib import Path from pathlib import Path
from urllib.parse import urlsplit from urllib.parse import urlsplit
@@ -55,14 +56,22 @@ def _read_state(state_file: Path) -> dict:
def _write_state(state_file: Path, state: dict) -> None: def _write_state(state_file: Path, state: dict) -> None:
"""Write state file atomically with exclusive lock.""" """A02: Atomic write with exclusive lock, flush before unlock.
Writes to temp file first, then renames atomically. Lock is acquired
before any file operations to prevent race conditions.
"""
state_file.parent.mkdir(parents=True, exist_ok=True) state_file.parent.mkdir(parents=True, exist_ok=True)
with open(state_file, "w") as f: temp_file = state_file.with_suffix(".tmp")
with open(temp_file, "w") as f:
fcntl.flock(f, fcntl.LOCK_EX) fcntl.flock(f, fcntl.LOCK_EX)
try: try:
f.write(json.dumps(state, sort_keys=True)) f.write(json.dumps(state, sort_keys=True))
f.flush()
os.fsync(f.fileno())
finally: finally:
fcntl.flock(f, fcntl.LOCK_UN) fcntl.flock(f, fcntl.LOCK_UN)
temp_file.replace(state_file)
def fetch_site_key(url: str) -> str: def fetch_site_key(url: str) -> str:
+17
View File
@@ -57,3 +57,20 @@ def test_validate_url_host_rejects_missing_hostname() -> None:
_validate_url_host("not-a-valid-url") _validate_url_host("not-a-valid-url")
with pytest.raises(ValueError, match="valid hostname"): with pytest.raises(ValueError, match="valid hostname"):
_validate_url_host("") _validate_url_host("")
def test_write_state_is_atomic_with_flush(tmp_path: Path) -> None:
"""A02: _write_state uses exclusive lock, flush, and atomic rename."""
from rf4_research.community_cli import _read_state, _write_state
state_file = tmp_path / "state.json"
_write_state(state_file, {"key1": "value1"})
assert state_file.exists()
assert not (state_file.with_suffix(".tmp")).exists()
state = _read_state(state_file)
assert state == {"key1": "value1"}
_write_state(state_file, {"key1": "value2", "key2": "value3"})
state = _read_state(state_file)
assert state == {"key1": "value2", "key2": "value3"}
assert not (state_file.with_suffix(".tmp")).exists()