Skip to content

vmm: Reset dirty bitmaps atomically - #6321

Open
not4s wants to merge 5 commits into
firecracker-microvm:mainfrom
not4s:fix/atomic-dirty-take
Open

not4s wants to merge 5 commits into
firecracker-microvm:mainfrom
not4s:fix/atomic-dirty-take

Conversation

@not4s

@not4s not4s commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Snapshot creation now takes the dirty pages before it copies guest memory. It takes Firecracker's bitmap of each region with get_and_reset, which reads and clears each word in one atomic operation, then ORs KVM's dirty log of each plugged slot into it. If reading a log or writing the snapshot fails, it marks the taken pages dirty again.

DirtyBitmap is now one bitmap per guest memory region, in region order, which is how Firecracker's bitmap, mincore and the memory file are laid out. Only KVM's log is per slot.

Reason

A snapshot read Firecracker's bitmap while it copied guest memory, then cleared the bitmap. A page that a device wrote during the copy lost its mark, and the next Diff snapshot missed it. No device writes during a snapshot today, but threaded device I/O will. A failed sync, or a KVM log read that failed partway, also dropped marks that the snapshot had already cleared.

Testing

Tests cover each failure path, a write during the copy, and concurrent marking. Kani proves set_bits, which puts taken pages back.

Case Failing test
Reads the bitmap while copying and clears it after A copy of the race test, on every run
Clearing Firecracker's bitmap after the copy test_write_during_memory_snapshot_stays_dirty
A Full snapshot taking the bitmap after the copy test_write_during_memory_snapshot_stays_dirty
Not marking pages dirty again when writing the file fails test_failed_memory_snapshot_keeps_dirty_pages
Not marking pages dirty again when a KVM log read fails test_failed_dirty_log_read_keeps_firecracker_marks
take_dirty reading the bitmap, then clearing it test_take_dirty_with_concurrent_marks

Performance

Time spent on dirty bitmaps in a 16 GiB snapshot, against main. It is the same whether devices (Firecracker's bitmap) or the guest (KVM's log) dirtied the pages.

Snapshot Change
Diff, nothing dirty -40% to -84%
Diff, 1% dirty -42% to -83%
Diff, 25% dirty -29% to -81%
Diff, all dirty -34% to -83%
Full with dirty tracking +0.5 to +0.75 ms

Diff snapshots get faster because one atomic take per word replaces a read per page during the copy and a clear afterwards, and KVM's log is merged with plain ORs. Full snapshots with dirty tracking now take the bitmap instead of clearing it with a plain store, which costs more.

License Acceptance

By submitting this pull request, I confirm that my contribution is made under the terms of the
Apache 2.0 license. For more information on following Developer Certificate of Origin and signing
off your commits, please check CONTRIBUTING.md.

PR Checklist

  • I have read and understand CONTRIBUTING.md.
  • I have run tools/devtool checkbuild --all to verify that the PR passes build checks on all
    supported architectures.
  • I have run tools/devtool checkstyle --no-clippy to verify that the PR passes the automated
    style checks.
  • I have described what is done in these changes, why they are needed, and how they are solving
    the problem in a clear and encompassing way.
  • I have updated any relevant documentation (both in code and in the docs) in the PR.
  • I have mentioned all user-facing changes in CHANGELOG.md.
  • If a specific issue led to this PR, this PR closes the issue.
  • When making API changes, I have followed the Runbook for Firecracker API changes.
  • I have tested all new and changed functionalities in unit tests and/or integration tests.
  • I have linked an issue to every new TODO.

  • This functionality cannot be added in rust-vmm.

@not4s
not4s marked this pull request as ready for review October 20, 2026 16:44
@not4s
not4s force-pushed the fix/atomic-dirty-take branch from 575bfa0 to 3352184 Compare October 20, 2026 16:45
@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.51852% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.65%. Comparing base (874d26c) to head (adb4e0c).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/vmm/src/vstate/vm.rs 96.82% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6321      +/-   ##
==========================================
+ Coverage   83.61%   83.65%   +0.03%     
==========================================
  Files         282      282              
  Lines       33613    33665      +52     
==========================================
+ Hits        28107    28162      +55     
+ Misses       5506     5503       -3     
Flag Coverage Δ
5.10-m5n.metal 83.80% <98.51%> (+0.03%) ⬆️
5.10-m6a.metal 83.24% <98.51%> (+0.03%) ⬆️
5.10-m6g.metal 80.88% <90.37%> (-0.03%) ⬇️
5.10-m6i.metal 83.81% <98.51%> (+0.03%) ⬆️
5.10-m7a.metal-48xl 83.23% <98.51%> (+0.04%) ⬆️
5.10-m7g.metal 80.88% <90.37%> (-0.03%) ⬇️
5.10-m7i.metal-24xl 83.77% <98.51%> (+0.01%) ⬆️
5.10-m7i.metal-48xl 83.79% <98.51%> (+0.03%) ⬆️
5.10-m8g.metal-24xl 80.88% <90.37%> (-0.03%) ⬇️
5.10-m8g.metal-48xl 80.90% <90.37%> (-0.01%) ⬇️
5.10-m8i.metal-48xl 83.78% <98.51%> (+0.03%) ⬆️
5.10-m8i.metal-96xl 83.79% <98.51%> (+0.04%) ⬆️
5.10-m9g.metal-48xl 80.90% <90.37%> (-0.01%) ⬇️
6.1-m5n.metal 83.93% <98.51%> (+0.04%) ⬆️
6.1-m6a.metal 83.36% <98.51%> (+0.03%) ⬆️
6.1-m6g.metal 80.87% <90.37%> (-0.03%) ⬇️
6.1-m6i.metal 83.93% <98.51%> (+0.04%) ⬆️
6.1-m7a.metal-48xl 83.35% <98.51%> (+0.03%) ⬆️
6.1-m7g.metal 80.87% <90.37%> (-0.03%) ⬇️
6.1-m7i.metal-24xl 83.94% <98.51%> (+0.03%) ⬆️
6.1-m7i.metal-48xl 83.94% <98.51%> (+0.04%) ⬆️
6.1-m8g.metal-24xl 80.89% <90.37%> (-0.01%) ⬇️
6.1-m8g.metal-48xl 80.89% <90.37%> (-0.01%) ⬇️
6.1-m8i.metal-48xl 83.94% <98.51%> (+0.03%) ⬆️
6.1-m8i.metal-96xl 83.94% <98.51%> (+0.03%) ⬆️
6.1-m9g.metal-48xl 80.89% <90.37%> (-0.01%) ⬇️
6.18-m5n.metal 83.93% <98.51%> (+0.03%) ⬆️
6.18-m6a.metal 83.36% <98.51%> (+0.03%) ⬆️
6.18-m6g.metal 80.96% <90.37%> (-0.03%) ⬇️
6.18-m6i.metal 83.93% <98.51%> (+0.04%) ⬆️
6.18-m7a.metal-48xl 83.36% <98.51%> (+0.04%) ⬆️
6.18-m7g.metal 80.97% <90.37%> (-0.03%) ⬇️
6.18-m7i.metal-24xl 83.94% <98.51%> (+0.03%) ⬆️
6.18-m7i.metal-48xl 83.94% <98.51%> (+0.03%) ⬆️
6.18-m8g.metal-24xl 80.97% <90.37%> (-0.03%) ⬇️
6.18-m8g.metal-48xl 80.99% <90.37%> (-0.01%) ⬇️
6.18-m8i.metal-48xl 83.94% <98.51%> (+0.04%) ⬆️
6.18-m8i.metal-96xl 83.94% <98.51%> (+0.03%) ⬆️
6.18-m9g.metal-48xl 80.99% <90.37%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@not4s not4s added the Status: Awaiting review Indicates that a pull request is ready to be reviewed label Oct 20, 2026
@not4s
not4s force-pushed the fix/atomic-dirty-take branch 3 times, most recently from a118982 to 8dd5e23 Compare October 27, 2026 15:55
Comment thread src/vmm/src/vstate/memory.rs Resolved
Comment thread src/vmm/src/vstate/memory.rs Resolved
Comment thread src/vmm/src/vstate/memory.rs Resolved
Comment thread src/vmm/src/vstate/memory.rs Resolved
Comment thread src/vmm/src/vstate/vm.rs Resolved
Comment thread src/vmm/src/vstate/memory.rs Resolved
@not4s
not4s force-pushed the fix/atomic-dirty-take branch 3 times, most recently from 97566c7 to 0d47025 Compare October 29, 2026 15:34
Comment thread CHANGELOG.md Outdated
largest frame needs, instead of every buffer the guest has made available.
This lowers the cost of receiving each frame and improves receive throughput
for small packets.
- [#6321](https://github.com/firecracker-microvm/firecracker/pull/6321) Snapshot

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since today we don't have any other thread running while this happens, maybe we should skip a changelog entry here.

In single thread, this is an invisible change, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that it shouldn't go under changed, but it does fix the failure case where FC used to lose dirty bitmaps apart from the atomic reset changes, so I'll rather put this under fixed with that content.

/// slots are whole 2 MiB blocks, so with 4K pages each starts on a word.
pub(crate) fn slot_words(&self, index: usize) -> Range<usize> {
let pages = self.slot_size / host_page_size();
assert_eq!(index * pages % 64, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, we have a new invariant that self.slot_size must be a multiple of 256KiB (with 4 KiB pages).

Can we make this explicit by both writing it into the documentation of GuestRegionMmapExt, and checking it when we construct it?


PS: is this actually always true? E.g. could a malformed snapshot return slots that aren't multiples? Maybe we should check the code to make sure this is enforced everywhere

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this is guaranteed on boot config (slots are multiple of 2MB blocks) but isn't guaranteed on restore. I'm planning to open another PR first to merge it in before this one since there seems to be some test changes as well in virtio mem as well that seems out of scope for this PR

Comment thread src/vmm/src/vstate/vm.rs
.guest_memory()
.iter()
.any(|region| (**region).bitmap().is_some());
let dirty_bitmap = match snapshot_type {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you look into this refactor (IDK if it will end up better or worst):

  • here, we could call take_dirty_bitmap only in case of tracks_dirty_pages (even for Diff)
  • we could add a check that either all regions have a bitmap, or none has
  • on take_dirty, we can assume the region has a bitmap

Basically, the goal would be to avoid having to create an empty bitmap on take_dirty, which creates an unused code path that is pretty expensive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, put the check inside take_dirty_bitmap rather than in its callers

for (region, region_bitmap) in self.iter().zip(dirty_bitmap.iter_mut()) {
for (i, (mem_slot, plugged)) in region.slots().enumerate() {
if plugged {
let log = read_log(&mem_slot)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add an assert_eq!(log.len(), words.len())? I think it's always true today.

zip stops at the shortest, so having an explicit check seems wiser

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment on lines +1301 to +1303
if region_bitmap.len() > (region.size() / page_size).div_ceil(64) {
return Err(MemoryError::DirtyBitmapTooLarge);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be an assert. If we enable dirty bitmaps, they must be correct size per region, so I would even check for full equality region_bitmap.len() == (region.size() / page_size).div_ceil(64). This should also remove these .get calls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it now asserts that and zips

Comment thread src/vmm/src/vstate/memory.rs Outdated
Comment on lines 453 to 457
if dirty_bitmap.len() > expected_bitmap_array_len {
return Err(MemoryError::DirtyBitmapTooLarge);
} else if kvm_bitmap.len() < expected_bitmap_array_len {
} else if dirty_bitmap.len() < expected_bitmap_array_len {
return Err(MemoryError::DirtyBitmapTooSmall);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while we are at it, let's remove this abomination of a check and better yet assert the size and remove these errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good

Comment thread src/vmm/src/vstate/memory.rs Outdated
let page_size = host_page_size();

let write_result = self.iter().enumerate().try_for_each(|(i, region)| {
self.iter().enumerate().try_for_each(|(i, region)| {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about converting this to a simple for loop since we don't need the write_result anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don’t use iterator combinator chains when direct iteration is clearer, agreed

Comment thread src/vmm/src/vstate/vm.rs Outdated

/// Writes guest memory to `file` as a snapshot of `snapshot_type`, taking the dirty pages it
/// covers. If writing fails, the pages taken are marked dirty again.
fn snapshot_memory<F: MemoryFile>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me we are running out of names that basically say put memory into a file. How about:

snapshot_memory_to_file -> snapshot_memory_to_path
snapshot_memory            -> snapshot_memory_to_file

and write_memory just merged back into snapshot_memory (before rename)?
Maybe there are better alternatives, but I think names should be improved here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/vmm/src/vstate/vm.rs Outdated
Comment on lines +664 to +693
let tracks_dirty_pages = self
.guest_memory()
.iter()
.any(|region| (**region).bitmap().is_some());
let dirty_bitmap = match snapshot_type {
SnapshotType::Diff => self.take_dirty_bitmap()?,
SnapshotType::Full if tracks_dirty_pages => self.take_dirty_bitmap()?,
SnapshotType::Full => DirtyBitmap::new(),
};
let result = self.write_memory(file, snapshot_type, &dirty_bitmap, sync_snapshot_files);
if result.is_err() {
self.guest_memory().store_dirty_bitmap(&dirty_bitmap);
}
result
}

/// Writes guest memory to `file`, all of it or the pages in `dirty_bitmap`, then flushes it
/// and, if asked, syncs it.
fn write_memory<F: MemoryFile>(
&self,
file: &mut F,
snapshot_type: SnapshotType,
dirty_bitmap: &DirtyBitmap,
sync_snapshot_files: bool,
) -> Result<(), CreateSnapshotError> {
use self::CreateSnapshotError::MemoryBackingFile;

match snapshot_type {
SnapshotType::Diff => {
let dirty_bitmap = self.get_dirty_bitmap()?;
self.guest_memory().dump_dirty(&mut file, &dirty_bitmap)?;
}
SnapshotType::Full => {
self.guest_memory().dump(&mut file)?;
self.reset_dirty_bitmap();
self.guest_memory().reset_dirty();
}
SnapshotType::Diff => self.guest_memory().dump_dirty(file, dirty_bitmap)?,
SnapshotType::Full => self.guest_memory().dump(file)?,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't something closer to the original code work a bit better:

match snapshot_type {
  SnapshotType::Diff => {
    let dirty_bitmap = self.take_dirty_bitmap()?;
    if let Err(err) = self.guest_memory().dump_dirty(file, dirty_bitmap) {
      self.guest_memory().store_dirty_bitmap(&dirty_bitmap)
    } else if sync_snapshot_files && let Err(err) = file.sync_all() {
      self.guest_memory().store_dirty_bitmap(&dirty_bitmap)
    }
  }
  SnapshotType::Full => {
    self.guest_memory().dump(file)?;

    if sync_snapshot_files {
      file.sync_all()
          .map_err(|err| MemoryBackingFile("sync_all", err))?;
    }

    // only need to reset if dirty tracking is on
    // can be done after memory write/flush since Full snaps can only be taken if VM is paused
    let tracks_dirty_pages = self
        .guest_memory()
        .iter()
        .any(|region| (**region).bitmap().is_some());
    if tracks_dirty_pages {
      _ = self.take_dirty_bitmap()?;
    }
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still take first, they do the same thing bug Diff has to take first anyways, and it also works if something later writes during the copy. The idea is if a device thead ever wrote a page mid-copy, taking last would clear that mark and the next Diff would miss the page. If the dirty log read fails, it fails before we've written anything. Performance-wise should be the same unless it fails (which is still minimal)

Comment thread CHANGELOG.md Outdated
Comment on lines +67 to +70
- [#6321](https://github.com/firecracker-microvm/firecracker/pull/6321) Snapshot
creation now takes the dirty page bitmaps before copying guest memory,
clearing each word atomically, and marks the taken pages dirty again if the
snapshot fails.

@ShadowCurse ShadowCurse Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is better to say something like: "Firecracker dose not lose dirty bitmaps on io failures anymore" and move it to fixed section?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, moved to fixed

Comment thread src/vmm/src/vstate/memory.rs Outdated

/// ORs into `dirty_bitmap` each plugged slot's log, as `read_log` returns it. Stops at the
/// first log that fails to read.
fn merge_slot_logs<E, F>(&self, dirty_bitmap: &mut DirtyBitmap, read_log: F) -> Result<(), E>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge_slot_kvm_dirty_logs/merge_slot_kvm_dirty_bitmap

Also as I can see the read_log is used purely for the unit testing which is kinda sad.
Maybe if we could get a single Vec<u64> for the whole region composed of slots this would be much nicer api. What I am thinking about is bypassing kvm-ioctls and allocating a single Vec<u64> with size of slots * u64s_per_slot and then calling KVM_GET_DIRTY_LOG directly passing parts of this Vec into an ioctls. In the end we get a single Vec which represent the bitmap for the whole region which is trivial to merge.

Anyway, these are just my thoughts for now. Maybe it can be done in the future PRs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with merge_slot_kvm_dirty_logs. I'd still keep read_log, and the region sized buffer is a nice idea it was awkward and wasteful to have that additional translation step. Should this be upstreamed to kvm-ioctls or should we own the ioctls if it were to go through?

not4s added 5 commits November 4, 2026 17:15
DirtyBitmap maps each KVM slot to its dirty log. Make it one bitmap
per guest memory region, in region order, which is how Firecracker's
bitmap, mincore and the memory file are laid out. Only KVM's log is
per slot. This lets the next commits take Firecracker's bitmap of a
region in one call to AtomicBitmap::get_and_reset.

A slot's pages are then a slice of its region's bitmap (slot_words),
so every hotpluggable slot must start on a bitmap word: its size must
be a multiple of 64 host pages, 256 KiB with 4K pages. Configuration
validation guarantees that, since hotpluggable slots are whole 2 MiB
blocks. The constructor asserts it, and slot_size is now private so
every region goes through a constructor.

get_dirty_bitmap copies each plugged slot's log into its slice,
dump_dirty writes each plugged slot from it, and store_dirty_bitmap
marks a region's pages from it with set_bits. Firecracker always
builds one full-size bitmap per region, so dump_dirty asserts the
sizes instead of returning DirtyBitmapTooLarge or DirtyBitmapTooSmall,
which are removed. Snapshots behave as before.

Kani proves set_bits against a per-bit specification, for bitmaps of
up to two words with at most two bits set per word, including that it
keeps pages already marked.

Signed-off-by: Jay Chung <jaehoc@amazon.com>
Add the two steps the next commit uses to take the dirty pages before
a snapshot copies memory. take_dirty takes Firecracker's bitmap of
each region with get_and_reset, which reads and clears each word in
one atomic operation, so a page marked during the take is either
taken or stays marked. It needs every region to track dirty pages.
merge_slot_kvm_dirty_logs ORs KVM's log of each plugged slot, as the
caller reads it, into its region's bitmap, and stops at the first log
that fails to read, keeping the logs read before it. Each log must
cover exactly its slot's words.

The tests mark every page from another thread while takes run and
check that no mark is lost, check that a take holds each region's KVM
and Firecracker bits, and check what a failed log read leaves.

Signed-off-by: Jay Chung <jaehoc@amazon.com>
A snapshot read Firecracker's dirty bitmap page by page while copying
guest memory, then cleared it with a plain store. A page marked after
its bit was read lost the mark to that store, and a page marked before
the read but written again after its copy was cleared while the file
held the older content. Both need a writer on another thread during
the snapshot, which threaded device I/O and snapshots of a running
microVM will bring.

Two failures lose dirty pages today. The bitmaps were cleared before
the memory file was flushed and synced, so a failed sync left the next
Diff without those pages. And a failed read of KVM's dirty log dropped
the slots already read, whose bits KVM had already cleared.

Take the dirty pages before writing any memory, Full included: take
Firecracker's bitmap of each region with take_dirty, then OR KVM's log
of each plugged slot into it with merge_slot_kvm_dirty_logs. Write only
the taken pages. If reading a log, writing, flushing or syncing the file
fails, mark the taken pages dirty again. Without dirty tracking, a Diff
writes the resident pages, as before. reset_dirty is now test-only.

The tests make reading KVM's log, writing, flushing and syncing the
file fail in turn and check the pages stay dirty. One writes a page
right after the snapshot copied it and checks the next snapshot takes
it again. Another writes pages from another thread while Diff
snapshots run and checks that one more snapshot brings the file up to
date. test_dirty_bitmap_success takes once before it counts, since a
take also holds the pages Firecracker marked while booting.

Signed-off-by: Jay Chung <jaehoc@amazon.com>
test_diff_snapshot_works_after_error fills the disk so that a Diff
fails, then checks that a Diff after it restores a working microVM. A
Full now takes the dirty bitmaps before writing memory too, so run the
same test with a Full as the snapshot that fails.

Signed-off-by: Jay Chung <jaehoc@amazon.com>
Snapshot creation now marks pages clean before it writes guest memory,
and marks them dirty again if writing the memory file fails. Describe
that, and list the fix under Fixed: a failed flush or sync of the
memory file, or a failed read of KVM's dirty log, no longer loses dirty
pages.

Signed-off-by: Jay Chung <jaehoc@amazon.com>
@not4s
not4s force-pushed the fix/atomic-dirty-take branch from 0d47025 to adb4e0c Compare November 4, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Awaiting review Indicates that a pull request is ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants