A Linux-only arena providing fast, single-level rollback for workloads with large state and sparse mutations.
const std = @import("std");
const znapshot = @import("znapshot");
const MemfdArena = znapshot.MemfdArena;
pub fn main(_: std.process.Init) !void {
var arena = try MemfdArena.init(std.heap.smp_allocator, 4096);
defer arena.deinit();
var allocator = arena.threadSafeAllocator();
const number = try allocator.create([10]usize);
for (0..10) |i| {
number[i] = i * 10;
}
std.debug.print("Nums set to {any} before snapshot.\n", .{number});
try arena.snapshot();
for (0..10) |iter| {
for (0..10) |i| {
number[i] = number[i] * 10;
}
if (iter == 5) {
// Updates the snapshotted memory to include the changes up to this point.
try arena.commit();
}
std.debug.print("[{}] Num set to {any} after the snapshot\n", .{ iter, number });
// Restores the memory to the snapshot state.
try arena.restore();
std.debug.print("[{}] After the restore, the value became {any}\n\n", .{ iter, number });
}
}Znapshot reserves the requested capacity in a Linux
memfd and maps
it twice. The arena is the mapping returned through the allocator; a second,
library-internal shared mapping provides direct access to the backing state.
Before the first snapshot, the arena is shared, so initialization writes go
straight into the memfd.
Taking a snapshot records the bump allocator's current end and remaps the
allocated prefix MAP_PRIVATE at the same address. From then on, the kernel's
normal copy-on-write mechanism preserves the snapshot: the first write to a
page creates a private anonymous copy, while the original file-backed page
remains unchanged in the memfd. No write barrier, signal handler, or watcher
thread is involved.
restore() uses madvise(MADV_DONTNEED)
to discard the private copies and then rewinds the bump allocator. The arena
therefore sees the original memfd-backed pages again. Restore does not consume
the snapshot, so the same snapshot can be restored repeatedly without scanning
or iterating over dirty pages.
Pages allocated beyond the snapshot remain shared whenever page alignment allows it. Their writes already land in the memfd, and restoring simply makes those allocations unreachable by rewinding the allocator. The page containing the snapshot boundary remains private so bytes that existed at snapshot time can still be restored correctly.
commit() makes the current state the new snapshot state. It uses
PAGEMAP_SCAN on /proc/self/pagemap to find present or swapped pages that
are no longer file-backed—the private CoW pages. Their contents are copied
through the shared base mapping into the memfd, and the private copies are
discarded so future writes can CoW again. Any newly committed extension is
then remapped private, and the saved allocator end is advanced.
MIT © 2026 Abdullah Eryüzlü