Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package firecracker for openSUSE:Factory checked in at 2026-07-07 21:03:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/firecracker (Old) and /work/SRC/openSUSE:Factory/.firecracker.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "firecracker" Tue Jul 7 21:03:59 2026 rev:24 rq:1364175 version:1.16.1 Changes: -------- --- /work/SRC/openSUSE:Factory/firecracker/firecracker.changes 2026-06-23 17:44:24.317701823 +0200 +++ /work/SRC/openSUSE:Factory/.firecracker.new.1982/firecracker.changes 2026-07-07 21:06:41.497522844 +0200 @@ -1,0 +2,15 @@ +Fri Jul 03 10:35:14 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.16.1: + * Fixed + - #5959: Reverted the use of O_NOFOLLOW for the jailer's cgroup + and network namespace file operations, so symlinks are again + allowed in these paths. + - #5958: Fixed a bug that caused vsock guest-to-host + connections to time out after snapshot restore, triggered by + taking a snapshot with a TX descriptor in-flight. On restore + the device now replays the TX queue notification so in-flight + TX descriptors are re-processed and notification suppression + is re-armed. + +------------------------------------------------------------------- Old: ---- firecracker-1.16.0.obscpio New: ---- firecracker-1.16.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ firecracker.spec ++++++ --- /var/tmp/diff_new_pack.ck37OG/_old 2026-07-07 21:06:43.393589216 +0200 +++ /var/tmp/diff_new_pack.ck37OG/_new 2026-07-07 21:06:43.397589355 +0200 @@ -17,7 +17,7 @@ Name: firecracker -Version: 1.16.0 +Version: 1.16.1 Release: 0 Summary: Virtual Machine Monitor for creating microVMs License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.ck37OG/_old 2026-07-07 21:06:43.453591316 +0200 +++ /var/tmp/diff_new_pack.ck37OG/_new 2026-07-07 21:06:43.457591456 +0200 @@ -2,7 +2,7 @@ <service name="obs_scm" mode="manual"> <param name="url">https://github.com/firecracker-microvm/firecracker.git</param> <param name="scm">git</param> - <param name="revision">v1.16.0</param> + <param name="revision">refs/tags/v1.16.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.ck37OG/_old 2026-07-07 21:06:43.493592716 +0200 +++ /var/tmp/diff_new_pack.ck37OG/_new 2026-07-07 21:06:43.501592996 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/firecracker-microvm/firecracker.git</param> - <param name="changesrevision">d83d72b710361a10294480131377b1b00b163af8</param></service></servicedata> + <param name="changesrevision">2038188f145fb81b8d098147a10e9d9f392fd22f</param></service></servicedata> (No newline at EOF) ++++++ firecracker-1.16.0.obscpio -> firecracker-1.16.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/.buildkite/common.py new/firecracker-1.16.1/.buildkite/common.py --- old/firecracker-1.16.0/.buildkite/common.py 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/.buildkite/common.py 2026-06-29 11:33:24.000000000 +0200 @@ -205,6 +205,13 @@ default=1, type=int, ) +COMMON_PARSER.add_argument( + "--max-jobs", + help="Max leaf jobs per pipeline chunk. If set, to_json() returns a JSON array of pipelines.", + required=False, + default=None, + type=int, +) def random_str(k: int): @@ -379,13 +386,61 @@ ) return self.add_step(grp, depends_on_build=depends_on_build) + def _chunk_steps(self, max_jobs: int): + """Split steps into chunks of at most max_jobs leaf jobs each. + + Groups that exceed the remaining space are split to fill each chunk. + """ + chunks = [] + current_chunk = [] + current_count = 0 + + for step in self.steps: + if isinstance(step, dict) and "group" in step: + remaining_steps = step["steps"] + while remaining_steps: + # -1 to reserve a slot for the group step itself + available = max_jobs - current_count - 1 + if available <= 0: + chunks.append(current_chunk) + current_chunk = [] + current_count = 0 + available = max_jobs - 1 + take = remaining_steps[:available] + remaining_steps = remaining_steps[available:] + current_chunk.append({**step, "steps": take}) + # +1 for the group step itself + current_count += len(take) + 1 + else: + if current_chunk and current_count + 1 > max_jobs: + chunks.append(current_chunk) + current_chunk = [] + current_count = 0 + current_chunk.append(step) + current_count += 1 + + if current_chunk: + chunks.append(current_chunk) + return chunks + def to_dict(self): """Render the pipeline as a dictionary.""" return {"steps": self.steps} def to_json(self): - """Serialize the pipeline to JSON""" - return json.dumps(self.to_dict(), indent=4, sort_keys=True, ensure_ascii=False) + """Serialize the pipeline to JSON. + + If --max-jobs is set, returns a JSON array of pipeline objects, + each with at most max_jobs leaf jobs. Otherwise, returns a single + pipeline object (legacy behavior). + """ + max_jobs = self.args.max_jobs + if max_jobs is None: + to_serialize = self.to_dict() + else: + chunks = self._chunk_steps(max_jobs) + to_serialize = [{"steps": chunk} for chunk in chunks] + return json.dumps(to_serialize, indent=4, sort_keys=True, ensure_ascii=False) def devtool_download_artifacts(self, artifacts): """Generate a `devtool download_ci_artifacts` command""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/.buildkite/pipeline_upload.sh new/firecracker-1.16.1/.buildkite/pipeline_upload.sh --- old/firecracker-1.16.0/.buildkite/pipeline_upload.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/firecracker-1.16.1/.buildkite/pipeline_upload.sh 2026-06-29 11:33:24.000000000 +0200 @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Proxy script that handles chunked pipeline upload. +# Usage: .buildkite/pipeline_pr.py --max-jobs 500 | .buildkite/pipeline_upload.sh +# +# If the input is a JSON array (chunked), uploads each element separately. +# If it's a single object, uploads it directly (backwards compatible). + +set -euo pipefail + +pipeline_json=$(cat) + +if echo "$pipeline_json" | jq -e 'type == "array"' > /dev/null 2>&1; then + count=$(echo "$pipeline_json" | jq 'length') + for ((i = 0; i < count; i++)); do + echo "$pipeline_json" | jq ".[$i]" | buildkite-agent pipeline upload + done +else + echo "$pipeline_json" | buildkite-agent pipeline upload +fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/CHANGELOG.md new/firecracker-1.16.1/CHANGELOG.md --- old/firecracker-1.16.0/CHANGELOG.md 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/CHANGELOG.md 2026-06-29 11:33:24.000000000 +0200 @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.16.1] + +### Fixed + +- [#5959](https://github.com/firecracker-microvm/firecracker/pull/5959): + Reverted the use of `O_NOFOLLOW` for the jailer's cgroup and network namespace + file operations, so symlinks are again allowed in these paths. +- [#5958](https://github.com/firecracker-microvm/firecracker/pull/5958): Fixed a + bug that caused vsock guest-to-host connections to time out after snapshot + restore, triggered by taking a snapshot with a TX descriptor in-flight. On + restore the device now replays the TX queue notification so in-flight TX + descriptors are re-processed and notification suppression is re-armed. + ## [1.16.0] ### Added diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/CREDITS.md new/firecracker-1.16.1/CREDITS.md --- old/firecracker-1.16.0/CREDITS.md 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/CREDITS.md 2026-06-29 11:33:24.000000000 +0200 @@ -189,6 +189,7 @@ - Manohar Castelino <[email protected]> - Marc Brooker <[email protected]> - Marco Cali <[email protected]> +- Marco Marangoni <[email protected]> - Marco Vedovati <[email protected]> - Markus Ziller <[email protected]> - Masatoshi Higuchi <[email protected]> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/Cargo.lock new/firecracker-1.16.1/Cargo.lock --- old/firecracker-1.16.0/Cargo.lock 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/Cargo.lock 2026-06-29 11:33:24.000000000 +0200 @@ -400,7 +400,7 @@ [[package]] name = "cpu-template-helper" -version = "1.16.0" +version = "1.16.1" dependencies = [ "clap", "displaydoc", @@ -576,7 +576,7 @@ [[package]] name = "firecracker" -version = "1.16.0" +version = "1.16.1" dependencies = [ "cargo_toml", "displaydoc", @@ -791,7 +791,7 @@ [[package]] name = "jailer" -version = "1.16.0" +version = "1.16.1" dependencies = [ "libc", "log-instrument", @@ -1188,7 +1188,7 @@ [[package]] name = "rebase-snap" -version = "1.16.0" +version = "1.16.1" dependencies = [ "displaydoc", "libc", @@ -1272,7 +1272,7 @@ [[package]] name = "seccompiler" -version = "1.16.0" +version = "1.16.1" dependencies = [ "bitcode", "clap", @@ -1360,7 +1360,7 @@ [[package]] name = "snapshot-editor" -version = "1.16.0" +version = "1.16.1" dependencies = [ "clap", "clap-num", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/cpu-template-helper/Cargo.toml new/firecracker-1.16.1/src/cpu-template-helper/Cargo.toml --- old/firecracker-1.16.0/src/cpu-template-helper/Cargo.toml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/cpu-template-helper/Cargo.toml 2026-06-29 11:33:24.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "cpu-template-helper" -version = "1.16.0" +version = "1.16.1" authors = ["Amazon Firecracker team <[email protected]>"] edition = "2024" license = "Apache-2.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/firecracker/Cargo.toml new/firecracker-1.16.1/src/firecracker/Cargo.toml --- old/firecracker-1.16.0/src/firecracker/Cargo.toml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/firecracker/Cargo.toml 2026-06-29 11:33:24.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "firecracker" -version = "1.16.0" +version = "1.16.1" authors = ["Amazon Firecracker team <[email protected]>"] edition = "2024" build = "build.rs" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/firecracker/swagger/firecracker.yaml new/firecracker-1.16.1/src/firecracker/swagger/firecracker.yaml --- old/firecracker-1.16.0/src/firecracker/swagger/firecracker.yaml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/firecracker/swagger/firecracker.yaml 2026-06-29 11:33:24.000000000 +0200 @@ -5,7 +5,7 @@ The API is accessible through HTTP calls on specific URLs carrying JSON modeled data. The transport medium is a Unix Domain Socket. - version: 1.16.0 + version: 1.16.1 termsOfService: "" contact: email: "[email protected]" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/jailer/Cargo.toml new/firecracker-1.16.1/src/jailer/Cargo.toml --- old/firecracker-1.16.0/src/jailer/Cargo.toml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/jailer/Cargo.toml 2026-06-29 11:33:24.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "jailer" -version = "1.16.0" +version = "1.16.1" authors = ["Amazon Firecracker team <[email protected]>"] edition = "2024" description = "Process for starting Firecracker in production scenarios; applies a cgroup/namespace isolation barrier and then drops privileges." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/jailer/src/env.rs new/firecracker-1.16.1/src/jailer/src/env.rs --- old/firecracker-1.16.0/src/jailer/src/env.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/jailer/src/env.rs 2026-06-29 11:33:24.000000000 +0200 @@ -519,11 +519,8 @@ fn join_netns(path: &str) -> Result<(), JailerError> { // The fd backing the file will be automatically dropped at the end of the scope - let netns = OpenOptions::new() - .read(true) - .custom_flags(libc::O_NOFOLLOW) - .open(path) - .map_err(|err| JailerError::FileOpen(PathBuf::from(path), err))?; + let netns = + File::open(path).map_err(|err| JailerError::FileOpen(PathBuf::from(path), err))?; // SAFETY: Safe because we are passing valid parameters. SyscallReturnCode(unsafe { libc::setns(netns.as_raw_fd(), libc::CLONE_NEWNET) }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/jailer/src/main.rs new/firecracker-1.16.1/src/jailer/src/main.rs --- old/firecracker-1.16.0/src/jailer/src/main.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/jailer/src/main.rs 2026-06-29 11:33:24.000000000 +0200 @@ -3,9 +3,6 @@ use std::ffi::{CString, NulError, OsString}; use std::fmt::{Debug, Display}; -use std::fs::OpenOptions; -use std::io::Read; -use std::os::unix::fs::OpenOptionsExt; use std::path::{Path, PathBuf}; use std::{env as p_env, fs, io}; @@ -243,25 +240,12 @@ T: AsRef<Path> + Debug, V: Display + Debug, { - let mut file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .custom_flags(libc::O_NOFOLLOW) - .open(file_path.as_ref()) - .map_err(|err| JailerError::Write(PathBuf::from(file_path.as_ref()), err))?; - io::Write::write_all(&mut file, format!("{}\n", value).as_bytes()) + fs::write(file_path, format!("{}\n", value)) .map_err(|err| JailerError::Write(PathBuf::from(file_path.as_ref()), err)) } pub fn readln_special<T: AsRef<Path> + Debug>(file_path: &T) -> Result<String, JailerError> { - let mut file = OpenOptions::new() - .read(true) - .custom_flags(libc::O_NOFOLLOW) - .open(file_path.as_ref()) - .map_err(|err| JailerError::ReadToString(PathBuf::from(file_path.as_ref()), err))?; - let mut line = String::new(); - file.read_to_string(&mut line) + let mut line = fs::read_to_string(file_path) .map_err(|err| JailerError::ReadToString(PathBuf::from(file_path.as_ref()), err))?; // Remove the newline character at the end (if any). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/rebase-snap/Cargo.toml new/firecracker-1.16.1/src/rebase-snap/Cargo.toml --- old/firecracker-1.16.0/src/rebase-snap/Cargo.toml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/rebase-snap/Cargo.toml 2026-06-29 11:33:24.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "rebase-snap" -version = "1.16.0" +version = "1.16.1" authors = ["Amazon Firecracker team <[email protected]>"] edition = "2024" license = "Apache-2.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/seccompiler/Cargo.toml new/firecracker-1.16.1/src/seccompiler/Cargo.toml --- old/firecracker-1.16.0/src/seccompiler/Cargo.toml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/seccompiler/Cargo.toml 2026-06-29 11:33:24.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "seccompiler" -version = "1.16.0" +version = "1.16.1" authors = ["Amazon Firecracker team <[email protected]>"] edition = "2024" description = "Program that compiles multi-threaded seccomp-bpf filters expressed as JSON into raw BPF programs, serializing them and outputting them to a file." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/snapshot-editor/Cargo.toml new/firecracker-1.16.1/src/snapshot-editor/Cargo.toml --- old/firecracker-1.16.0/src/snapshot-editor/Cargo.toml 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/snapshot-editor/Cargo.toml 2026-06-29 11:33:24.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "snapshot-editor" -version = "1.16.0" +version = "1.16.1" authors = ["Amazon Firecracker team <[email protected]>"] edition = "2024" license = "Apache-2.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/arch/x86_64/msr.rs new/firecracker-1.16.1/src/vmm/src/arch/x86_64/msr.rs --- old/firecracker-1.16.0/src/vmm/src/arch/x86_64/msr.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/arch/x86_64/msr.rs 2026-06-29 11:33:24.000000000 +0200 @@ -225,6 +225,9 @@ MSR_RANGE!(MSR_MISC_FEATURES_ENABLES), MSR_RANGE!(MSR_K7_HWCR), MSR_RANGE!(MSR_IA32_TSX_CTRL), + // IA32_XSS controls which supervisor XSTATE components are enabled. Without it, the guest + // kernel's XRSTORS of process FPU state containing CET/PT supervisor components triggers #GP. + MSR_RANGE!(MSR_IA32_XSS), MSR_RANGE!( MSR_KVM_RANGE_START, MSR_KVM_RANGE_END - MSR_KVM_RANGE_START + 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/csm/connection.rs new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/csm/connection.rs --- old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/csm/connection.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/csm/connection.rs 2026-06-29 11:33:24.000000000 +0200 @@ -290,8 +290,9 @@ /// using them to manage the internal connection state. /// /// Returns: - /// always `Ok(())`: the packet has been consumed; - fn send_pkt(&mut self, pkt: &VsockPacketTx) -> Result<(), VsockError> { + /// The packet is always consumed: host-side back-pressure is buffered and unrecoverable + /// stream errors terminate the connection. + fn send_pkt(&mut self, pkt: &VsockPacketTx) { // Update the peer credit information. self.peer_buf_alloc = pkt.hdr.buf_alloc(); self.peer_fwd_cnt = Wrapping(pkt.hdr.fwd_cnt()); @@ -309,7 +310,7 @@ "vsock: dropping empty data packet from guest (lp={}, pp={}", self.local_port, self.peer_port ); - return Ok(()); + return; } // Unwrapping here is safe, since we just checked `pkt.buf()` above. @@ -321,7 +322,7 @@ self.local_port, self.peer_port, err ); self.kill(); - return Ok(()); + return; } // We might've just consumed some data. If that's the case, we might need to @@ -394,8 +395,6 @@ ); } }; - - Ok(()) } /// Check if the connection has any pending packet addressed to the peer. @@ -922,7 +921,7 @@ } fn send(&mut self) { - self.conn.send_pkt(&self.tx_pkt).unwrap(); + self.conn.send_pkt(&self.tx_pkt); } fn recv(&mut self) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/device.rs new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/device.rs --- old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/device.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/device.rs 2026-06-29 11:33:24.000000000 +0200 @@ -242,10 +242,7 @@ } }; - if self.backend.send_pkt(&self.tx_packet).is_err() { - queue.undo_pop(); - break; - } + self.backend.send_pkt(&self.tx_packet); have_used = true; queue.add_used(index, 0).unwrap_or_else(|err| { @@ -406,21 +403,47 @@ } fn kick(&mut self) { - // Vsock has complicated protocol that isn't resilient to any packet loss, - // so for Vsock we don't support connection persistence through snapshot. - // Any in-flight packets or events are simply lost. - // Vsock is restored 'empty'. - // The only reason we still `kick` it is to make guest process - // `TRANSPORT_RESET_EVENT` event we sent during snapshot creation. if self.is_activated() { self.pending_event_ack = true; + // Vsock has a complicated protocol that isn't resilient to any packet loss, + // so for Vsock we don't support connection persistence through snapshot. Any + // in-flight packets or events are simply lost and Vsock is restored 'empty'. + // We signal the event queue to make the guest process the + // `TRANSPORT_RESET_EVENT` event we sent during snapshot creation. (We signal + // it host->guest rather than writing its eventfd, which would invoke the + // guest's reset-ack path and clear `pending_event_ack` prematurely.) info!( "[{:?}:{}] signaling event queue", self.device_type(), self.id() ); self.signal_used_queue(EVQ_INDEX).unwrap(); + + // Replay the TX queue notification, like the default `VirtioDevice::kick` + // does for its data queues, so the device re-processes any TX descriptor + // that was in-flight at snapshot time and re-arms `avail_event`. + // + // Without this, `avail_idx` stays ahead of the `avail_event` we published. + // Under EVENT_IDX the guest only notifies us when `avail_idx` crosses + // `avail_event`; since it is already past, the guest considers itself to + // have notified us and stays silent, so we never process the queue and + // guest-to-host connections hang. RX needs no replay: it is gated by + // `pending_event_ack` until the guest acks the reset, and the host pulls + // from the backend rather than waiting on a guest RX notification. + info!( + "[{:?}:{}] notifying tx queue", + self.device_type(), + self.id() + ); + if let Err(err) = self.queue_events[TXQ_INDEX].write(1) { + error!( + "[{:?}:{}] error notifying tx queue: {}", + self.device_type(), + self.id(), + err + ); + } } } @@ -619,6 +642,26 @@ } #[test] + fn test_kick_replays_tx_notification_only() { + // On restore, kick() must replay only the TX data queue (to re-process in-flight + // TX and re-arm avail_event). RX is gated by pending_event_ack so it needs no + // replay, and the event queue's data eventfd must not be notified -- that is the + // guest's TRANSPORT_RESET ack path; the event queue is signaled host->guest. + let test_ctx = TestContext::new(); + let mut ctx = test_ctx.create_event_handler_context(); + ctx.mock_activate(test_ctx.mem.clone(), test_ctx.interrupt.clone()); + + ctx.device.kick(); + + // TX queue eventfd was replayed for re-processing. + assert_eq!(ctx.device.queue_events[TXQ_INDEX].read().unwrap(), 1); + // RX and the event queue's data eventfd must not be signaled by kick() + // (non-blocking read returns an error when the eventfd has no pending count). + ctx.device.queue_events[RXQ_INDEX].read().unwrap_err(); + ctx.device.queue_events[EVQ_INDEX].read().unwrap_err(); + } + + #[test] fn test_prepare_save_emits_transport_reset_when_active() { // The snapshot path goes through prepare_save -> send_transport_reset_event. // Both the evq publication and the RX gate must be observable afterwards. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/event_handler.rs new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/event_handler.rs --- old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/event_handler.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/event_handler.rs 2026-06-29 11:33:24.000000000 +0200 @@ -123,11 +123,8 @@ pub fn notify_backend(&mut self, evset: EventSet) -> Result<Vec<u16>, InvalidAvailIdx> { let mut used_queues = Vec::new(); self.backend.notify(evset); - // After the backend has been kicked, it might've freed up some resources, so we - // can attempt to send it more data to process. - // In particular, if `self.backend.send_pkt()` halted the TX queue processing (by - // returning an error) at some point in the past, now is the time to try walking the - // TX queue again. + // After the backend has been kicked it may have freed up resources, so walk the TX + // queue again in case there are packets waiting to be forwarded to it. if self.process_tx()? { used_queues.push(TXQ_INDEX.try_into().unwrap()); } @@ -286,23 +283,6 @@ } // Test case: - // - the driver has something to send (there's data in the TX queue); and - // - the backend errors out and cannot process the TX queue. - { - let test_ctx = TestContext::new(); - let mut ctx = test_ctx.create_event_handler_context(); - ctx.mock_activate(test_ctx.mem.clone(), test_ctx.interrupt.clone()); - - ctx.device.backend.set_pending_rx(false); - ctx.device.backend.set_tx_err(Some(VsockError::NoData)); - ctx.signal_txq_event(); - - // Both RX and TX queues should be untouched. - assert_eq!(ctx.guest_txvq.used.idx.get(), 0); - assert_eq!(ctx.guest_rxvq.used.idx.get(), 0); - } - - // Test case: // - the driver supplied a malformed TX buffer. { let test_ctx = TestContext::new(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/mod.rs new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/mod.rs --- old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/mod.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/mod.rs 2026-06-29 11:33:24.000000000 +0200 @@ -169,7 +169,11 @@ fn recv_pkt(&mut self, pkt: &mut VsockPacketRx) -> Result<(), VsockError>; /// Write/send a packet through the channel. - fn send_pkt(&mut self, pkt: &VsockPacketTx) -> Result<(), VsockError>; + /// + /// The packet is always consumed (its virtio TX buffers can be returned to the guest): + /// host-side back-pressure is absorbed by buffering, and unrecoverable errors terminate + /// the offending connection rather than halting queue processing. + fn send_pkt(&mut self, pkt: &VsockPacketTx); /// Checks whether there is pending incoming data inside the channel, meaning that a subsequent /// call to `recv_pkt()` won't fail. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/test_utils.rs new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/test_utils.rs --- old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/test_utils.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/test_utils.rs 2026-06-29 11:33:24.000000000 +0200 @@ -27,7 +27,6 @@ pub struct TestBackend { pub evfd: EventFd, pub rx_err: Option<VsockError>, - pub tx_err: Option<VsockError>, pub pending_rx: bool, pub rx_ok_cnt: usize, pub tx_ok_cnt: usize, @@ -39,7 +38,6 @@ Self { evfd: EventFd::new(libc::EFD_NONBLOCK).unwrap(), rx_err: None, - tx_err: None, pending_rx: false, rx_ok_cnt: 0, tx_ok_cnt: 0, @@ -50,9 +48,6 @@ pub fn set_rx_err(&mut self, err: Option<VsockError>) { self.rx_err = err; } - pub fn set_tx_err(&mut self, err: Option<VsockError>) { - self.tx_err = err; - } pub fn set_pending_rx(&mut self, prx: bool) { self.pending_rx = prx; } @@ -84,14 +79,8 @@ } } - fn send_pkt(&mut self, _pkt: &VsockPacketTx) -> Result<(), VsockError> { - match self.tx_err.take() { - None => { - self.tx_ok_cnt += 1; - Ok(()) - } - Some(err) => Err(err), - } + fn send_pkt(&mut self, _pkt: &VsockPacketTx) { + self.tx_ok_cnt += 1; } fn has_pending_rx(&self) -> bool { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/unix/muxer.rs new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/unix/muxer.rs --- old/firecracker-1.16.0/src/vmm/src/devices/virtio/vsock/unix/muxer.rs 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/src/vmm/src/devices/virtio/vsock/unix/muxer.rs 2026-06-29 11:33:24.000000000 +0200 @@ -193,10 +193,9 @@ /// This absorbs unexpected packets, handles RSTs (by dropping connections), and forwards /// all the rest to their owning `MuxerConnection`. /// - /// Returns: - /// always `Ok(())` - the packet has been consumed, and its virtio TX buffers can be - /// returned to the guest vsock driver. - fn send_pkt(&mut self, pkt: &VsockPacketTx) -> Result<(), VsockError> { + /// The packet is always consumed, and its virtio TX buffers can be returned to the guest + /// vsock driver. + fn send_pkt(&mut self, pkt: &VsockPacketTx) { let conn_key = ConnMapKey { local_port: pkt.hdr.dst_port(), peer_port: pkt.hdr.src_port(), @@ -212,7 +211,7 @@ // if pkt.hdr.type_() != uapi::VSOCK_TYPE_STREAM { self.enq_rst(pkt.hdr.dst_port(), pkt.hdr.src_port()); - return Ok(()); + return; } // We don't know how to handle packets addressed to other CIDs. We only handle the host @@ -222,7 +221,7 @@ "vsock: dropping guest packet for unknown CID: {:?}", pkt.hdr ); - return Ok(()); + return; } if !self.conn_map.contains_key(&conn_key) { @@ -236,7 +235,7 @@ // Send back an RST, to let the drive know we weren't expecting this packet. self.enq_rst(pkt.hdr.dst_port(), pkt.hdr.src_port()); } - return Ok(()); + return; } // Right, we know where to send this packet, then (to `conn_key`). @@ -244,16 +243,13 @@ // there's no point in forwarding it the packet. if pkt.hdr.op() == uapi::VSOCK_OP_RST { self.remove_connection(conn_key); - return Ok(()); + return; } // Alright, everything looks in order - forward this packet to its owning connection. - let mut res: Result<(), VsockError> = Ok(()); self.apply_conn_mutation(conn_key, |conn| { - res = conn.send_pkt(pkt); + conn.send_pkt(pkt); }); - - res } /// Check if the muxer has any pending RX data, with which to fill a guest-provided RX @@ -895,7 +891,7 @@ } fn send(&mut self) { - self.muxer.send_pkt(&self.tx_pkt).unwrap(); + self.muxer.send_pkt(&self.tx_pkt); } fn recv(&mut self) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/tests/framework/utils_vsock.py new/firecracker-1.16.1/tests/framework/utils_vsock.py --- old/firecracker-1.16.0/tests/framework/utils_vsock.py 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/tests/framework/utils_vsock.py 2026-06-29 11:33:24.000000000 +0200 @@ -6,7 +6,7 @@ import os.path import re import time -from contextlib import ExitStack +from contextlib import ExitStack, contextmanager from pathlib import Path from socket import AF_UNIX, SOCK_STREAM, socket from subprocess import Popen @@ -176,18 +176,18 @@ assert wrk.hash == blob_hash -def check_guest_connections(vm, server_port_path, blob_path, blob_hash): - """Test guest-initiated connections. - - This will start an echo server on the host (in its own thread), then - start `TEST_CONNECTION_COUNT` workers inside the guest VM, all - communicating with the echo server. +@contextmanager +def host_echo_server(vm, server_port_path): + """Run a host-side echo server reachable by the guest over vsock. + + Starts `socat` listening on the Unix socket `server_port_path`, links it + into the VM's jail so Firecracker can reach it, and raises the ssh + service's `pids.max` so guest workers can fork freely. Yields the socket + path and tears the server down on exit. """ - echo_server = Popen( ["socat", f"UNIX-LISTEN:{server_port_path},fork,backlog=5", "exec:'/bin/cat'"] ) - try: # Give socat a bit of time to create the socket for attempt in Retrying( @@ -210,6 +210,23 @@ f"echo 1024 > /sys/fs/cgroup/system.slice/{vm.distro.ssh_service}/pids.max" ) + yield server_port_path + finally: + echo_server.terminate() + rc = echo_server.wait() + # socat exits with 128 + 15 (SIGTERM) + assert rc == 143 + + +def check_guest_connections(vm, server_port_path, blob_path, blob_hash): + """Test guest-initiated connections. + + This will start an echo server on the host (in its own thread), then + start `TEST_CONNECTION_COUNT` workers inside the guest VM, all + communicating with the echo server. + """ + + with host_echo_server(vm, server_port_path): # Build the guest worker sub-command. # `vsock_helper` will read the blob file from STDIN and send the echo # server response to STDOUT. This response is then hashed, and the @@ -234,11 +251,6 @@ cmd += "for w in $workers; do wait $w || (wait; exit 1); done" vm.ssh.check_output(cmd) - finally: - echo_server.terminate() - rc = echo_server.wait() - # socat exits with 128 + 15 (SIGTERM) - assert rc == 143 def make_host_port_path(uds_path, port): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/firecracker-1.16.0/tests/integration_tests/functional/test_vsock.py new/firecracker-1.16.1/tests/integration_tests/functional/test_vsock.py --- old/firecracker-1.16.0/tests/integration_tests/functional/test_vsock.py 2026-06-03 17:36:09.000000000 +0200 +++ new/firecracker-1.16.1/tests/integration_tests/functional/test_vsock.py 2026-06-29 11:33:24.000000000 +0200 @@ -34,6 +34,7 @@ check_guest_connections, check_host_connections, check_vsock_device, + host_echo_server, make_blob, make_host_port_path, start_guest_echo_server, @@ -474,3 +475,48 @@ validate_fc_metrics(metrics) finally: vm.kill() + + +def test_snapshot_restore_with_inflight_vsock_tx( + vsock_uvm, bin_vsock_path, tmp_path, microvm_factory +): + """ + Guest-initiated vsock connections must still work after a snapshot taken + while the guest is actively transmitting. + + If a guest TX descriptor is un-consumed when the snapshot is created, the + restored TX queue has avail_idx ahead of avail_event; with EVENT_IDX the + guest then suppresses all TX notifications and guest-initiated connections + hang. Unlike test_cycled_snapshot_restore (which snapshots after traffic has + drained, so it only hits this by chance), this test snapshots while a guest + worker is streaming, making the in-flight condition reliable. + """ + vm = vsock_uvm + + vm_blob_path = "/tmp/vsock/test.blob" + blob_path, blob_hash = make_blob(tmp_path) + _copy_vsock_data_to_guest(vm.ssh, blob_path, vm_blob_path, bin_vsock_path) + + server_port_path = os.path.join( + vm.path, make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT) + ) + with host_echo_server(vm, server_port_path): + # Continuously stream guest->host so the TX queue is non-empty when the + # snapshot is taken. + vm.ssh.check_output( + "nohup sh -c 'while true; do " + f"cat {vm_blob_path} | /tmp/vsock_helper echo 2 {ECHO_SERVER_PORT} " + ">/dev/null 2>&1; done' >/dev/null 2>&1 &" + ) + # Let the stream ramp up so traffic is genuinely in-flight. + time.sleep(2) + snapshot = vm.snapshot_full() + vm.kill() + + # Restore and verify a *fresh* guest-initiated connection works -- this is + # what hangs when a TX descriptor was in-flight at snapshot time. + new_vm = microvm_factory.build_from_snapshot(snapshot) + path = os.path.join( + new_vm.path, make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT) + ) + check_guest_connections(new_vm, path, vm_blob_path, blob_hash) ++++++ firecracker.obsinfo ++++++ --- /var/tmp/diff_new_pack.ck37OG/_old 2026-07-07 21:06:45.349657687 +0200 +++ /var/tmp/diff_new_pack.ck37OG/_new 2026-07-07 21:06:45.361658107 +0200 @@ -1,5 +1,5 @@ name: firecracker -version: 1.16.0 -mtime: 1780500969 -commit: d83d72b710361a10294480131377b1b00b163af8 +version: 1.16.1 +mtime: 1782725604 +commit: 2038188f145fb81b8d098147a10e9d9f392fd22f ++++++ vendor.tar.xz ++++++ /work/SRC/openSUSE:Factory/firecracker/vendor.tar.xz /work/SRC/openSUSE:Factory/.firecracker.new.1982/vendor.tar.xz differ: char 15, line 1
