Odysseus v1.0
This commit is contained in:
43
core/atomic_io.py
Normal file
43
core/atomic_io.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Atomic JSON file writes.
|
||||
|
||||
Use this everywhere a JSON config file is persisted. A plain `open("w") +
|
||||
json.dump` truncates the file on first write and only fills it with new
|
||||
content afterwards — a kill -9 / power loss / OOM in between produces a
|
||||
truncated or empty file. For password DBs (`auth.json`) and live state
|
||||
(`sessions.json`, `settings.json`, `integrations.json`, `cookbook_state.json`),
|
||||
that's a data-loss event.
|
||||
|
||||
`atomic_write_json` writes to a sibling tmp file, fsyncs, then `os.replace`s
|
||||
into place. On POSIX `os.replace` is atomic on the same filesystem.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def atomic_write_json(path: str, data: Any, *, indent: Optional[int] = None) -> None:
|
||||
"""Atomically persist `data` as JSON at `path`.
|
||||
|
||||
The temp file uses the live PID as a suffix so two processes saving the
|
||||
same file (e.g. unit tests) don't collide on the rename target.
|
||||
"""
|
||||
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
||||
tmp = f"{path}.tmp.{os.getpid()}"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(data, f, indent=indent)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp, path)
|
||||
|
||||
|
||||
def atomic_write_text(path: str, text: str) -> None:
|
||||
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
||||
tmp = f"{path}.tmp.{os.getpid()}"
|
||||
with open(tmp, "w") as f:
|
||||
f.write(text)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp, path)
|
||||
Reference in New Issue
Block a user