Summary
We'd like to upstream a new CRIU mode that clones a running process from one machine to another with minimal downtime on the source, no intermediate disk image required.
A high level design is at: https://asafpamzn.github.io/criu/clone-dump-design.html.
This issue gives the short version.
See PR: #3049
Goals
- Reduce downtime at the source. The source process must be responsive almost the entire migration.
- Minimize total migration time. The wall-clock from "start migrating" to "process running on target" should be dominated by physical resources, that is, network bandwidth or CPU, not by design limitations.
- No disk needed. Pages stream directly from source memory to target memory; no intermediate image file is written for pages.
How it works
CLONE dump introduce a way to clone a process from one machine to the other with a three-phase flow that uses userfaultfd write-protect and PAGEMAP_SCAN to track writes while the process keeps running. Conceptually similar to the existing pre-dump + dump split, but the running phase tracks dirty pages via UFFD_FEATURE_WP_ASYNC rather than via soft-dirty/pagemap snapshots.
Note: pre-dump did not meet the requirements as it required going over all of the dirty pages during the freeze, which caused a long freeze time for our use case.
Phase 1 — short freeze, set up tracking
Seize the process, enumerate VMAs, register them with userfaultfd in UFFDIO_REGISTER_MODE_WP | UFFD_FEATURE_WP_ASYNC mode, apply UFFDIO_WRITEPROTECT to mark the address space write-protected (async), then unfreeze. After Phase 1, every page the process writes leaves a soft-dirty trail in pagemap; reads run normally.
Phase 2 — process runs; pages stream
Multiple scanner threads call PAGEMAP_SCAN to find newly-dirty regions. They publish dirty regions to a shared MPMC queue. P3 sender threads pull from the queue, process_vm_readv the pages out of the running process, LZ4-compress, and send them over a TLS-protected socket to the target. The target's clone-receive daemon receives, decompresses, and buffers the pages in a per-chunk hash table keyed by virtual address. The scanners iterate until the dirty-page count converges below a threshold.
Phase 3 — final short freeze
Re-seize, do one last PAGEMAP_SCAN for any pages dirtied between the last Phase 2 scan and the freeze, send those, write the regular CRIU "skeleton" image (everything except the page contents), and unfreeze or kill. Total of two brief freezes — the wall-clock between them is governed by your write rate and link bandwidth, not by CRIU.
On the target, the buffered pages are applied via UFFDIO_COPY from the userfaultfd handler that takes over once criu restore runs the restorer. From the restored process's point of view the migration is indistinguishable from a normal CRIU restore — userfaultfd serves any page that hasn't been applied yet, and the drain threads finish applying buffered pages in the background.
Multithreading
Both sides are heavily multithreaded:
- Source: N scanner threads (default 20) + N P3 sender threads (default 15) + an event-reader thread for UFFD UNMAP/REMOVE/REMAP. Scanners share a lock-free MPMC dirty-region queue; senders share a shared work queue for the initial bulk transfer (atomic fetch-and-add for work-stealing).
- Target: N P3 receiver threads (one per source sender) + N drain threads that apply buffered pages via
UFFDIO_COPY chunk-by-chunk.
All thread counts are runtime-tunable via CLI options (--clone-p3-threads, --clone-scanners, --clone-drain-threads).
In our benchmark we migrated a 300GB process under heavy writes in less than 30 seconds.
Footprint in CRIU's code base
We tried hard to keep the change isolated:
- New code lives in
criu/clone/ and criu/include/clone/ - all behind --clone-dump / criu clone-receive entry points.
- Touch-points to existing CRIU code are small as much as we could. Existing code paths are untouched when
--clone-dump is not set.
Requirements
- Linux 6.7+ on both source and target — needs both
UFFD_FEATURE_WP_ASYNC (5.7+) and PAGEMAP_SCAN (6.7+).
vm.unprivileged_userfaultfd=1 on both hosts.
- TCP reachability source → target.
Usage
# target
sudo criu clone-receive --images-dir /tmp/img \
--address <SRC_IP> --port 27 -v4
# source
sudo criu dump -t <PID> --images-dir /tmp/img \
--clone-dump \
--page-server --address <TGT_IP> --port 27 -v4
clone-receive performs the restore itself once all pages have arrived.
High level design doc (HTML, with diagrams): https://asafpamzn.github.io/criu/clone-dump-design.html
Happy to chunk the patch series however you'd prefer (one giant series vs. a stack of smaller ones; we lean toward the smaller-stack approach, starting with the parts that touch existing files).
Summary
We'd like to upstream a new CRIU mode that clones a running process from one machine to another with minimal downtime on the source, no intermediate disk image required.
A high level design is at: https://asafpamzn.github.io/criu/clone-dump-design.html.
This issue gives the short version.
See PR: #3049
Goals
How it works
CLONE dump introduce a way to clone a process from one machine to the other with a three-phase flow that uses userfaultfd write-protect and
PAGEMAP_SCANto track writes while the process keeps running. Conceptually similar to the existingpre-dump+dumpsplit, but the running phase tracks dirty pages viaUFFD_FEATURE_WP_ASYNCrather than via soft-dirty/pagemap snapshots.Note:
pre-dumpdid not meet the requirements as it required going over all of the dirty pages during the freeze, which caused a long freeze time for our use case.Phase 1 — short freeze, set up tracking
Seize the process, enumerate VMAs, register them with userfaultfd in
UFFDIO_REGISTER_MODE_WP | UFFD_FEATURE_WP_ASYNCmode, applyUFFDIO_WRITEPROTECTto mark the address space write-protected (async), then unfreeze. After Phase 1, every page the process writes leaves a soft-dirty trail in pagemap; reads run normally.Phase 2 — process runs; pages stream
Multiple scanner threads call
PAGEMAP_SCANto find newly-dirty regions. They publish dirty regions to a shared MPMC queue. P3 sender threads pull from the queue,process_vm_readvthe pages out of the running process, LZ4-compress, and send them over a TLS-protected socket to the target. The target'sclone-receivedaemon receives, decompresses, and buffers the pages in a per-chunk hash table keyed by virtual address. The scanners iterate until the dirty-page count converges below a threshold.Phase 3 — final short freeze
Re-seize, do one last
PAGEMAP_SCANfor any pages dirtied between the last Phase 2 scan and the freeze, send those, write the regular CRIU "skeleton" image (everything except the page contents), and unfreeze or kill. Total of two brief freezes — the wall-clock between them is governed by your write rate and link bandwidth, not by CRIU.On the target, the buffered pages are applied via
UFFDIO_COPYfrom the userfaultfd handler that takes over oncecriu restoreruns the restorer. From the restored process's point of view the migration is indistinguishable from a normal CRIU restore — userfaultfd serves any page that hasn't been applied yet, and the drain threads finish applying buffered pages in the background.Multithreading
Both sides are heavily multithreaded:
UFFDIO_COPYchunk-by-chunk.All thread counts are runtime-tunable via CLI options (
--clone-p3-threads,--clone-scanners,--clone-drain-threads).In our benchmark we migrated a 300GB process under heavy writes in less than 30 seconds.
Footprint in CRIU's code base
We tried hard to keep the change isolated:
criu/clone/andcriu/include/clone/- all behind--clone-dump/criu clone-receiveentry points.--clone-dumpis not set.Requirements
UFFD_FEATURE_WP_ASYNC(5.7+) andPAGEMAP_SCAN(6.7+).vm.unprivileged_userfaultfd=1on both hosts.Usage
clone-receiveperforms the restore itself once all pages have arrived.High level design doc (HTML, with diagrams): https://asafpamzn.github.io/criu/clone-dump-design.html
Happy to chunk the patch series however you'd prefer (one giant series vs. a stack of smaller ones; we lean toward the smaller-stack approach, starting with the parts that touch existing files).