use arena allocator for cli commands

This commit is contained in:
Emil Lerch 2026-04-23 06:38:09 -07:00
parent 614f846a41
commit a44ad0b7d0
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -145,12 +145,15 @@ fn resolveUserPath(
}
pub fn main() !u8 {
// Long-lived allocator for things that span the whole process. Only
// actually used for the early argsAlloc and the TUI path CLI
// commands run under a per-invocation arena (see below).
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const gpa_alloc = gpa.allocator();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
const args = try std.process.argsAlloc(gpa_alloc);
defer std.process.argsFree(gpa_alloc, args);
// Single buffered writer for all stdout output
var stdout_buf: [4096]u8 = undefined;
@ -194,19 +197,38 @@ pub fn main() !u8 {
const color = @import("format.zig").shouldUseColor(globals.no_color);
var config = zfin.Config.fromEnv(allocator);
defer config.deinit();
const command = args[globals.cursor];
const cmd_args = args[globals.cursor + 1 ..];
// Interactive TUI -- delegates to the TUI module (owns its own DataService).
// Interactive TUI: long-lived, per-frame allocations benefit from a
// real (non-arena) allocator. Runs against `gpa` directly.
if (std.mem.eql(u8, command, "interactive") or std.mem.eql(u8, command, "i")) {
var tui_config = zfin.Config.fromEnv(gpa_alloc);
defer tui_config.deinit();
try out.flush();
try tui.run(allocator, config, globals.portfolio_path, globals.watchlist_path, cmd_args);
try tui.run(gpa_alloc, tui_config, globals.portfolio_path, globals.watchlist_path, cmd_args);
return 0;
}
// Per-invocation arena
//
// CLI commands do a batch of work then exit. Almost every allocation
// they make has the same lifetime (the invocation). An arena matched
// to that unit gives us three wins: skip per-allocation bookkeeping,
// ignore all the per-object `defer X.deinit()` calls (they become
// no-ops but remain correct code if the function is ever called from
// a non-arena context), and avoid gpa's leak-checking overhead for
// ephemeral state we're about to discard anyway.
//
// See models/portfolio.zig for the "match the arena to the unit of
// work" principle. Here the unit is one `zfin <subcommand>`.
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var config = zfin.Config.fromEnv(allocator);
defer config.deinit();
// Version: doesn't need DataService; uses build_info + Config paths.
if (std.mem.eql(u8, command, "version")) {
commands.version.run(config, cmd_args, out) catch |err| switch (err) {