Skip to content
This repository was archived by the owner on Oct 3, 2026. It is now read-only.

fix(snapshotter): atomic + complete layer manifest - #45

Merged
aledbf merged 2 commits into
mainfrom
fix/atomic-complete-layer-manifest
Jun 15, 2026
Merged

aledbf merged 2 commits into
mainfrom
fix/atomic-complete-layer-manifest

Conversation

@aledbf

@aledbf aledbf commented Jun 15, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

writeLayerManifest had two independent defects:

  1. Non-atomic write — os.WriteFile could leave a truncated layers.manifest on a crash mid-write, unlike the fsmeta.erofs/merged.vmdk write path which already uses temp + rename.

  2. Lost positional alignment — the digest-only format silently dropped fallback blobs (snapshot-<id>.erofs, which have no content digest). A fallback layer in the chain produced fewer lines than the device= options (mountFsMeta emits one per layer, never skipping), desyncing every layer after it. The manifest is documented as the authoritative source for VMDK layer-order verification, so it must be positionally 1:1 with device=.

Changes

  • New atomicWriteFile helper: temp file in the same dir → fsync → rename, with the temp removed on any error path. writeLayerManifest now uses it.

  • writeLayerManifest emits one line per layer in oldest-first (VMDK/device=) order:

    • sha256:<hex> for content-addressed blobs
    • blob:<basename> for fallback blobs with no content digest

    Line count now equals the device= count, so consumers detect truncation/desync instead of misaligning silently.

  • Tests for writeLayerManifest (digest-only, interleaved fallback preserves position, empty, no leftover temp) and atomicWriteFile (content/perm, overwrite without leftover temp, error on missing dir).

  • readLayersManifest (integration test) is forward-compatible — it already filters by the sha256: prefix and skips blob: lines; only its doc comment is updated to document the new format.

Also: flaky loop-detach assertion (was #46)

  • checkNoLoopDevice (differ root tests) did a single immediate FindByBackingFile check after cleanup. The kernel releases loop devices asynchronously after LOOP_CLR_FD / autoclear, causing flaky loop device ... still attached failures in TestWithStackedErofsTempMount and TestWithActiveSnapshotMountOverlay. Now polls with a 5s deadline, matching the existing mountutils.verifyNoLoopDevice / loop.verifyDetached helpers. Test-only.

Follow-up (outside this repo)

qemubox's manifest reader should be updated to recognize blob: lines to validate completeness by count. Today it would ignore them without breaking.

Testing

  • task fmt && task lint && task test — all green (lint 0 issues).
  • GOOS=linux go vet ./internal/differ/ — clean (the differ test fix only compiles under linux).
  • Integration / root tests (Docker/root) not run locally.

🤖 Generated with Claude Code

aledbf and others added 2 commits June 15, 2026 18:41
writeLayerManifest had two independent defects:

1. Non-atomic write: os.WriteFile could leave a truncated manifest on a
   crash mid-write, unlike the fsmeta/VMDK write path which already uses
   temp + rename.

2. Lost positional alignment: the digest-only format silently dropped
   fallback blobs (snapshot-<id>.erofs, no content digest), so a fallback
   layer in the chain produced fewer lines than the device= options and
   desynced every layer after it. The manifest is the authoritative source
   for VMDK layer-order verification, so it must be 1:1 with device=.

Changes:
- New atomicWriteFile helper (temp in same dir + fsync + rename, temp
  cleaned up on any error). writeLayerManifest now uses it.
- writeLayerManifest emits one line per layer in oldest-first order:
  "sha256:<hex>" for content-addressed blobs, "blob:<basename>" for
  fallback blobs. Line count now equals the device= count so consumers
  detect truncation/desync instead of misaligning silently.
- Tests for both writeLayerManifest (digest-only, interleaved fallback,
  empty, no leftover temp) and atomicWriteFile.
- readLayersManifest (integration) is forward-compatible: it already
  filters by the sha256: prefix and skips blob: lines; only its doc
  comment is updated to document the new format.

Note: qemubox's manifest reader should be updated to recognize blob:
lines to validate completeness by count; today it ignores them without
breaking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
checkNoLoopDevice did a single immediate FindByBackingFile check after
the temp mount cleanup returned. The kernel releases loop devices
asynchronously after LOOP_CLR_FD / autoclear, so the backing file can
still resolve to a device for a short window, causing flaky failures:

  loop device /dev/loop1 still attached to .../layer.erofs

(seen in TestWithStackedErofsTempMount and TestWithActiveSnapshotMountOverlay)

Poll with a 5s deadline, matching the existing mountutils.verifyNoLoopDevice
and loop.verifyDetached helpers, which already handle the same race.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.75000% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/snapshotter/commit.go 68.75% 6 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@aledbf
aledbf merged commit 98892ca into main Jun 15, 2026
6 checks passed
@aledbf
aledbf deleted the fix/atomic-complete-layer-manifest branch June 15, 2026 21:55
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant