Skip to content

Latest commit

 

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

znapshot

A Linux-only arena providing fast, single-level rollback for workloads with large state and sparse mutations.

Usage

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 });
    }
}

How it works

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.

License

MIT © 2026 Abdullah Eryüzlü

About

A Linux-only arena providing fast, single-level rollback for workloads with large state and sparse mutations.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages