Layered ArenaAllocator

Following code has been reported a memory leak by GeneralPurposeAllocator.

const std = @import("std");

const Foo = struct {
    arena: *std.heap.ArenaAllocator,
    x1: []const u8,
    x2: []const u8,

    pub fn init(allocator: std.mem.Allocator) !Foo {
        var arena = try allocator.create(std.heap.ArenaAllocator);
        arena.* = std.heap.ArenaAllocator.init(allocator);

        const a = arena.allocator();

        return .{
            .arena = arena,
            .x1 = try a.dupe(u8, "X1"),
            .x2 = try a.dupe(u8, "X2"),
        };
    }
    pub fn deinit(self: *Foo) void {
        const a = self.arena.child_allocator;
        self.arena.deinit();
        a.destroy(self.arena);
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
    defer {
        std.debug.print("Leak?: {}\n", .{gpa.detectLeaks()}); // <----- Report memory leaks
        arena.deinit();
    }

    var foo = try Foo.init(arena.allocator());
    defer foo.deinit();
}

Foo instance release themselves arena to return memory to parent arena.
But a memory leak is reported by gpa.

Sometime, realloc has occured, memory leaks is reported from gpa.
It seems that the ArenaAllocator is pooling the returned memory.

Is this layered ArenaAllocator the anti-pattern?

$ zig version
0.14.0-dev.6+0ba64e9ce

You perform the leak check before you call arena.deinit(), so naturally it thinks that you leaked memory.

Also I would recommend to call gpa.deinit() in the future, this will also free the OS resource and still give you a detailed stacktrace of where the leaked memory got allocated.

3 Likes

Exactly!
Two memory leaks found.

One is I created…oh
Other one is due to patch to package. (the original code is not leaks but occur seffault)

And nested ArenaAllocator has no problem.