refactor(persistence): 24 hand-rolled atomic JSON/text writers go through utils.atomic_json_write / atomic_write_text

Each copy re-implemented temp+replace by hand and lacked one or more of
fsync, symlink preservation, atomic_replace's Windows-contention retry and
EXDEV/bind-mount fallback, mode preservation, or interrupt-safe temp
cleanup. Three (gateway/session_persistence, cron/suggestions,
agent/shell_hooks) were verbatim inlines of utils._atomic_write; two
modules defined their own directory-fsync helper, now utils.fsync_directory.
plugins/google_meet/_jsonfile.write_json_atomic is deleted (callers use the
canonical helper directly).

Behavior change: every one of these writers now fsyncs the payload, keeps a
pre-existing target's mode, cleans its temp file on BaseException, and
survives Windows AV/indexer contention and cross-device renames the way
config writes already did. cron/suggestions.json is 0600 from creation
(previously chmod'ed after the replace). Skipped on purpose: cron/jobs.py
two-phase staging, gateway/status._write_json_excl (create-only lock),
kanban_transfer staging (not atomic writers); tools/skill_usage.
_write_suppressed_names lives inside a PLUGIN-COMPAT block.
This commit is contained in:
teknium1
2026-09-12 20:12:12 -07:00
committed by Teknium
parent 2be8e6147a
commit 3ef8b384a9
33 changed files with 151 additions and 300 deletions
+5 -24
View File
@@ -10,10 +10,11 @@ from __future__ import annotations
import json
import os
import re
import tempfile
import time
import uuid
from contextlib import contextmanager
from utils import atomic_json_write, fsync_directory
from pathlib import Path
from typing import Any
@@ -73,25 +74,14 @@ def _root(home: Path | str) -> Path:
return Path(home).resolve() / "runtime" / DELIVERY_DIR_NAME
def _fsync_dir(path: Path) -> None:
# Windows cannot open directories with os.open; file fsync still applies.
if os.name == "nt":
return
fd = os.open(path, os.O_RDONLY)
try:
os.fsync(fd)
finally:
os.close(fd)
@contextmanager
def _locked(home: Path | str):
root = _root(home)
root.parent.mkdir(parents=True, exist_ok=True)
root.mkdir(mode=0o700, exist_ok=True)
root.chmod(0o700)
_fsync_dir(root.parent)
_fsync_dir(root.parent.parent)
fsync_directory(root.parent)
fsync_directory(root.parent.parent)
lock = root / ".lock"
fd = os.open(lock, os.O_CREAT | os.O_WRONLY, 0o600)
os.close(fd)
@@ -107,16 +97,7 @@ def _read(path: Path) -> dict[str, Any] | None:
def _write(path: Path, record: dict[str, Any]) -> None:
fd, temporary = tempfile.mkstemp(dir=path.parent, prefix=".delivery-")
try:
with os.fdopen(fd, "w", encoding="utf-8") as stream:
json.dump(record, stream, ensure_ascii=False, sort_keys=True)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
_fsync_dir(path.parent)
finally:
Path(temporary).unlink(missing_ok=True)
atomic_json_write(path, record, indent=None, sort_keys=True, fsync_dir=True)
def deliver_to_live_owner(