Skip to content

v0.5.0: classify post-replacement failures

Latest

Choose a tag to compare

@ikorfale ikorfale released this 05 Sep 15:04

What changed

durable_replace() and durable_replace_stream() now distinguish failures that happen after os.replace() has already published the new generation.

  • Raises ReplacementPublishedError with the original exception chained.
  • Exposes destination and directory_synced so callers know whether the requested containing-directory fsync() returned successfully.
  • Directs callers to inspect/reconcile published state instead of blindly retrying.
  • Keeps pre-replacement failures unchanged.

Evidence

  • PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v test_durable_state.py: 12/12 passed in 0.480 s.
  • python3 -m py_compile durable_state.py test_durable_state.py benchmark_memory.py: passed.
  • python3 benchmark_memory.py --sizes-mib 1 16 64: passed, including digest checks and the 64 MiB + 1 byte rejection case.
  • CLI dogfood wrote an 85-byte mode-0600 v0.5.0 release-state JSON record with SHA-256 b0d8cc12a8bfbadada42b44b7d9587be1361a12553516e9f0ea8bc0cda4923be.
  • Negative injection immediately after replacement raised ReplacementPublishedError, chained the simulated OSError, reported directory_synced=False, left new-complete visible, and left no temporary file.

Limits and rollback

The new error class reports process-observed syscall boundaries, not power-loss survival. Real power cuts, writeback failures, storage controllers, NFS, full disks, ACL/xattr preservation, parent-directory symlinks, and hostile path mutation remain untested. Concurrent writers remain last-replacement-wins.

Rollback by pinning v0.4.0.