use arena for refresh loop
All checks were successful
Generic zig build / build (push) Successful in 1m23s
Generic zig build / deploy (push) Successful in 22s

This commit is contained in:
Emil Lerch 2026-06-01 08:29:56 -07:00
parent 0897eb850f
commit dbe487e0f4
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -508,27 +508,31 @@ fn writeNewPortfolio(io: std.Io, allocator: std.mem.Allocator, path: []const u8,
// Refresh command
fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process.Environ.Map) !void {
var config = zfin.Config.fromEnv(io, allocator, environ);
fn refresh(
io: std.Io,
gpa: std.mem.Allocator,
environ: *const std.process.Environ.Map,
) !void {
var config = zfin.Config.fromEnv(io, gpa, environ);
defer config.deinit();
var svc = zfin.DataService.init(io, allocator, config);
var svc = zfin.DataService.init(io, gpa, config);
defer svc.deinit();
const portfolio_path = environ.get("ZFIN_PORTFOLIO") orelse "portfolio.srf";
const data = std.Io.Dir.cwd().readFileAlloc(io, portfolio_path, allocator, .limited(10 * 1024 * 1024)) catch {
const data = std.Io.Dir.cwd().readFileAlloc(io, portfolio_path, gpa, .limited(10 * 1024 * 1024)) catch {
log.err("failed to read portfolio: {s}", .{portfolio_path});
return error.ReadFailed;
};
defer allocator.free(data);
defer gpa.free(data);
var portfolio = zfin.cache.deserializePortfolio(allocator, data) catch {
var portfolio = zfin.cache.deserializePortfolio(gpa, data) catch {
log.err("failed to parse portfolio", .{});
return error.ParseFailed;
};
defer portfolio.deinit();
var symbols = std.StringHashMap(void).init(allocator);
var symbols = std.StringHashMap(void).init(gpa);
defer symbols.deinit();
for (portfolio.lots) |lot| {
if (lot.security_type != .stock and lot.security_type != .watch) continue;
@ -577,6 +581,25 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
try stdout.flush();
}
// Per-iteration scratch arena. Wraps the gpa so iteration-scoped
// allocations get freed in bulk via `reset(.retain_capacity)` at
// the bottom of each loop iteration no per-allocation defers,
// no manual `free` mistakes. Capacity is retained so we're not
// re-acquiring pages every symbol.
//
// What goes through this arena: any per-iteration scratch we
// allocate ourselves (CIK dupes, future symbol-derived strings).
// NOT the `FetchResult` payloads those are owned by the
// service's gpa and freed via `result.deinit()` at their own
// call sites.
//
// We don't use juicy-main's `init.arena` because that's
// process-lifetime; resetting it mid-run would clobber other
// startup allocations.
var iter_arena_state = std.heap.ArenaAllocator.init(gpa);
defer iter_arena_state.deinit();
const iter_arena = iter_arena_state.allocator();
var it = symbols.iterator();
while (it.next()) |entry| {
const sym = entry.key_ptr.*;
@ -637,14 +660,16 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
// had it used to chain into entity_facts below.
// NotFound is logged as `n/a` (symbol genuinely has no
// Wikidata entry) and doesn't flip sym_ok.
var cik_buf: ?[]u8 = null;
defer if (cik_buf) |b| allocator.free(b);
//
// The CIK dupe goes through `iter_arena`. No defer-free
// needed the arena is reset at end of iteration.
var cik_str: ?[]const u8 = null;
try printRateLimitWait(&svc, stdout);
if (svc.getClassification(sym, .{})) |result| {
defer result.deinit();
if (result.data.len > 0) {
if (result.data[0].cik) |cik| {
cik_buf = allocator.dupe(u8, cik) catch null;
cik_str = iter_arena.dupe(u8, cik) catch null;
}
}
try stdout.print(", classification ok", .{});
@ -678,7 +703,7 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
// have an EDGAR CIK from the ticker map (production
// zfin chains entity_facts off Wikidata's CIK, so the
// server warms the cache the same way).
if (cik_buf) |cik| {
if (cik_str) |cik| {
try printRateLimitWait(&svc, stdout);
if (svc.getEntityFacts(cik, .{})) |result| {
result.deinit();
@ -696,6 +721,10 @@ fn refresh(io: std.Io, allocator: std.mem.Allocator, environ: *const std.process
try stdout.flush();
if (sym_ok) success_count += 1 else fail_count += 1;
// Reset per-iteration scratch. Retains capacity so the
// next iteration's allocations reuse the same pages.
_ = iter_arena_state.reset(.retain_capacity);
}
try stdout.print("\nRefresh complete: {d} ok, {d} failed\n", .{ success_count, fail_count });