Skip to content

fix(storage): make save and export crash-safe (fixes #282)#296

Open
sshekhar563 wants to merge 1 commit into
qbtrix:devfrom
sshekhar563:fix/crash-safe-saves-282
Open

fix(storage): make save and export crash-safe (fixes #282)#296
sshekhar563 wants to merge 1 commit into
qbtrix:devfrom
sshekhar563:fix/crash-safe-saves-282

Conversation

@sshekhar563

Copy link
Copy Markdown
Collaborator

Description

Fixes #282 — Soul save and export are not crash-safe; a crash can destroy the only copy.

The Bug

save_soul_full() and save_soul_flat() used shutil.rmtree() / unlink() to delete the existing soul directory before moving new data into place. A crash between the delete and the move destroys the only readable copy. Soul.export() used Path.write_bytes() directly, which truncates the file on open — a mid-write crash leaves a corrupt, partial archive.

The Fix

Function Before After
save_soul_full() rmtreemove temp dir → fsync → _replace_with_backup
save_soul_flat() per-file deletemove temp dir → fsync → whole-directory swap
Soul.export() Path.write_bytes() write_bytes_atomic() (temp + fsync + swap)

New crash-safe helpers in storage/file.py:

  • _replace_with_backup(source, target) — moves existing target to .bak, swaps source into place, restores .bak if swap fails
  • write_bytes_atomic(path, data) — writes to temp file, fsync, atomic replace via _replace_with_backup
  • _fsync_directory(path) — platform-aware dir fsync (no-op on Windows where os.open() on dirs raises PermissionError)
  • _fsync_tree(path) — fsyncs all files and directories under a generated soul directory

Tests Added

Replace delete-before-move with backup-then-swap pattern. Adds:

- _replace_with_backup(): atomic swap with rollback on failure

- write_bytes_atomic(): temp file + fsync + atomic replace for exports

- _fsync_directory(): platform-aware dir fsync (no-op on Windows)

- Regression tests for failed replacements and temp cleanup
@sshekhar563 sshekhar563 requested a review from prakashUXtech July 7, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant