I’m using several arenas and I lock them when I use them, so that I don’t accidentally reset them further down the stack, and can only get/reset them in one scope:
code
//! Arenas used by the editor.
/// Temporary allocations that don't survive function scope.
temp: Arena = .{},
/// Used when drawing rows.
render: Arena = .{},
/// Used when building edit targets.
targets: Arena = .{},
///////////////////////////////////////////////////////////////////////////////
//
// Types
//
///////////////////////////////////////////////////////////////////////////////
const Kind = enum { temp, targets, render };
const Arena = struct {
/// Underlying arena instance.
instance: std.heap.ArenaAllocator = undefined,
/// True when an arena is already being used and shouldn't be reset.
///
/// For example, if an outer scope starts using an arena, resetting it on
/// scope exit, but it calls another function that does the same, if the
/// second function resets the arena it invalidates all memory.
///
/// To avoid this, arenas can only be used in one scope, and if they are
/// needed further down their allocator must be passed explicitly.
busy: bool = false,
};
///////////////////////////////////////////////////////////////////////////////
//
// Init/deinit
//
///////////////////////////////////////////////////////////////////////////////
pub fn init(gpa: mem.Allocator) Arenas {
return .{
.temp = .{ .instance = .init(gpa) },
.render = .{ .instance = .init(gpa) },
.targets = .{ .instance = .init(gpa) },
};
}
pub fn deinit(self: *const Arenas) void {
self.temp.instance.deinit();
self.render.instance.deinit();
self.targets.instance.deinit();
}
///////////////////////////////////////////////////////////////////////////////
//
// Methods
//
///////////////////////////////////////////////////////////////////////////////
/// Resets arenas with different strategies.
pub fn reset(self: *Arenas, comptime which: Kind) void {
switch (which) {
.temp => {
self.temp.busy = false;
_ = self.temp.instance.reset(.{ .retain_with_limit = 1024 * 1024 });
},
.targets => {
self.targets.busy = false;
_ = self.targets.instance.reset(.{ .retain_with_limit = 1024 * 1024 });
},
.render => {
self.render.busy = false;
_ = self.render.instance.reset(.retain_capacity);
},
}
}
/// Returns an arena allocator.
pub fn get(self: *Arenas, comptime which: Kind) !mem.Allocator {
switch (which) {
.temp => {
if (self.temp.busy) {
return error.ArenaIsBusy;
}
else {
self.temp.busy = true;
return self.temp.instance.allocator();
}
},
.targets => {
if (self.targets.busy) {
return error.ArenaIsBusy;
}
else {
self.targets.busy = true;
return self.targets.instance.allocator();
}
},
.render => {
if (self.render.busy) {
return error.ArenaIsBusy;
}
else {
self.render.busy = true;
return self.render.instance.allocator();
}
},
}
}
///////////////////////////////////////////////////////////////////////////////
//
// Tests
//
///////////////////////////////////////////////////////////////////////////////
test "Arenas.get rejects a busy arena until reset" {
inline for ([_]Kind{ .temp, .targets, .render }) |kind|
{
var arenas = Arenas.init(std.testing.allocator);
defer arenas.deinit();
_ = try arenas.get(kind);
try testing.expectError(error.ArenaIsBusy, arenas.get(kind));
arenas.reset(kind);
_ = try arenas.get(kind);
}
}
test "Arenas.reset only clears the requested arena" {
var arenas = Arenas.init(std.testing.allocator);
defer arenas.deinit();
_ = try arenas.get(.temp);
arenas.reset(.targets);
try testing.expectError(error.ArenaIsBusy, arenas.get(.temp));
_ = try arenas.get(.targets);
}
///////////////////////////////////////////////////////////////////////////////
//
// Constants
//
///////////////////////////////////////////////////////////////////////////////
const Arenas = @This();
const std = @import("std");
const mem = std.mem;
const testing = std.testing;
But I’m wondering if these busy checks can be moved to comptime, because from the functions flow it should be evident already at comptime if an arena is being used where it shouldn’t (because it’s being used somewhere up in the stack). Is this somehow possible?