From 58711f2c71b36f1e44c73ccd7be701f9463e76e0 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 17 Jul 2026 17:16:10 -0600 Subject: [PATCH 1/5] perf(firecracker): auto-default snapshot restore to UFFD when the host allows it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MICROVM_MEM_BACKEND unset now resolves via a host probe instead of hard-defaulting to File: if /proc/sys/vm/unprivileged_userfaultfd is 1, from_env picks the Uffd backend (VM resumes immediately, pages fault in lazily) instead of File (FC reads the entire guest-RAM file before resume — ~150ms for a 4GiB guest per the uffd module's estimate). The sysctl is the probe — not a userfaultfd(2) attempt in this process — because the uffd is created by the Firecracker process itself; a jailed, uid-dropped FC can be weaker than this process, so a syscall probe here would over-approximate. Sysctl off → File plus a one-line stderr warning naming the sysctl and the explicit override. Behavior default change (from_env only): capable hosts silently gain lazy restores. An explicit MICROVM_MEM_BACKEND always wins in both directions, and MemBackend::default() stays File so programmatically built configs are unchanged. --- README.md | 2 +- src/adapters/firecracker.rs | 95 ++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7e95042..7f4e49a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ provider.destroy_vm("vm-1")?; | `MICROVM_FIRECRACKER_STATE_DIR` | `/var/lib/microvm/state` | Per-VM state dir | | `MICROVM_FIRECRACKER_VCPU` | `1` | Default vCPU count | | `MICROVM_FIRECRACKER_MEM_MIB` | `256` | Default memory size | -| `MICROVM_MEM_BACKEND` | `file` | Snapshot-restore memory backend: `file` (FC reads the whole mem file before resume) or `uffd` (userfaultfd handler pages memory in on demand) | +| `MICROVM_MEM_BACKEND` | auto | Snapshot-restore memory backend: `file` (FC reads the whole mem file before resume) or `uffd` (userfaultfd handler pages memory in on demand). Unset, the config probes `/proc/sys/vm/unprivileged_userfaultfd`: `1` → `uffd`, else `file` with a warning. Set `uffd` explicitly if Firecracker runs with CAP_SYS_PTRACE on a host with the sysctl off. | ## License diff --git a/src/adapters/firecracker.rs b/src/adapters/firecracker.rs index 758c467..e7db0ce 100644 --- a/src/adapters/firecracker.rs +++ b/src/adapters/firecracker.rs @@ -37,6 +37,12 @@ const DEFAULT_SOCKET_READY_TIMEOUT_MS: u64 = 5_000; const UFFD_SOCKET_BASENAME: &str = "uffd.sock"; /// Guest-memory backend Firecracker uses on `PUT /snapshot/load`. +/// +/// The `Default` impl is [`MemBackend::File`] — the always-works choice for +/// programmatically constructed configs. [`FirecrackerConfig::from_env`] +/// is smarter: with `MICROVM_MEM_BACKEND` unset it probes the host (see +/// [`host_allows_unprivileged_uffd`]) and picks `Uffd` when the spawned +/// Firecracker will be able to create a userfaultfd. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum MemBackend { /// FC reads the whole snapshot mem file synchronously before resuming. @@ -61,13 +67,53 @@ impl std::str::FromStr for MemBackend { } } -/// Parse `MICROVM_MEM_BACKEND`. Absent → the `File` default; an invalid +/// Sysctl gating `userfaultfd(2)` for processes without CAP_SYS_PTRACE. +const UNPRIVILEGED_USERFAULTFD_SYSCTL: &str = "/proc/sys/vm/unprivileged_userfaultfd"; + +/// Whether *any* process on this host — including a jailed, privilege- +/// dropped Firecracker — can create a userfaultfd. +/// +/// The userfaultfd behind [`MemBackend::Uffd`] is created by the +/// **Firecracker process** (it hands the fd to our handler over the UDS), +/// so probing our own process (a `userfaultfd(2)` attempt here) would +/// over-approximate: this process may hold CAP_SYS_PTRACE while the jailed +/// FC it spawns runs uid-dropped and fails at restore time. The +/// `vm.unprivileged_userfaultfd=1` sysctl is the one signal valid for +/// every FC we spawn. Hosts running FC privileged with the sysctl off can +/// still opt in explicitly via `MICROVM_MEM_BACKEND=uffd`. +/// +/// Missing file (kernel without CONFIG_USERFAULTFD, non-Linux) reads as +/// unsupported — fail-safe to the File backend. +fn host_allows_unprivileged_uffd() -> bool { + fs::read_to_string(UNPRIVILEGED_USERFAULTFD_SYSCTL) + .map(|v| v.trim() == "1") + .unwrap_or(false) +} + +/// Parse `MICROVM_MEM_BACKEND`. An explicit value always wins; an invalid /// value panics rather than silently running with the wrong backend — a /// misconfigured operator must find out at startup, not on the first slow /// restore. -fn mem_backend_from_env_value(value: Option<&str>) -> MemBackend { +/// +/// Absent → auto-detect from `uffd_usable` (the +/// [`host_allows_unprivileged_uffd`] probe, injected for testability): +/// `Uffd` when the host allows it — restores resume in ~ms instead of +/// reading the whole guest-RAM file — else `File` with a one-line warning +/// naming the sysctl that would unlock the fast path. +fn mem_backend_from_env_value(value: Option<&str>, uffd_usable: bool) -> MemBackend { match value { - None => MemBackend::default(), + None if uffd_usable => MemBackend::Uffd, + None => { + eprintln!( + "[microvm-firecracker] MICROVM_MEM_BACKEND unset and \ + {UNPRIVILEGED_USERFAULTFD_SYSCTL} != 1: using the File memory \ + backend (snapshot restore reads the entire guest-RAM file \ + before resume). Set vm.unprivileged_userfaultfd=1 — or \ + MICROVM_MEM_BACKEND=uffd if firecracker runs with \ + CAP_SYS_PTRACE — for lazy userfaultfd restores." + ); + MemBackend::File + } Some(v) => v .parse::() .unwrap_or_else(|e| panic!("MICROVM_MEM_BACKEND: {e}")), @@ -203,8 +249,9 @@ pub struct FirecrackerConfig { /// Max wait for Firecracker API socket readiness after process spawn. pub socket_ready_timeout: Duration, /// Guest-memory backend for snapshot restore. `MICROVM_MEM_BACKEND` - /// accepts `file` (default) or `uffd`; an invalid value fails loudly at - /// config load. + /// accepts `file` or `uffd`; unset, [`Self::from_env`] auto-detects + /// (`uffd` when the host permits unprivileged userfaultfd, else `file` + /// with a warning); an invalid value fails loudly at config load. pub mem_backend: MemBackend, } @@ -255,8 +302,10 @@ impl FirecrackerConfig { .filter(|v| *v > 0) .unwrap_or(DEFAULT_SOCKET_READY_TIMEOUT_MS), ); - let mem_backend = - mem_backend_from_env_value(std::env::var("MICROVM_MEM_BACKEND").ok().as_deref()); + let mem_backend = mem_backend_from_env_value( + std::env::var("MICROVM_MEM_BACKEND").ok().as_deref(), + host_allows_unprivileged_uffd(), + ); Self { binary_path, @@ -1907,15 +1956,39 @@ mod tests { } #[test] - fn mem_backend_env_absent_defaults_to_file() { - assert_eq!(mem_backend_from_env_value(None), MemBackend::File); - assert_eq!(mem_backend_from_env_value(Some("uffd")), MemBackend::Uffd); + fn mem_backend_env_absent_resolves_from_uffd_probe() { + // Probe says the host allows unprivileged userfaultfd → lazy restore. + assert_eq!(mem_backend_from_env_value(None, true), MemBackend::Uffd); + // Probe says no → fail-safe to File (with a warning on stderr). + assert_eq!(mem_backend_from_env_value(None, false), MemBackend::File); + } + + #[test] + fn mem_backend_env_explicit_value_beats_probe() { + // Operator forces uffd on a host the probe rejected (e.g. FC runs + // with CAP_SYS_PTRACE and the sysctl is off). + assert_eq!( + mem_backend_from_env_value(Some("uffd"), false), + MemBackend::Uffd + ); + // Operator forces file even though the host could do uffd. + assert_eq!( + mem_backend_from_env_value(Some("file"), true), + MemBackend::File + ); + } + + #[test] + fn mem_backend_default_stays_file_for_programmatic_configs() { + // `Default` must not probe — a hand-built config keeps the + // always-works backend unless the caller opts in. + assert_eq!(MemBackend::default(), MemBackend::File); } #[test] #[should_panic(expected = "MICROVM_MEM_BACKEND")] fn mem_backend_env_invalid_value_fails_loud() { - mem_backend_from_env_value(Some("filee")); + mem_backend_from_env_value(Some("filee"), false); } // ---- Snapshot artifact path model ---- From d6e2321a62b3c5a3a745453432f677da18870fdb Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 17 Jul 2026 17:16:40 -0600 Subject: [PATCH 2/5] perf(firecracker): exponential backoff for the API socket-ready poll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wait_for_socket_ready slept a flat 100 ms between readiness probes, but the FC API socket is typically ready well under 20 ms after exec — so nearly every spawn ate 50–90 ms of pure idle wait. Poll at 2 ms first, doubling to the old 100 ms cap (2, 4, 8, …, 100), so fast hosts are detected within single-digit ms and slow hosts converge to exactly the previous cadence. Timeout semantics unchanged. --- src/adapters/firecracker.rs | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/adapters/firecracker.rs b/src/adapters/firecracker.rs index e7db0ce..6efdf64 100644 --- a/src/adapters/firecracker.rs +++ b/src/adapters/firecracker.rs @@ -30,6 +30,23 @@ const DEFAULT_BOOT_ARGS: &str = const DEFAULT_API_TIMEOUT_MS: u64 = 5_000; const DEFAULT_SOCKET_READY_TIMEOUT_MS: u64 = 5_000; +/// First sleep between API-socket readiness probes after spawning FC. The +/// socket is usually ready within a few ms of exec, so the first re-check +/// must come fast — a flat 100 ms poll wasted 50–90 ms of pure idle wait on +/// every VM spawn. +const SOCKET_POLL_INITIAL_INTERVAL: Duration = Duration::from_millis(2); +/// Backoff cap for the readiness poll — the previous flat interval, so a +/// genuinely slow host converges to exactly the old cadence instead of +/// busy-spinning for the whole `socket_ready_timeout`. +const SOCKET_POLL_MAX_INTERVAL: Duration = Duration::from_millis(100); + +/// Next sleep in the socket-readiness backoff: double, capped at +/// [`SOCKET_POLL_MAX_INTERVAL`]. From the initial 2 ms: 4, 8, 16, 32, 64, +/// 100, 100, … +fn next_socket_poll_interval(current: Duration) -> Duration { + (current * 2).min(SOCKET_POLL_MAX_INTERVAL) +} + /// Basename of the per-VM userfaultfd socket used when /// [`MemBackend::Uffd`] is selected. Jailed VMs get it at the chroot root /// (FC connects to `/uffd.sock` post-chroot); non-jailed VMs get it in the @@ -831,6 +848,7 @@ impl FirecrackerVmProvider { fn wait_for_socket_ready(&self, socket_path: &Path) -> VmRuntimeResult<()> { let deadline = Instant::now() + self.config.socket_ready_timeout; + let mut interval = SOCKET_POLL_INITIAL_INTERVAL; while Instant::now() < deadline { if socket_path.exists() && self @@ -839,7 +857,8 @@ impl FirecrackerVmProvider { { return Ok(()); } - thread::sleep(Duration::from_millis(100)); + thread::sleep(interval); + interval = next_socket_poll_interval(interval); } Err(VmRuntimeError::Unsupported(format!( "firecracker api socket not ready within {:?}: {}", @@ -1991,6 +2010,26 @@ mod tests { mem_backend_from_env_value(Some("filee"), false); } + // ---- Socket-ready poll backoff ---- + + #[test] + fn socket_poll_backoff_doubles_then_caps_at_old_flat_interval() { + let mut interval = SOCKET_POLL_INITIAL_INTERVAL; + let mut schedule = vec![interval]; + for _ in 0..8 { + interval = next_socket_poll_interval(interval); + schedule.push(interval); + } + let ms: Vec = schedule.iter().map(|d| d.as_millis() as u64).collect(); + // 2,4,8,16,32,64 then pinned at the 100 ms cap (the old flat poll). + assert_eq!(ms, vec![2, 4, 8, 16, 32, 64, 100, 100, 100]); + // The cap is sticky: once reached the interval never moves again. + assert_eq!( + next_socket_poll_interval(SOCKET_POLL_MAX_INTERVAL), + SOCKET_POLL_MAX_INTERVAL + ); + } + // ---- Snapshot artifact path model ---- #[test] From 76f67b1800c7c5a1266b59ecdafa3796e72050f2 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 17 Jul 2026 17:17:17 -0600 Subject: [PATCH 3/5] =?UTF-8?q?perf(firecracker):=20default=20track=5Fdirt?= =?UTF-8?q?y=5Fpages=20off=20=E2=80=94=20Full=20snapshots=20have=20no=20bi?= =?UTF-8?q?tmap=20consumer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /machine-config was sent track_dirty_pages=true whenever the spec left it unset, but this adapter only ever issues snapshot_type=Full creates and enable_diff_snapshots=false loads — the dirty-page bitmap costs a write-protect fault on every first-touch guest write and nothing reads it. Default it to false; VmSpec.track_dirty_pages=Some(true) still re-enables it per-VM for externally driven diff snapshots. Behavior default change: specs that relied on the implicit true (none in-tree; diff snapshots were never issuable through this crate) must now set Some(true) explicitly. --- src/adapters/firecracker.rs | 54 ++++++++++++++++++++++++++++++++----- src/model.rs | 5 +++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/adapters/firecracker.rs b/src/adapters/firecracker.rs index 6efdf64..54f410d 100644 --- a/src/adapters/firecracker.rs +++ b/src/adapters/firecracker.rs @@ -446,6 +446,27 @@ fn move_into_place(from: &Path, to: &Path) -> VmRuntimeResult<()> { } } +/// Build the `PUT /machine-config` body. +/// +/// `track_dirty_pages` defaults to **off**: it exists solely to feed diff +/// snapshots, and this adapter only ever issues `snapshot_type: "Full"` +/// creates and `enable_diff_snapshots: false` loads — so the kernel +/// dirty-page bitmap would tax every guest write with no consumer. Set +/// [`crate::model::VmSpec::track_dirty_pages`] to `Some(true)` per-VM when +/// diff snapshots are driven externally. +fn machine_config_body( + vcpu_count: u8, + mem_size_mib: u32, + track_dirty_pages: Option, +) -> serde_json::Value { + serde_json::json!({ + "vcpu_count": vcpu_count, + "mem_size_mib": mem_size_mib, + "smt": false, + "track_dirty_pages": track_dirty_pages.unwrap_or(false) + }) +} + /// Build the `PUT /snapshot/load` body. `mem_backend` is either the `File` /// object pointing at the FC-visible mem path or the `Uffd` object pointing /// at the FC-visible handler socket (see @@ -891,13 +912,7 @@ impl FirecrackerVmProvider { let vcpu_count = spec.vcpu_count.unwrap_or(self.config.vcpu_count); let mem_size_mib = spec.mem_size_mib.unwrap_or(self.config.mem_size_mib); - let track_dirty_pages = spec.track_dirty_pages.unwrap_or(true); - let machine = serde_json::json!({ - "vcpu_count": vcpu_count, - "mem_size_mib": mem_size_mib, - "smt": false, - "track_dirty_pages": track_dirty_pages - }); + let machine = machine_config_body(vcpu_count, mem_size_mib, spec.track_dirty_pages); self.firecracker_request(socket_path, "PUT", "/machine-config", Some(machine))?; // A jailed FC resolves every path inside its chroot, where the jailer @@ -2084,6 +2099,31 @@ mod tests { assert_eq!(fs::read(&to).unwrap(), b"pages"); } + // ---- /machine-config body ---- + + #[test] + fn machine_config_defaults_track_dirty_pages_off() { + // No consumer exists for the dirty bitmap (Full snapshots only), so + // the unset spec must not pay for it. + let body = machine_config_body(2, 512, None); + assert_eq!(body["vcpu_count"], 2); + assert_eq!(body["mem_size_mib"], 512); + assert_eq!(body["smt"], false); + assert_eq!(body["track_dirty_pages"], false); + } + + #[test] + fn machine_config_track_dirty_pages_stays_settable() { + assert_eq!( + machine_config_body(1, 128, Some(true))["track_dirty_pages"], + true + ); + assert_eq!( + machine_config_body(1, 128, Some(false))["track_dirty_pages"], + false + ); + } + // ---- /snapshot/load body ---- fn snapshot_ref(resume: bool, overrides: Vec) -> SnapshotRef { diff --git a/src/model.rs b/src/model.rs index 8e06e2e..fa8f625 100644 --- a/src/model.rs +++ b/src/model.rs @@ -80,7 +80,10 @@ pub struct VmSpec { /// [`SnapshotRef::network_overrides`] to swap network interfaces on restore. pub restore_from: Option, /// Track dirty pages during execution — required to later capture diff snapshots. - /// None = enabled (FC's safer default for snapshot-friendly workloads). + /// None = disabled: the runtime only takes Full snapshots, so the kernel's + /// dirty-page bitmap would cost a write-protect fault per touched page with + /// no consumer. Set `Some(true)` only if diff snapshots are driven against + /// the Firecracker API externally. pub track_dirty_pages: Option, } From 7596eda75e096830cd2fcbe7b453150e0f8af3ca Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 17 Jul 2026 17:18:21 -0600 Subject: [PATCH 4/5] feat(rootfs): surface the clone strategy + warn once when clones fall back to full byte copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clone_file_with_mode now reports which rung of the reflink → hardlink → copy ladder a clone landed on, and the first clone that reaches the full streaming copy emits a one-time [microvm-rootfs] warning naming the remediation (btrfs / XFS reflink=1). Previously a writable rootfs on ext4 silently byte-copied the whole multi-GB image for every VM with nothing in the logs pointing at the slow path. No behavior change to the fallback order itself; the strategy enum is crate-internal. --- src/rootfs.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 3d522a7..3b38934 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -77,7 +77,7 @@ use std::fs; use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use std::process::Command; -use std::sync::Mutex; +use std::sync::{Mutex, Once}; use std::time::SystemTime; use sha2::{Digest, Sha256}; @@ -725,12 +725,30 @@ enum CloneMode { Independent, } -fn clone_file_with_mode(source: &Path, dest: &Path, mode: CloneMode) -> VmRuntimeResult<()> { +/// Which rung of the reflink → hardlink → copy ladder a clone landed on. +/// Surfaced so callers/tests can observe the effective clone cost — a +/// `FullCopy` means every VM pays a whole-image byte copy. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum CloneStrategy { + Reflink, + Hardlink, + FullCopy, +} + +/// One-shot latch for the full-copy warning: a fleet of VMs on a +/// non-reflink filesystem would otherwise emit one line per clone. +static FULL_COPY_WARNING: Once = Once::new(); + +fn clone_file_with_mode( + source: &Path, + dest: &Path, + mode: CloneMode, +) -> VmRuntimeResult { // 1. Reflink (btrfs/XFS/bcachefs/ZFS-on-Linux). `cp --reflink=always` // exits non-zero on filesystems that do not support FICLONE, so // failure is the signal to fall through, not a hard error. if try_reflink(source, dest).is_ok() { - return Ok(()); + return Ok(CloneStrategy::Reflink); } // Ensure the partial output from a failed reflink attempt is gone before // the next strategy runs — `cp` cleans up after itself, but defensively @@ -743,11 +761,26 @@ fn clone_file_with_mode(source: &Path, dest: &Path, mode: CloneMode) -> VmRuntim // through the shared inode. if mode == CloneMode::SharedOk { if fs::hard_link(source, dest).is_ok() { - return Ok(()); + return Ok(CloneStrategy::Hardlink); } let _ = fs::remove_file(dest); } + // Landing here means every clone of a multi-GB rootfs is a full byte + // copy — operationally fine, but slow enough (seconds per VM) that it + // must not stay silent. Warn once per process; the remediation is a + // reflink-capable filesystem, not a code change. + FULL_COPY_WARNING.call_once(|| { + eprintln!( + "[microvm-rootfs] cloning {} by full byte copy: the filesystem \ + supports no reflink (and hardlink sharing does not apply). Every \ + VM clone copies the entire image; host the rootfs dirs on btrfs \ + or XFS (reflink=1) for instant CoW clones. Warned once — later \ + clones stay on this path silently.", + source.display() + ); + }); + // 3. Full streaming copy. `fs::copy` uses `copy_file_range(2)` under the // hood on Linux, which handles sparse files efficiently — same path // that GNU `cp --sparse=auto` takes when reflink is unavailable. @@ -758,7 +791,7 @@ fn clone_file_with_mode(source: &Path, dest: &Path, mode: CloneMode) -> VmRuntim dest.display() )) })?; - Ok(()) + Ok(CloneStrategy::FullCopy) } fn try_reflink(source: &Path, dest: &Path) -> std::io::Result<()> { @@ -1269,6 +1302,38 @@ mod tests { assert_eq!(cfg.resize2fs_bin, PathBuf::from(DEFAULT_RESIZE2FS_BIN)); } + // ===================================================== clone strategy ==== + + #[test] + fn shared_clone_never_lands_on_full_copy_within_one_fs() { + // Source and dest share a tempdir (one filesystem), so even without + // reflink support the hardlink rung must catch a SharedOk clone — + // FullCopy here would mean the ladder is broken. + let tmp = TempDir::new().unwrap(); + let src = tmp.path().join("template.ext4"); + let dst = tmp.path().join("clone.ext4"); + fs::write(&src, b"rootfs-bytes").unwrap(); + let strategy = clone_file_with_mode(&src, &dst, CloneMode::SharedOk).unwrap(); + assert_ne!(strategy, CloneStrategy::FullCopy, "got {strategy:?}"); + assert_eq!(fs::read(&dst).unwrap(), b"rootfs-bytes"); + } + + #[test] + fn independent_clone_never_shares_the_source_inode() { + // Independent mode must skip the hardlink rung: it lands on reflink + // (new inode, CoW) or a full copy (new inode), never inode sharing. + let tmp = TempDir::new().unwrap(); + let src = tmp.path().join("template.ext4"); + let dst = tmp.path().join("clone.ext4"); + fs::write(&src, b"rootfs-bytes").unwrap(); + let strategy = clone_file_with_mode(&src, &dst, CloneMode::Independent).unwrap(); + assert_ne!(strategy, CloneStrategy::Hardlink); + use std::os::unix::fs::MetadataExt; + let (s, d) = (fs::metadata(&src).unwrap(), fs::metadata(&dst).unwrap()); + assert_ne!(s.ino(), d.ino(), "independent clone shares the inode"); + assert_eq!(fs::read(&dst).unwrap(), b"rootfs-bytes"); + } + // ============================================== clone_for_vm_with_size ==== fn template_bytes(n: usize) -> Vec { From d71aaee64c177d5e5b599fe9a68c3c74f8c7a97e Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 17 Jul 2026 17:19:36 -0600 Subject: [PATCH 5/5] chore: bump to 0.4.0-alpha.4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/adapters/firecracker.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62322f1..5fb8498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,7 +248,7 @@ dependencies = [ [[package]] name = "microvm-runtime" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "base64", "libc", diff --git a/Cargo.toml b/Cargo.toml index 4e24cb6..ee196fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microvm-runtime" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" edition = "2024" rust-version = "1.91" description = "Firecracker microVM driver for decentralized Tangle operators — pure-Rust primitive, no service, no auth, no business logic." diff --git a/src/adapters/firecracker.rs b/src/adapters/firecracker.rs index 54f410d..b5cfa8c 100644 --- a/src/adapters/firecracker.rs +++ b/src/adapters/firecracker.rs @@ -57,9 +57,9 @@ const UFFD_SOCKET_BASENAME: &str = "uffd.sock"; /// /// The `Default` impl is [`MemBackend::File`] — the always-works choice for /// programmatically constructed configs. [`FirecrackerConfig::from_env`] -/// is smarter: with `MICROVM_MEM_BACKEND` unset it probes the host (see -/// [`host_allows_unprivileged_uffd`]) and picks `Uffd` when the spawned -/// Firecracker will be able to create a userfaultfd. +/// is smarter: with `MICROVM_MEM_BACKEND` unset it probes +/// `/proc/sys/vm/unprivileged_userfaultfd` and picks `Uffd` when the +/// spawned Firecracker will be able to create a userfaultfd. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum MemBackend { /// FC reads the whole snapshot mem file synchronously before resuming.