Skip to content

Releases: ikorfale/durable-state-write

v0.5.0: classify post-replacement failures

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.

v0.4.0: fail closed on non-regular destinations

Choose a tag to compare

@ikorfale ikorfale released this 04 Sep 15:04

What changed

  • Existing symlinks, dangling symlinks, FIFOs, directories, and other non-regular destinations are rejected instead of silently replacing the directory entry.
  • Existing regular-file mode preservation now uses lstat(), so the leaf is not followed while selecting permissions.
  • Corrected stale benchmark wording: peak RSS comes from Python resource.getrusage, not GNU time.

Evidence

  • PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v test_durable_state.py: 9/9 passed in 0.482 s on Python 3.12.3/Linux.
  • python3 -m py_compile durable_state.py test_durable_state.py benchmark_memory.py: passed.
  • git diff --check: passed.
  • python3 benchmark_memory.py --sizes-mib 1 16 64: passed; all SHA-256 checks matched, and 64 MiB + 1 byte preserved the old file with zero temp residue.
  • CLI dogfood wrote an 83-byte mode-0600 JSON state file. A symlink destination then exited 2, preserved the link and target digest, and left zero matching temporary files.

Limits and rollback

This remains a tested prototype for trusted local containing directories. It does not establish power-loss guarantees, NFS/full-disk behavior, parent-directory symlink safety, or safety under hostile concurrent path mutation. Concurrent writers remain last-replacement-wins. Roll back by pinning v0.3.0.

v0.3.0: reproducible peak-RSS evidence

Choose a tag to compare

@ikorfale ikorfale released this 02 Sep 15:06

Adds benchmark_memory.py, a dependency-free Linux benchmark that compares the working-tree CLI with v0.1.1 using sparse 1, 16, and 64 MiB inputs. It verifies output SHA-256 values and exercises the 64 MiB + 1 negative case.\n\nObserved on Linux/Python 3.12.3 in the clean release commit: at 64 MiB, v0.1.1 peaked at 84,352 KiB RSS and v0.3.0 at 20,688 KiB. For 64 MiB + 1, both exited 2, preserved the old destination, and left no temporary files; peaks were 84,352 KiB and 20,560 KiB. Unit suite: 8/8 passed in 0.466 s.\n\nThe first benchmark draft assumed GNU time, which was absent in the dogfood environment. The released benchmark uses Python resource.getrusage in an isolated worker instead. Measurements are same-host observations, not fixed guarantees or throughput results. Power-loss, NFS, full-disk, ACL/xattr, locking, idempotency, and consensus guarantees remain explicitly out of scope.

v0.2.0: bounded-memory streamed input

Choose a tag to compare

@ikorfale ikorfale released this 01 Sep 15:03

The CLI now copies stdin in chunks of at most 1 MiB instead of buffering the full permitted input. Oversized input still fails before replacement, preserving an existing destination and cleaning the temporary file.

Evidence on Linux/Python 3.12:

  • python3 -m py_compile durable_state.py test_durable_state.py passed.
  • python3 -m unittest -v test_durable_state.py passed 8 tests in 0.474s.
  • Real piped JSON dogfood produced a valid 50-byte mode-0600 file.
  • A 64 MiB+1 pipe exited 2, preserved the old destination, and left no temporary file.

Limitations remain explicit: no physical power-cut testing, NFS/full-disk/ACL/xattr coverage, locking, or lost-update prevention. Roll back by pinning v0.1.1 or removing the copied helper.

v0.1.1: explicit concurrent-writer contract

Choose a tag to compare

@ikorfale ikorfale released this 31 Aug 15:02

This bounded increment turns the previously untested concurrent-writer limitation into an explicit contract and regression test.

Evidence on Linux/Python 3.12:

  • python3 -m unittest -v test_durable_state.py: 6 tests passed in 0.481s.
  • Eight synchronized writer processes each submitted a distinct 128 KiB generation; all exited 0, the final file exactly matched one submitted payload, and no completed-process temp files remained.
  • Real CLI dogfood wrote a 50-byte JSON state file with mode 0600.
  • Negative case: 64 MiB + 1 byte stdin exited 2 and created no destination.

Concurrent writers are deliberately last-replacement-wins with unspecified ordering. This is not read-modify-write protection and does not prevent lost updates. Power-loss durability, NFS, full disks, ACLs/xattrs, and symlink policy remain untested/unsupported boundaries.

v0.1.0 — bounded tested prototype

Choose a tag to compare

@ikorfale ikorfale released this 31 Aug 05:09

First useful release.

  • Atomic same-directory replacement with file and directory fsync barriers
  • Permission preservation and stdin CLI
  • Five dependency-free regression tests

Limitations: process-crash tests are not power-cut tests; no guarantee is claimed for NFS, full disks, concurrent writers, ACLs/xattrs, symlinks, or storage-controller failures.