Skip to content

api: share_memory fork mode + fork_count/mem_locked on Instance#220

Closed
sjmiller609 wants to merge 9 commits into
hypeship/uffd-firecracker-wiringfrom
hypeship/fork-share-memory-api
Closed

api: share_memory fork mode + fork_count/mem_locked on Instance#220
sjmiller609 wants to merge 9 commits into
hypeship/uffd-firecracker-wiringfrom
hypeship/fork-share-memory-api

Conversation

@sjmiller609
Copy link
Copy Markdown
Collaborator

Summary

Exposes the uffd/template-fork machinery through the existing POST /instances/{id}/fork endpoint, without introducing a new /templates resource.

  • ForkInstanceRequest.share_memory: bool — when true, the source instance is auto-promoted (idempotently) into a fan-out parent and the fork attaches to its mem-file instead of copying. Subsequent share_memory forks against the same source reuse the same registry entry.
  • Instance.fork_count: int and Instance.mem_locked: bool — read-only fields so clients can introspect lock state on the existing GetInstance response.
  • While mem_locked = true, start/restore/delete on the source return 409 (ErrInvalidState) until all share_memory forks are deleted. templateGuard now returns ErrInvalidState rather than ErrNotSupported so the lock surfaces as a transient 409, not a 501 capability gap.
  • Validation: share_memory is incompatible with template_id and with from_running=true (the latter would re-restore the source after locking).

The lib/templates package and TemplateID field stay as internal machinery — no public templates resource. share_memory=false forks are unchanged and may still be created against a locked source.

Test plan

  • go vet ./... clean
  • New unit tests in lib/instances/share_memory_test.go:
    • TestValidateForkRequest_ShareMemoryConflicts — rejects share_memory + template_id and share_memory + from_running
    • TestEnsureShareMemoryTemplate_AutoPromoteAndReuse — first call promotes, second reuses
    • TestEnsureShareMemoryTemplate_RejectsNonStandby — non-Standby source returns ErrInvalidState
    • TestTemplateGuard_ReturnsInvalidStateNotUnsupported — guard now maps to 409
    • TestHydrateForkLockState — fork_count/mem_locked reflect refcount changes
  • CI on green firecracker E2E + cross-hypervisor regression

sjmiller609 and others added 9 commits May 8, 2026 12:48
Introduces a templates package and Standby-instance promotion path so
later PRs can wire fork-from-template into firecracker. The registry
persists one JSON file per template under <data>/templates/<id>/, with
fork refcount accounting and Delete-blocked-when-in-use.

The instance manager owns the registry because template lifecycle is
coupled to instance lifecycle: promotion guards on Standby+HasSnapshot,
deletion clears IsTemplate on the source instance, and forks (PR 3)
will increment the refcount.

Hypervisor wire-up is deferred — this PR ships the registry, the
StoredMetadata flags, and the manager helpers (templateGuard,
templateForFork, validateForkResolvedFromTemplate) so PR 3 can plug in.
When ForkInstanceRequest.TemplateID is set, the fork resolves the source
instance from the templates registry, skips the per-fork mem-file copy,
and installs a symlink to the template's snapshot mem-file instead.
firecracker mmaps the symlinked mem-file MAP_PRIVATE during restore, so
many concurrent forks COW from the same backing file rather than each
holding a private copy.

Also wires:
  - templateGuard on Start/Restore so a template parent is never resumed
    while live forks share its mem-file.
  - Refcount lifecycle: bump on fork creation, decrement on fork delete.
  - Delete safety: deleting a template instance refuses while ForkCount>0
    via templates.ErrInUse.
  - forkvm.CopyOptions.SkipRelPaths so callers can opt out of specific
    files in the source dir without breaking the existing copy semantics.
Adds lib/uffd, a userfaultfd page server that backs many concurrent
fan-out forks against a single read-only template mem-file instead of
letting each fork mmap it privately. Firecracker connects to a per-fork
UDS, hands us its userfaultfd via SCM_RIGHTS along with a JSON
mappings handshake, and the server then services UFFD_EVENT_PAGEFAULT
events with UFFDIO_COPY reads from the template.

The Linux hot path lives behind a build tag; non-Linux builds return
ErrUnsupported so callers can fall back to MAP_PRIVATE. Cross-platform
tests cover the handshake parser and the server lifecycle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a hot-page recorder + prefetch primitive on top of the userfaultfd
page server. During a template's first warmup fork the server can
record every served page (Config.RecordHotPages); the resulting
HotPageList is stable-sorted, deduplicated, and saved to disk in a
small binary format alongside the template. Later forks call
Server.Prefetch(forkID, list) to issue UFFDIO_COPY for every recorded
page against their userfaultfd before the guest unpauses, eliminating
the fault round-trips on those addresses.

The prefetcher is installed by the platform-specific listener once the
fork's uffd has been received and registered, so callers can race
Prefetch and the fault loop safely. EEXIST/EAGAIN are tolerated the
same way the fault handler does to absorb first-touch races with
vCPUs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wires firecracker's snapshot restore through a userfaultfd-backed page
server when the fork descends from a template. Each template gets one
uffd.Server lazily started on the first fork and torn down once the
last fork is released, so non-template forks (the symlink-only path)
are unaffected.

Adds an E2E test that boots a firecracker source, standbys it,
promotes it to a template, forks against the template, and asserts:
fork reaches Running, mem-file is a symlink to the template's, refcount
bumps to 1, the per-template uffd tracker registers the fork, and on
fork delete the refcount drops and the tracker detaches.

Also adds unit coverage for the uffd tracker lifecycle (lazy start,
multi-fork share, last-release teardown, closeAll, empty-acquire
rejection).
Unix domain socket paths cap at 108 bytes (sun_path). Putting the per-
fork socket under <DataDir>/templates/<25-char-cuid>/uffd/<25-char-
cuid>.uffd blew that limit on CI runners where t.TempDir() returns long
prefixes, surfacing as "bind: invalid argument" in
TestFirecrackerForkFromTemplate.

Sockets are ephemeral, so anchoring them at /tmp/h-uffd/<templateID>/
keeps the path well under the limit regardless of how deep DataDir is.
The tracker now also rm -rfs the per-template socket dir on the last
release so we don't leak stale entries.
The previous fork-mem-file symlink looped through withSnapshotSourceDirAlias
during firecracker restore: fork/.../memory -> source/.../memory, but the
alias dance temporarily symlinks source dir -> fork dir, so resolution
ping-pongs back to fork/.../memory and trips ELOOP. Switching to a hardlink
makes the fork's mem-file resolve by inode so the temporary directory alias
no longer affects it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Firecracker enables snapshot base reuse, which renames the post-restore
snapshot dir from snapshot-latest to snapshot-base. The hardlink survives
the rename (same inode), so the test just needs the right path.
ForkInstanceRequest gains share_memory: when true, the source instance is
auto-promoted (idempotently) into a fan-out parent and the fork attaches
to its mem-file via the existing template path instead of copying. While
any share_memory fork is alive, the source is mem-locked: start/restore/
delete return 409 (ErrInvalidState) until the forks are deleted.

Instance gains read-only fork_count and mem_locked fields so clients can
introspect lock state on the existing GetInstance response without
needing a separate templates resource. The internal templates registry
stays in place but is not exposed in OpenAPI.

templateGuard now returns ErrInvalidState rather than ErrNotSupported so
the lock surfaces as a transient 409, not a 501 capability gap.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

✱ Stainless preview builds for hypeman

This PR will update the hypeman SDKs with the following commit message.

feat: api: share_memory fork mode + fork_count/mem_locked on Instance

Edit this comment to update it. It will appear in the SDK's changelogs.

hypeman-typescript studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ✅

npm install https://pkg.stainless.com/s/hypeman-typescript/8a1894a220dba7b8928dfecb720e3c85d418b61f/dist.tar.gz
hypeman-go studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

go get github.com/stainless-sdks/hypeman-go@5bf5c10c8e7abc5dd96c6185bbe931301aca0d77
hypeman-openapi studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-05-08 18:18:37 UTC

@sjmiller609 sjmiller609 force-pushed the hypeship/uffd-firecracker-wiring branch from 030b69e to 0c36cb3 Compare May 13, 2026 18:23
@sjmiller609
Copy link
Copy Markdown
Collaborator Author

Closing as obsolete — superseded by the state-machine template design in #229.

The features this PR added are now provided by the state machine intrinsically:

  • share_memory opt-in: no longer needed. In the state-machine design, the first fork from a firecracker Standby source implicitly promotes it to StateTemplate, and the fork attaches to the source's mem-file via the existing fan-out path. This is the default — there is no opt-out.
  • mem_locked: redundant with state == "Template". StateTemplate cannot wake/restore/delete while ForkCount > 0; clients already see this via the existing state field.
  • fork_count / template introspection: StoredMetadata.ForkCount already exists; exposing it as a public API field can be a follow-up if there's demand.
  • templateGuardErrInvalidState: handled by the state-machine transitions in Introduce "template" as a VM state #229.

If exposing fork_count / is_template / fork_of_template on the Instance API turns out to be useful, that should be a separate, much smaller PR.

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.

1 participant