DrumsBench

Research notes / 2026-10-25

Checkpoint and restore, and what breaks

CRIU basics for newcomers, the three Linux failures we hit (a lost POSIX lock, runc's empty network namespace, GNOME trees and mount namespaces), and the macOS equivalent that has no process checkpoint at all.

A checkpoint saves a running computer's state so it can be brought back later, or brought back many times. On Linux, CRIU can do this for a tree of processes without a virtual machine. It is the piece that would let a shared runtime restore a prepared desktop in well under the time a VM takes to boot. It is also where our Linux work is stuck. This note explains the tool, then the three failures, then why macOS has no equivalent.

CRIU in plain words

CRIU (Checkpoint/Restore In Userspace) freezes a process tree, writes everything it can see about it to image files, and later rebuilds the tree from those files.

TermMeaning
dumpFreeze the tree and write images: memory pages, open files, sockets, namespaces, locks, signals, timers
restoreCreate new processes with the same pids, map the same memory, reopen the same files, then resume
imagesThe directory of files a dump writes; a restore reads them. Size grows with dirty memory
--file-locksRecord and re-apply file locks. Off by default; a dump of a process holding a lock fails without it
--rootThe filesystem root the restored tree should see, for a tree that was checkpointed inside a chroot or pivot_root
pre-resume hookAn --action-script called when every process is rebuilt but still stopped. The moment to audit what the restored tree actually holds
external namespacesA namespace CRIU is told not to dump or restore (for example a network namespace owned by something else); the image records only a key

A restore that "finished successfully" means CRIU rebuilt the tree and resumed it. It does not mean the tree has the same locks, the same network, or the same view of the filesystem. That gap is where the failures below live.

Failure 1: a POSIX lock on a mapped file is lost (unresolved)

A tiny child process takes a POSIX read lock on a plain file A and on a file B that it also maps into memory through a second descriptor, the way SQLite maps its -shm file. Dump with --file-locks; restore with --file-locks and a pre-resume hook that lists every lock the restored tasks hold while still stopped.

With stock CRIU 4.3 the dump records two locks and the restore log reports applying two locks. The pre-resume census shows the lock on A present and the lock on B absent (author-claimed from an authoring sandbox; no result file on the branch; see EXP-009).

observed (probe, stock CRIU 4.3)suspected ordering (source, v4.3)one process holdslock Aplain filelock Bfile it also mmapscriu dump --file-locksrecords 2 lockscriu restore --file-lockslog: applied 2pre-resume census, tasks stoppedA: presentB: ABSENTrestore failing; dump was completeprepare_file_locks()sets A and B on the fds of the taskopen_vmas()restorer closes the VMA fdPOSIX locks: per process + inode.closing any fd of that filedrops lock Bpatch candidate (unproven):set POSIX locks after VMA fd closecause not uniquely proven: the probeshows the effect, not the only causeUNRESOLVED: patch candidate, no recorded repair on the operator host
Why the lock disappears. In stock CRIU 4.3 the restore applies POSIX locks before it maps the task's memory regions, and the restorer then closes the descriptor it used to map the file. POSIX locks are per process and per inode, so closing any descriptor of that file drops the process's lock on it. The order is read from the source and is a candidate cause, not a proven one.

Why it matters: a desktop runtime full of SQLite databases (browser profiles, GNOME settings, LibreOffice registries) would restore with databases that believe they are unlocked. That is a silent fidelity failure, the kind that corrupts a reward without a crash. The full GNOME candidate run saw exactly this shape: after restore only the meta.db lock was present and the meta.db-shm lock was missing, and the gate correctly failed (EXP-008).

Failure 2: runc restores a container with no network

The OCI reference arm uses runc 1.5.2 driving CRIU 4.3 on the same desktop image, as a control: if the custom runtime fails and runc also fails, the method is suspect; if runc passes, the fault is ours.

The first runc restore reported success, with correct pid identity and cgroup limits, but the loopback interface inside the restored container was down with no address, and the restore log said no network image existed (author-claimed, no artifact; EXP-010). A layered probe put the cause at runc itself: its checkpoint code always asks CRIU to treat the network namespace as empty, whether or not the flag is given, so CRIU skips the network dump and restore. A minimal container with a network namespace held open by a separate holder process, handed to runc as an external namespace, restored with the loopback up and the namespace byte-identical before and after (author-claimed, minimal sleep container only). The desktop run with that fix is the next step, not a done one.

The consequence for the control: the runc restore that "passed" did so with no file lock held and with a broken network. It is not a complete passing cycle either.

Failure 3: a GNOME tree and its mount namespace

The custom rootfs slot mounts the same OSWorld Ubuntu image read-only, puts a private overlay on top in its own pid and mount namespaces, pivots root, and starts the image's own D-Bus, Xorg (dummy driver), GNOME and guest server. Three successive dump refusals were hit and fixed in source: a root mount that was a device file, a root that was not the mount namespace root, and a session leader outside the pid namespace. The first dump that succeeded was followed by a restore that crashed immediately without a --root argument; a --root restore contract was then added, and the next full cycle reached the lock failure above (all author-claimed, no logs on the branch; EXP-008). Numbers from those runs (dump seconds, image size) are not published as measurements because no log exists.

Earlier, in a separate sandbox probe, a smaller tree (Xvfb, a window manager, D-Bus, the guest server and Chromium) was checkpointed and restored with CRIU 4.2, with the restored clones sharing random-number state, cookies and hostname until a reseed step was added; the restore was faster than a cold start but missed its pre-registered 2x bar once hygiene was included (EXP-005, see that record for the exact ratios and their labels).

The macOS equivalent: there is none

macOS has no process checkpoint. Forking a Cocoa application without exec is unsupported (vendor claim, fork(2) documentation), and there is no page-merging mechanism like KSM (inference). So on macOS there is no prewarm-and-fork and no restore of a running application. What exists:

So the macOS arm restores files and relaunches applications; it never restores a running application. Its "restore" is a reset plus a relaunch, and every page that reports a reset time for macOS says so.

What a reader should take away

Checkpoint and restore is the mechanism that makes a shared runtime worth having on Linux, and the mechanism is not yet faithful for the processes a desktop actually runs. Until the lock gate passes, every Linux number about the custom runtime is about a runtime that restores incorrectly. We publish the failure rather than the number.