Skip to content

fix(posix): write the fs-revisions copy from the stored file - #841

Open
Mortimer-RR wants to merge 1 commit into
opencloud-eu:mainfrom
Mortimer-RR:fix/fs-revisions-copy
Open

Mortimer-RR wants to merge 1 commit into
opencloud-eu:mainfrom
Mortimer-RR:fix/fs-revisions-copy

Conversation

@Mortimer-RR

Copy link
Copy Markdown

Fixes #840

Changes

  • pkg/storage/fs/posix/tree/revisions.go RestoreRevision: copies the revision into a
    temp file in the space's .oc-tmp directory, fsyncs it, carries over the target's
    user.oc.* xattrs, mode and owner (a failed chown is logged, not fatal), applies the
    revision's checksum, blob id, blob size and type attributes and the mtime, and
    rename()s it over the target, like blobstore.Upload does for uploads. The mtime is then
    set through the metadata backend so its cache picks up the new attributes. The "current"
    copy for EnableFSRevisions is unchanged.
  • pkg/storage/pkg/decomposedfs/upload/upload.go Cleanup (versionID branch): takes the
    node's metadata lock and re-reads the status through the metadata backend (bypassing the
    node's attribute cache). It only restores when the node is still processing:<this session>. Otherwise it logs, keeps the node, and leaves the revision as an ordinary
    version. The lock is released before the rest of Cleanup, whose UnmarkProcessing
    locks again through a different node object.

Tests

  • pkg/storage/fs/posix/tree/revisions_test.go:
    • restore keeps the node metadata and applies the revision's blob metadata;
    • a failed copy leaves the target untouched (fails on main: target truncated to 0 bytes);
    • concurrent readers only ever see the old or the new content (fails on main: 3831 of
      3841 reads saw partial content).
  • pkg/storage/pkg/decomposedfs/upload_async_test.go, "two uploads overwrite an existing
    file in parallel": the older upload fails after the newer one succeeded; the newer content
    and the original version must survive (fails on main: the file is rolled back from 20
    to 10 bytes).
  • go test -race clean on pkg/storage/fs/posix/tree, pkg/storage/pkg/decomposedfs and
    its upload package. pkg/storage/... passes.
  • End to end (OpenCloud, posix): restores under load with 10 readers had 0 bad downloads
    (upstream: 50 of 50 bad); aborting the older upload keeps the newer content (upstream:
    rolled back).

Note: downloads still don't take the node lock; with the atomic rename they don't need it
for consistent bytes (an open file descriptor keeps the old inode).

🤖 Generated with Claude Code

With enable_fs_revisions, Tree.WriteBlob passes a copy target to
Blobstore.Upload to keep the "current" version of the file. On the
default code path Upload renames the upload source into place first and
then tried to open the source again to write that copy. The open fails,
so WriteBlob returned an error after the blob was already stored, and
finalizing the upload failed, leaving the file stuck in processing.

Read the copy from the node's file instead, and fsync and close it with
error checks.

canUseRenameForUpload was a plain bool written by concurrent uploads
when a rename failed with EXDEV. Make it an atomic.Bool and use one
value per upload.

Assisted-by: Claude:claude-opus-5-5
@Mortimer-RR
Mortimer-RR force-pushed the fix/fs-revisions-copy branch from 9f10bb6 to 9b09e83 Compare October 9, 2026 04:05
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.

posix: with enable_fs_revisions every upload fails at finalize

1 participant