Skip to content

fix(posix): restore revisions atomically and guard upload rollback - #839

Open
Mortimer-RR wants to merge 1 commit into
opencloud-eu:mainfrom
Mortimer-RR:fix/atomic-restore-revision
Open

Mortimer-RR wants to merge 1 commit into
opencloud-eu:mainfrom
Mortimer-RR:fix/atomic-restore-revision

Conversation

@Mortimer-RR

@Mortimer-RR Mortimer-RR commented Oct 2, 2026 •

Copy link
Copy Markdown

Fixes #838

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

RestoreRevision in the posix tree truncated the live file and copied the
revision into it in place. Concurrent readers could see an empty or
partially restored file, and a failed copy left the file truncated
while its xattrs still described the previous content.

The posix RestoreRevision now copies the revision into a temp file in
the space's .oc-tmp directory, fsyncs it, carries over the target's oc
xattrs, mode and owner, applies the revision's checksum, blob id, blob
size and mtime, and renames it over the target, like the blobstore does
for uploads.

Separately, rolling back an aborted upload (Cleanup with a versionID)
doesn't check that the session still owns the node. An older upload
whose postprocessing fails after a newer upload has finished therefore
reverts the node to the old version, replacing the newer content.
Cleanup now locks the node, checks that it is still processing this
session, and only then calls RevertUpload, under the same lock (the
metadata lock is re-entrant for the node object). Otherwise it keeps
the node and leaves the revision as an ordinary version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mortimer-RR
Mortimer-RR force-pushed the fix/atomic-restore-revision branch from 3c8a465 to e21625a Compare October 3, 2026 02:28
@Mortimer-RR

Copy link
Copy Markdown
Author

Resolved the merge conflict

@micbar

micbar commented Oct 3, 2026

Copy link
Copy Markdown
Member

I approved the CI run.

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: version restore overwrites the live file in place, and an aborted upload can roll back newer content

2 participants