Skip to content

pool: allow live writable and attach-only volumes - #383

Merged
CMGS merged 5 commits into
cocoonstack:mainfrom
minatoaquaMK2:review/live-volume-modes-20261027
Oct 29, 2026
Merged

CMGS merged 5 commits into
cocoonstack:mainfrom
minatoaquaMK2:review/live-volume-modes-20261027

Conversation

@minatoaquaMK2

@minatoaquaMK2 minatoaquaMK2 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

This is the live-volume follow-up to #382 in the #300 series, following the split agreed in #379. It depends on #382 and stays draft until that PR merges. The base is main, so GitHub also shows the catalog changes. Review only the live-volume changes.

Why

Controllers need to attach and detach writable disks on a running sandbox. Per-volume attach_only lets them mix mounted disks with devices whose mounts they manage themselves.

What changed

  • Extend live PUT and PATCH to all four modes, with explicit attach_only in the API and Go and Python SDKs.
  • Clear a mounted writer's dirty marker only after a clean writable unmount and confirmed host detach. Release the admission hold after detach and marker handling.
  • Reject new live RW attachments in replicating pools and while replica references remain. Recheck holds before publishing a replica to close the reverse race.
  • Accept guest mountinfo rows with an empty mount source, which previously blocked live volume operations.

Failed detach-result writes can leave an old attachment in the claims journal. Restart recovers its conservative hold; a successful retry clears the reference. converging means a volume request is active.

Scope

Disk lifecycle and the combined controller sleep/wake example remain separate. Attach-only writers have no automatic dirty-marker protection. Changing a held volume's mode or mount requires removal first.

Upgrade the daemon and SDKs together. Older daemons can ignore the per-entry flag, and older Python clients reject it when callers reuse response entries in requests. The old claim-wide flag and saved empty-mount records remain supported.

Verification

The composed controller head 636c8d7 contains this live daemon implementation and passes sandboxd and e2e race tests. The two-restart regression passes with the live detach interface. A failed result write recovers a conservative reader. An empty-target retry permits a writer, and the next restart recovers one writer without another attach.

Go SDK race tests and 266 Python tests passed on f7e5d12; the SDK and example trees are unchanged at 636c8d7.

The prior composed-stack run at b3a5e4b passed all four modes on Linux KVM with Cocoon v0.7.3 and Cloud Hypervisor v54. The SDK example completed detach, hibernate, daemon restart, wake, attach, and mount with disk identity and data preserved and no writer errors. Busy unmount and a simulated lost detach response kept workload access closed. That single-node run predates this rebase. The final controller head still needs a new VM run. Distributed reservations and access gating across nodes remain unverified.

Performance

These historical measurements remain from dc7a4e3; they have not been rerun for this head.

Compared main cf9280e, catalog 09bb47b, and live dc7a4e3 on Linux amd64, Go 1.27.1, GOMAXPROCS=1, pinned to CPU 48. Five alternating two-second samples used 5,000 claims and 5,000 checkpoint references. Values are microseconds, median [min, max].

Operation main cf9280e Catalog 09bb47b Live dc7a4e3
Full replica scan, no volume owner 649.0 [627.6, 1017.2] 677.2 [641.1, 794.0] 681.9 [642.8, 706.5]
Full replica scan, one volume owner 673.0 [627.9, 990.0] 670.3 [642.4, 677.7] 679.9 [644.1, 695.1]
Update one row and commit, fsync off 411.9 [407.8, 613.3] 412.8 [401.8, 417.7] 408.3 [402.8, 416.6]
RO resolve, reserve, and release Not measured 1.393 [1.371, 1.400] 1.383 [1.382, 1.424]

Each scan checks all 5,000 states and uses 459,176 bytes and 20 allocations on every version. Commit uses about 43 KB and 19 allocations. Admission uses 584 bytes and six allocations on both measured versions. The overlapping scan ranges do not establish a stable difference. These runs do not measure fsync latency or end-to-end live attachment.

@CMGS

CMGS commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

These changes look right in direction:

  • live writable and attach-only attachments in all four modes;
  • attach_only as an explicit per-entry field. An empty mount in a request means the default mount, so the flag has to be on the wire;
  • 409 in replicating pools and 503 while a replica or recovery checkpoint is being retired;
  • the dirty marker cleared only after a clean writable unmount and a confirmed host detach;
  • the fix for mountinfo rows with an empty mount source.

Two notes for when this is rebased on the slimmed #382:

  • The claimStore volume tables from pool: register runtime volume catalog entries #382 should not survive into this PR. The reasons are in my pool: register runtime volume catalog entries #382 comment. For live writers in particular, a lost attach intent cannot leave a device attached after a crash, because cocoon does not record hot-attached disks and a cold boot rebuilds from the VM record. Main's commit-before-attach and detach-before-release ordering is enough, and the live writer intent needs no forced fsync.
  • hasCheckpointReferences for refusing a live writer while checkpoint references remain is fine to keep.

@minatoaquaMK2
minatoaquaMK2 force-pushed the review/live-volume-modes-20261027 branch 2 times, most recently from 7167a2f to 8d963fd Compare October 29, 2026 01:03
@CMGS
CMGS force-pushed the review/live-volume-modes-20261027 branch from 8d963fd to 96f9abd Compare October 29, 2026 06:42
@minatoaquaMK2
minatoaquaMK2 marked this pull request as ready for review October 29, 2026 06:45

Copy link
Copy Markdown
Contributor Author

Thanks for the catalog follow-ups in #395. I've added 8661848 on top of 96f9abd to fix a marker race found during the next review.

An old live detach could pass its final check, then pause before removing .dirty. Release could remove the VM and free its hold, allowing a new writer in. The old unlink would then remove the new writer's marker.

The clear now takes one temporary writer reference under the same lock as the final check and returns it when the file operation finishes. Release, reap and ownership-transfer cleanup release their own references. File I/O stays outside the manager lock. This adds no journal state or fsync and leaves the empty-volume release path unchanged.

On 8661848, 14 deterministic schedules passed 20 times each under -race, including rollback, removal retry and the ownership-transfer cleanup from #395. Mutations that omit or double-release the temporary reference fail the checks. The committed test covers marker unlink failure and retry. Sandboxd, e2e and Go SDK race tests, 255 Python tests, vet and Linux/Darwin lint passed. These checks use real marker files with a fake device engine; I haven't rerun a real VM on this head.

I also compared 96f9abd with 8661848 in eight alternating runs per side, using Go 1.27.1, GOMAXPROCS=2 and ext4. Values below are median [min, max].

Measurement 96f9abd 8661848
Clean-detach bookkeeping, marker I/O stubbed 76.8 ns [47.3, 77.0] 143.4 ns [90.0, 148.8]
Manager RW attach followed by remove, real marker and claim files 2.010 ms [1.816, 2.375] 1.968 ms [1.821, 2.285]
Allocations per Manager cycle 161 161

Each side completed 4,800 Manager cycles without errors, checking marker creation and removal on every cycle. Claim fsync used the default off setting; marker directory sync still ran. The extra bookkeeping costs about 67 ns with no heap allocation. The complete cycle's difference is within run variation. Separate CPU profiles show file operations dominate that cycle. These measurements use a fake VM engine and do not measure VM latency or saturated lock contention.

minatoaquaMK2 and others added 5 commits October 29, 2026 15:14
Extend live mutations and SDKs with explicit per-volume attachment modes. Keep dirty-marker cleanup tied to engine proof and reject writer admission while replica recovery remains possible, using the catalog and main claim ordering without a volume-reference ledger.
Remove the recovery-only reference description after the volume ledger was deleted. Pending or detaching entries still require an explicit caller retry when no request is active.
Acquire an operation reference with the final run check so concurrent claim teardown cannot admit a new writer before the old host unlink finishes. Keep that reference local and release it on both success and unlink failure.
One-line DetachVolume and WithVolumesAttachOnly docs, keyed volumeMount fields, a named admission helper instead of an inline closure, no slice copy for claims without the legacy attach-only flag, attach_only named in shape-change errors, and two doc lines aligned with the code.
Remove the e2e marker test that repeated the pool test and its fake mount tracking, fold the empty-source mountinfo case into the engine table and keep one malformed row, restore read-only mounts in the cancel and gate tests, pass subtest t into replica table closures, and fold single-row and repeated Python cases.
@CMGS
CMGS force-pushed the review/live-volume-modes-20261027 branch from 8661848 to d0612c6 Compare October 29, 2026 07:18
@CMGS
CMGS merged commit 4bd1d78 into cocoonstack:main Oct 29, 2026
2 checks passed
CMGS added a commit that referenced this pull request Oct 29, 2026
#383 refuses publishing a replica row while volume holds exist, so archived claims no longer need to count volumes; the adoption, restart, transfer and reap bookkeeping and the legacy-row tests go. Compat removals (claim-wide volumes_attach_only, journal attach_only migration, per-call cocoon v0.7.2 gate) are scheduled for a later release.
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.

2 participants