Hi,
I’ve been experimenting with ArenaAllocator backed by GeneralPurposeAllocator and ran into confusing crashes. Here’s a minimal example:
const std = @import("std");
const Content = struct {
gpa: std.heap.GeneralPurposeAllocator(.{}),
arena: std.heap.ArenaAllocator,
inner: std.ArrayList(u8),
const Self = @This();
fn new() !Self {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
const allocator = arena.allocator();
var inner = try std.ArrayList(u8).initCapacity(allocator, 10);
try inner.append(allocator, 1);
return Self{
.gpa = gpa,
.arena = arena,
.inner = inner,
};
}
fn deinit(self: *Self) void {
self.arena.deinit();
}
};
test "content" {
var content = try Content.new();
content.deinit();
}
When I run this test, I get a crash:
thread panic: reached unreachable code
/usr/local/Cellar/zig/0.15.2/lib/zig/std/debug.zig:559:14: in assert
if (!ok) unreachable; // assertion failure
/usr/local/Cellar/zig/0.15.2/lib/zig/std/heap/debug_allocator.zig:951:27: in free
assert(self.buckets[size_class_index] == bucket);
...
/Users/.../allocator_learn.zig:27:26: in deinit
self.arena.deinit();
Occasionally, instead of the above, I also see a panic with the message:
thread #1, stop reason = signal SIGABRT
frame #4: debug.defaultPanic(msg="Deadlock detected")
frame #5: Thread.Mutex.DebugImpl.lock
So sometimes it’s an assertion failure inside debug_allocator.zig, other times it’s a deadlock panic.
Interestingly, if I don’t store gpa inside the struct (only keep arena), the program runs fine:
zig
const Content = struct {
arena: std.heap.ArenaAllocator,
inner: std.ArrayList(u8),
...
};
This version works without crashing.
-
Why does storing
gpainside the struct causearena.deinit()to crash or deadlock, while leavinggpaas a local variable innew()works fine? -
I thought the correct order was simply
arena.deinit()thengpa.deinit(), but here I’m not even callinggpa.deinit(). Is there some subtle interaction betweenArenaAllocatorandGeneralPurposeAllocator’s debug tracking that makes this unsafe? -
What’s the recommended ownership/lifetime pattern for using
ArenaAllocatorbacked by GPA?
Any insights would be greatly appreciated. Thanks!