Skip to content

fix: preserve project saves across concurrent writes and failed closes - #377

Merged
jub0t merged 1 commit into
jub0t:mainfrom
faelsete:fix/ordered-project-save-and-close
Oct 17, 2026
Merged

jub0t merged 1 commit into
jub0t:mainfrom
faelsete:fix/ordered-project-save-and-close

Conversation

@faelsete

Copy link
Copy Markdown
Contributor

Summary

Fixes #375.

Two connected data-loss paths are addressed:

  • Every project save used the same concat.json.saving file, while the GUI dispatches saves to a three-worker pool. Concurrent writes can tear the manifest; unique temporary files alone still allow an old snapshot to replace a new one.
  • Window-close handlers ignored a failed final save and quit anyway.

Changes

  • Exclusively create a per-attempt temporary file, flush it, atomically replace the manifest, and remove only our own temporary on error.
  • Serialize writes per canonical project directory and order snapshots using a ticket captured on the UI thread before dispatch. Superseded snapshots are distinguishable from write failures.
  • Use project-scoped callbacks so a result from an earlier project cannot update the next project UI.
  • Return the final save outcome from close_project; keep the window/session open on failure and cancel project jobs only after a successful save.
  • Schedule an update relaunch only after the close save succeeds. The installer may already have replaced the on-disk bundle; this change does not roll that back.

Regression evidence

  • An isolated copy of the original Rust save code, with test-only scheduling hooks, fails deterministically for a torn shared temporary. Original code with only unique temporaries also fails a separate out-of-order snapshot assertion. These are instrumented source tests, not an unmodified GUI reproduction.
  • A real Session and filesystem integration test forces a manifest rename error using a directory at the destination. The session retains its edit; retry saves and reopens it successfully. Replacing only the tested exit gate with unconditional leave makes the same test fail.
  • A real-session delayed-autosave test verifies that the newer close snapshot survives an older worker completing afterward.
  • Concurrent writers, superseded tickets, genuine errors, manifest preservation and temporary cleanup are covered in host tests.

Validation of this exact public commit

Commit: e75af9b.
Executed in a separate clean worktree on Apple Silicon/macOS 27.0.1, Rust 1.93.1, Homebrew FFmpeg 8.1.1:

  • cargo fmt --all --check: passed.
  • cargo clippy --locked --workspace --all-targets -- -D warnings: passed.
  • cargo test --locked --workspace: 782 passed, 0 failed, 1 ignored (external-model transcription test).

Limits

  • Ticket ordering is process-local. Unique temporary files prevent shared-byte corruption across processes, but two independent app/CLI instances can still overwrite each other with stale snapshots. No cross-process locking is claimed.
  • The per-project registry retains one small entry per saved directory for the process lifetime.
  • Tests exercise real session/filesystem I/O and the exit decision, not end-to-end Slint window interactions or physical disk-full/power-loss scenarios.
  • This does not address switching to another project, recovery of partially malformed documents, or all other editor bugs.

Developed with AI assistance, reviewed and tested locally. Maintainer review is welcome, including suggestions to split the change. Commits are signed off under the project CLA/DCO.

Signed-off-by: Rafael Fernandes <17238930+faelsete@users.noreply.github.com>
@jub0t
jub0t merged commit 1b7e8e6 into jub0t:main Oct 17, 2026
1 check passed
jub0t added a commit that referenced this pull request Oct 17, 2026
…g, not traps the window (#375)

#377 keeps the window open when the final save fails, so edits are not
thrown away. But nothing then let the person leave: a project on an
unplugged drive, a full disk or a read-only folder made a window that
would never close. The title bar, Alt+F4, File > Close Window and the
version switch now offer "Quit without saving?" when the save is
refused, and only that answer lets the project go unsaved.

On Windows a scanner, OneDrive or a backup tool holding concat.json for
a moment refused the replace; the save now retries it for about a
second and a half before it calls the save failed.

Refs #375

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

GUI exits even when the final project save fails

2 participants