DrumsBench

Experiments / EXP-005

Mechanism C: CRIU checkpoint and restore of a warm Drums L0 session with Chromium is 2.09x faster than cold without hygiene, 1.76x with the required reseed, below the 2x bar

Decide, against thresholds fixed before measurement, whether a warm Drums L0 session with its browser can be checkpointed with CRIU and restored as isolated siblings at least 2x faster than a cold start, without costing more than 1.05x the memory, with a public task still scoring correctly.

failedRan and missed a correctness, isolation, reset or pre-registered performance bar. Kept on the record.
Date
2026-10-24
Hypothesis
The author put the chance of Chromium checkpointing cleanly at about 30% and the chance of a 2x speedup given correctness at about 25%; a reseeded restore was expected to land at 1.5 to 1.7x.
Arms
l1-criu-container
Hardware
4 vCPU, 16 GiB, cgroup v1, no KVM (dev-sandbox)
OS
Ubuntu 24.04, kernel 6.18 (sandbox)
Workload
Drums L0 session with the real OSWorld guest server and Playwright Chromium 141 (--no-sandbox) on a page titled drums-ready; public OSWorld task e0df059f for correctness; 20 starts per arm, 3 memory trials to 20 sessions
Code
claude/linux-comparison-prep at ba21252
Evidence
docs/data/l1-criu/criu_probe.json
docs/data/l1-criu/criu_probe.log
docs/data/l1-criu/criu_probe_dropcache.json
docs/data/l1-criu/probe_reseeded.json
docs/data/l1-criu/probe_reseeded.log
docs/data/l1-criu/probe_reseeded_run1_serveronly_leak.json
docs/data/l1-criu/clone_hygiene.json
docs/data/l1-criu/clone_task_semantics.json
docs/data/l1-criu/sysctl_before.json, sysctl_after.json, sysctl_before_reseeded.json, sysctl_after_reseeded.json
docs/l1-shared-runtime-design.md (sections 14 to 17)
Limitation
The restore that passes the 2x bar skips clone hygiene and is not usable (siblings share RNG state, cookies and hostname); the usable reseeded restore is 1.76x; the comparison is against Drums' own cold L0 start, not against any incumbent, because the sandbox has no KVM.

In one paragraph

Memory sharing had failed three ways (EXP-004, EXP-019, EXP-020), so we turned to time: checkpoint one warm session once, browser included, and restore copies of it instead of starting every session cold. It worked, which the author did not expect: with private CRIU 4.2 a full Drums L0 session with Chromium dumps in 0.81 s to a 253 MiB image and restores as isolated siblings. Measured time to agent-ready, no hygiene: cold p50 2.05 s, restore p50 0.98 s, ratio 2.09x (passes the registered 2x point rule, bootstrap interval 1.99 to 2.15). But a restore with no hygiene is a clone: identical Python random state, identical V8 Math.random, the same cookie, the same hostname (all shown in clone_hygiene.json). With the reseed that makes a restore usable, measured restore p50 is 1.19 s against cold 2.10 s, ratio 1.76x, below the 2x bar; memory is 1.038x of cold (passes the 1.05x bar, costs more than cold). The sibling-sharing audit C3 fails its literal wording on two host device nodes (/dev/null, /dev/urandom) that cold sessions share too, and passes an amended rule registered afterwards. Status: failed (the usable restore misses the bar); the no-hygiene 2.09x is preliminary and must not be quoted as the Drums restore speedup. "Drums" here means the L0 prototype: namespaces, overlayfs on the sandbox's own Ubuntu 24.04 root, Xvfb, openbox. No shared-runtime (L1) result exists, and no incumbent snapshot-revert latency was measured.

Setup

Host. One development sandbox: 4 vCPU, 16 GiB, kernel 6.18, cgroup v1, no KVM; hostname and provider not recorded.

Session. The L0 session of EXP-004 in CRIU-friendly mode: unshare PID/mount/IPC/UTS/net; overlayfs full root (lower: template and the sandbox's own /), pivot_root; private Xvfb, D-Bus, AT-SPI, openbox; the real OSWorld guest server; Chromium 141 --no-sandbox on a data-URL page titled drums-ready. Around 19 processes per session.

CRIU. Version 4.2 from private extracts of Ubuntu archive packages; nothing installed system-wide. CRIU 4.1.1 failed on this kernel: getsockopt of SO_PASSCRED/SO_PASSSEC on a TCP socket returns EOPNOTSUPP on kernel 6.18, so any session with a listening socket would not dump.

Arms.

ArmWhat startsAgent-ready when
COLDcreate the session, launch Chromium, load the pagea screenshot differs from a blank desktop in more than 10% of pixels AND the page title is present
RESTORE_FULLcriu restore of a checkpoint taken from a COLD session in that state, into a fresh network and mount namespacesame
RESTORE_SERVER_ONLY (partial, by definition)restore a checkpoint without the browser, then cold-start Chromiumsame

Pre-registered bars (design values, commit 8f5ed06, 2026-10-24 07:02 UTC): C1 the public task e0df059f scores 0.0 then 1.0 from a restored session; C2 the restored screenshot shows the browser (more than 10% pixels differ from blank, title present); C3 two siblings restored from one checkpoint share no PID, no pipe, socket or eventfd inode, no writable regular file, no lock entry, with distinct Xvfb sockets and network namespaces ("any shared writable object fails C3"); latency RESTORE p50 at most 0.50 x COLD p50 and p95 not worse; memory restored at most 1.05 x cold.

Revision 2a of C3 (registered in 080a893 and narrowed in fc1d88d, both before the retest): a shared writable object is tolerated only if it is exactly /dev/null or /dev/urandom, a cold-pair control in the same pass shares the same object, and nothing else is shared. The original FAIL stays on the record.

Clone hygiene (registered in section 16.3 before any run): per restored sibling, set a per-sibling hostname; have the guest-server process reseed Python random (and numpy if loaded) from fresh os.urandom via a signal handler installed before the checkpoint; for a checkpoint declared clean, clear the default browser context's cookies and storage and open a new context (new renderer, new V8 isolate). A checkpoint not declared clean keeps its browser state by design, because erasing a real task's cookies would change the task.

sequenceDiagram
participant Sup as Supervisor
participant Cold as cold session
participant CRIU as criu 4.2 (private)
participant Sib as restored sibling
Sup->>Cold: create (criu_friendly), browser up, page loaded
Sup->>CRIU: dump -t init (0.81 s, 253 MiB)
Sup->>Sup: keep the session's upper tmpfs as a read-only checkpoint layer
loop each sibling
  Sup->>Sup: ledger mounts, link, veth; mount a fresh overlay with an empty upper
  Sup->>CRIU: restore --root overlay, external veth, X11 dir, device nodes
  CRIU->>Sib: processes resume
  Sup->>Sib: re-address veth, reseed hostname and RNG, wait for the guest server
end
Lifecycle of a checkpoint and its restored siblings. The checkpoint is the processes' memory and kernel state plus the session's overlay upper layer; each sibling gets its own empty upper on top.

What ran

envd_linux.l1proto.criu_probe ran the registered ladder on 2026-10-24 (results commit ca07330, 07:41 UTC): R1 criu check; R3 dump and restore of a session with only the guest server and desktop; R4 the same with Chromium; R5 restore into fresh network and mount namespaces; then C1, C2, C3, 20 interleaved starts per arm with a warm page cache, and 3 memory trials. The same probe ran again with 10 starts per arm after dropping the page cache before every start (criu_probe_dropcache.json, not pre-registered), and again with the reseed on (probe_reseeded.json, commit ba21252, 08:41 UTC). tests/test_criu_clone_hygiene.py produced clone_hygiene.json and clone_task_semantics.json. 793 non-volatile /proc/sys entries were snapshotted before and after each probe.

Four changes were needed before a session would dump at all, each a finding: the session init had to re-exec itself after pivot_root so no mapping points into the detached old root; unshare --kill-child had to be cleared (the restoring CRIU's exit would otherwise SIGKILL the session; consequence: the kernel no longer kills orphans, only the ownership sweep does, see EXP-022); the overlay upper had to sit on a host-visible tmpfs; and CRIU needed --force-irmap, --ghost-limit, --file-locks, --external veth[...], external mounts for the X11 directory and six /dev nodes, plus a private ip.

Results

Mechanism C: CRIU 4.2 checkpoint and restore of a warm Drums L0 session with Chromium, without and with the clone-hygiene reseed failed2026-10-24 · dev-sandbox

MetricValueUnitnNote
no hygiene: COLD time to agent-ready2.05 (p95 2.19)s20
no hygiene: RESTORE_FULL time to agent-ready0.98 (p95 1.22)s20preliminary: a restore that skips hygiene is not a usable restore (clones share RNG state, cookies, hostname)
no hygiene: RESTORE_SERVER_ONLY time to agent-ready (partial arm)1.22 (p95 1.28)s20
no hygiene: COLD p50 / RESTORE_FULL p502.09xdesign target 2x (RESTORE p50 at most 0.50 x COLD p50); passes the point rule narrowly
no hygiene: host memory per added session, COLD229.4MiB3
no hygiene: host memory per added session, RESTORE221.88MiB3ratio 0.9672; cold minus restore 95% interval [6.09, 8.94] MiB
server-only dump (R3)0.21s1image 74.78 MiB
Chromium dump (R4)0.806s1image 253.09 MiB; a one-time cost per checkpoint
Chromium restore call (R4), total0.763s1of which criu restore 0.319 s; browser title drums-ready present
C2 screenshot pixels changed vs blank desktop0.998fractionthreshold 0.10; PASS
C3 sibling audit, literal pre-registered wording0 / 1checksFAIL: two host character devices shared, /dev/null and /dev/urandom; the cold-pair control shares the same two
C3 sibling audit, revision 2a (exactly those two devices, cold control, nothing else)1 / 1checksPASS; the original FAIL stays on the record
C1 task e0df059f score from restored sessions, before acting0score
C1 task e0df059f score from restored sessions, after the scripted action1scoreshell getter + exact_match path only; librosa stubbed and unreached; stub list not saved for this run
page cache dropped (NOT pre-registered): COLD time to agent-ready2.59 (p95 2.74)s10
page cache dropped (NOT pre-registered): RESTORE_FULL time to agent-ready1.38 (p95 1.47)s10ratio 1.874; the 2x target is not met with cold cache
reseeded: COLD time to agent-ready2.1 (p95 2.24)s20
reseeded: RESTORE_FULL time to agent-ready1.19 (p95 1.29)s20the usable restore: hostname, server RNG and browser context reseeded
reseeded: RESTORE_SERVER_ONLY time to agent-ready (partial arm)1.24 (p95 1.37)s20
reseeded: COLD p50 / RESTORE_FULL p501.76xbelow the 2x target: FAIL
reseeded: host memory per added session, COLD229.59MiB3
reseeded: host memory per added session, RESTORE238.32MiB3ratio 1.0380; cold minus restore 95% interval [-9.86, -7.42] MiB (restore costs more); passes the 1.05x bar, fails 'no more than cold'
reseed cost per sibling0.198 to 0.215s2clone_hygiene.json reseed_s
reseeded: C1 task score before / after0 to 1scoreclone_task_semantics.json: reseeded true, sessions_were_restored true, session clock minus host clock -0.185 s

Host: 4 vCPU, 16 GiB, cgroup v1, no KVM, Ubuntu 24.04, kernel 6.18 (sandbox). one development sandbox; hostname and provider not recorded; not a VM or cloud host. Workload: Drums L0 session (namespaces + overlayfs full root + Xvfb + D-Bus + AT-SPI + openbox) with the real OSWorld guest server and Playwright Chromium 141 (--no-sandbox) on a data-URL page titled drums-ready; public OSWorld task e0df059f for the correctness check. Method: COLD = create the session, launch the browser, load the page. RESTORE_FULL = criu restore of a checkpoint taken from a COLD session in that ready state, into a fresh network and mount namespace. RESTORE_SERVER_ONLY = restore of a checkpoint without the browser, then a cold browser start (partial arm). Time to agent-ready ends when a screenshot differs from blank in more than 10% of pixels and the page title is present. 20 starts per arm, interleaved, page cache warm (10 starts with the page cache dropped). Memory: slope of host used memory against session count at 1, 4, 8, 12, 16, 20, 3 trials. Private CRIU 4.2 extracted from Ubuntu archive packages. Thresholds fixed in 8f5ed06; no-hygiene results in ca07330; C3 revision 2/2a registered in 080a893 and fc1d88d; reseeded results in ba21252.. Source: branch claude/linux-comparison-prep: docs/data/l1-criu/criu_probe.json (criu, stages.R3_server_only_dump, stages.R4_chromium_dump, stages.R4_chromium_restore, stages.C2_screenshot_shows_browser, stages.C3_sibling_audit, stages.C1_public_task_from_restored_session, latency, memory_summary); branch claude/linux-comparison-prep: docs/data/l1-criu/probe_reseeded.json (latency, memory_summary, stages.C3_sibling_audit.PASS_revision_2a, stages.C1_public_task_from_restored_session); branch claude/linux-comparison-prep: docs/data/l1-criu/criu_probe_dropcache.json (latency, stages); branch claude/linux-comparison-prep: docs/data/l1-criu/clone_hygiene.json; branch claude/linux-comparison-prep: docs/data/l1-criu/clone_task_semantics.json; branch claude/linux-comparison-prep: docs/l1-shared-runtime-design.md sections 14 to 17.

  • Drums here is the L0 prototype restored by CRIU; no shared-runtime (L1) result exists; no container, OCI image, VM or incumbent is in any of these files.
  • The 2.09x figure is a restore with no hygiene and is labelled preliminary; the usable (reseeded) restore is 1.76x and misses the 2x target.
  • The 1.87x drop-cache figure was not pre-registered (10 starts).
  • Residual identical state across clones: Python string-hash seed and address-space layout; a checkpoint not declared clean keeps identical cookies and V8 Math.random sequences by design.
  • One task, one checkpoint image per arm, --no-sandbox Chromium, one host and kernel; restore on another host untested.
  • The CRIU POSIX-lock-after-restore failure recorded elsewhere on the site stays unresolved; this probe's --file-locks flag is a dump-time flag, not a repair.

Ladder and correctness (measured, criu_probe.json)

Rung or checkResult
R1 criu checkpass in 0.049 s (CRIU 4.2; libnl "no version information" warnings)
R3 server-only dump0.210 s, image 74.78 MiB; restore 0.659 s total (criu 0.219 s)
R4 Chromium dump0.806 s, image 253.09 MiB; restore 0.763 s total (criu 0.319 s); browser title drums-ready
C1 task e0df059f from restored sessionsscore 0.0 before, 1.0 after the scripted action; sessions_were_restored: true; 34.0 s; PASS
C2 screenshot shows the browserchanged_fraction_vs_blank: 0.99745 (bar 0.10); PASS
C3 sibling audit, literal19 processes each; pid sets disjoint; PID, mount, net, IPC, UTS namespaces distinct; no shared pipe, socket, lock or regular file; Xvfb socket inodes and addresses differ; a write in one sibling invisible to the other; shared_character_devices: ["chr:6:3:/dev/null", "chr:6:7:/dev/urandom"]; PASS_literal: false
C3 vs the cold-pair controlthe cold pair shares the same two devices; introduced_by_restore is empty; PASS_vs_cold_control: true; under revision 2a, PASS_revision_2a: true (reseeded run)

Latency and memory, no hygiene (measured; preliminary, not a usable restore)

Armp50p95vs COLD p50
COLD2.046 s2.189 s
RESTORE_FULL0.980 s1.220 s2.09x (author-reported bootstrap interval 1.99 to 2.15)
RESTORE_SERVER_ONLY1.215 s1.282 s1.68x, below the bar as predicted

Memory per added session: COLD 229.40 MiB, RESTORE 221.88 MiB, ratio 0.967, cold minus restore interval [6.09, 8.94] MiB. Of the 0.98 s restore p50, the CRIU restore itself is about 0.32 s and the restore call as a whole about 0.76 s; the rest is the readiness check.

Not pre-registered, page cache dropped before every start (10 starts per arm): COLD p50 2.587 s, RESTORE_FULL 1.380 s, ratio 1.87x; server-only 1.55x. With the 253 MiB image read from disk the 2x target is not met. The sandbox disk is a virtual device; a real host's would differ and was not measured.

The clone problem, shown (measured, clone_hygiene.json)

With the reseed off, two siblings from one checkpoint reported the same hostname, the same next random.random() from the guest server (0.6463347644353902 in both), the same next three page Math.random() values (0.49652, 0.49981, 0.91877), the same pre-checkpoint cookie (test string secret-token-pre-checkpoint), and the same Python string-hash seed. Contrary to what was registered, numpy's global generator differed (it is seeded lazily) and crypto.getRandomValues differed (kernel-backed). With the reseed on: hostnames differ, Python draws differ (0.6995 vs 0.6990), cookies are gone in both, the new-context page's Math.random() differs; reseed cost 0.198 and 0.215 s. For a checkpoint not declared clean (stand-in cookie task-login-token), the cookie is preserved in both siblings and the page's Math.random() sequence stays identical: the stated residual. clone_task_semantics.json: the public task scores 0.0 then 1.0 from a reseeded restored session; session clock minus host clock -0.185 s.

Latency and memory with the reseed (measured, probe_reseeded.json; the usable restore)

Armp50p95vs COLD p50
COLD2.096 s2.236 s
RESTORE_FULL, reseeded1.188 s1.290 s1.76x (author-reported interval 1.73 to 1.83): FAIL of the 2x bar
RESTORE_SERVER_ONLY, reseeded1.245 s1.374 s1.68x

Memory per added session: COLD 229.59 MiB, RESTORE 238.32 MiB, ratio 1.038; cold minus restore interval [-9.86, -7.42] MiB, so restored sessions cost 7.4 to 9.9 MiB more (the extra renderer of the new browser context). Against the registered numeric bar (at most 1.05x) this passes; against the plain statement "no more than cold" it does not. Both are reported. 793 /proc/sys entries before and after were identical.

Failures and surprises

Limitations

Reproduce

PYTHONPATH=<OSWorld checkout>:envd-linux:compare DRUMS_CRIU_HOME=<private criu dir> \
  <osworld venv python> -m envd_linux.l1proto.criu_probe --out probe.json \
  --task <e0df059f.json> --starts 20 --trials 3            # add --drop-caches for the cache-cold run
pytest envd-linux/tests/test_criu_clone_hygiene.py

DRUMS_CRIU_HOME holds x-4.2/usr/sbin/criu and its private libraries; needs root, overlayfs and a kernel CRIU 4.2 supports. Check latency.ratio_cold_over_restore_full_p50 and memory_summary.restore_over_cold against section 14's bars.

Sources