From d70a051a575ea25f4fa01faaecff0f9fd97c9efd Mon Sep 17 00:00:00 2001 From: Overlord Date: Sun, 2 Aug 2026 22:59:02 +0200 Subject: [PATCH] Introduce `deinitIfLive` utility and integrate enhanced memory management --- src/main.zig | 43 +++++++++++++++++++++++++++++++++---------- src/memory/util.zig | 27 +++++++++++++++++++++++++++ src/root.zig | 2 ++ 3 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 src/memory/util.zig diff --git a/src/main.zig b/src/main.zig index e72081a..a06a6cf 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,3 +1,8 @@ +//! Entry point for the zocket daemon. +//! +//! Handles allocator selection (debug-checked vs. release), CLI argument +//! parsing, and configuration path resolution before handing off to `run`. + const zocket = @import("zocket"); const std = @import("std"); @@ -7,6 +12,14 @@ const options = @import("build_options"); const args = zocket.deps.args; +// TODO: switch vars/consts/params to snake_case + +/// Resolves the config path, then delegates to `run`. +/// +/// Allocator strategy: +/// - Debug builds use `DebugAllocator`, which tracks every allocation and +/// panics/exits on leaks or double-frees. +/// - Release builds use `c_allocator` for C ABI interop. pub fn main(init: std.process.Init) !void { const debug = builtin.mode == .Debug; @@ -21,10 +34,13 @@ pub fn main(init: std.process.Init) !void const allocator = if (debug) gpa.allocator() else std.heap.c_allocator; - var tmpArena = std.heap.ArenaAllocator.init(std.heap.c_allocator); - const tmpAllocator = tmpArena.allocator(); + var tmp_arena = std.heap.ArenaAllocator.init(allocator); + var arena_live = true; - var parser = try args.ArgumentParser.init(tmpAllocator, .{ + const tmp_allocator = tmp_arena.allocator(); + errdefer zocket.memory.deinitIfLive(&tmp_arena, &arena_live); + + var parser = try args.ArgumentParser.init(tmp_allocator, .{ .name = "zocket", .version = options.version, .description = "A lightweight Zig WebSocket server and mesh routing daemon.", @@ -39,23 +55,30 @@ pub fn main(init: std.process.Init) !void const path = if (debug) path: { - const tmpPath = result.getString("path") orelse "config/config.toml"; - break :path try allocator.dupe(u8, tmpPath); + const tmp_path = result.getString("path") orelse "config/config.toml"; + break :path try allocator.dupe(u8, tmp_path); } else path: { - const tmpPath = result.getString("path") orelse blk: { - const exe = try std.process.executableDirPathAlloc(io, tmpAllocator); - break :blk try std.Io.Dir.path.join(tmpAllocator, &.{ exe, "config/config.toml" }); + const tmp_path = result.getString("path") orelse blk: { + const exe = try std.process.executableDirPathAlloc(io, tmp_allocator); + break :blk try std.Io.Dir.path.join(tmp_allocator, &.{ exe, "config/config.toml" }); }; - break :path try allocator.dupe(u8, tmpPath); + break :path try allocator.dupe(u8, tmp_path); }; - tmpArena.deinit(); + zocket.memory.deinitIfLive(&tmp_arena, &arena_live); try run(io, allocator, path); } +/// Loads and parses the configuration file at `path`, then hands off to +/// the rest of the application. +/// +/// Owns `path` and frees it once it's no longer needed; Callers must not free `path` themselves! +/// +/// On parse failure, logs a human-readable reason and terminates the +/// process immediately via `std.process.exit`. fn run(io: std.Io, allocator: std.mem.Allocator, path: []const u8) !void { const parsed = zocket.config.parse(io, allocator, path) catch |err| diff --git a/src/memory/util.zig b/src/memory/util.zig new file mode 100644 index 0000000..e5e4791 --- /dev/null +++ b/src/memory/util.zig @@ -0,0 +1,27 @@ +const std = @import("std"); + +/// Deinitializes `allocator` only if it is still live. +/// +/// Pass a pointer to the original allocator owner, such as an +/// `ArenaAllocator` or `DebugAllocator`. +/// +/// If `live` is `true`, calls `allocator.deinit()` and then sets +/// `live` to `false`, preventing a later cleanup path from attempting +/// to deinitialize the same allocator again. +/// +/// The return value of `deinit()` is discarded. +pub fn deinitIfLive(allocator: anytype, live: *bool) void +{ + comptime + { + const T = @TypeOf(allocator); + + if (@typeInfo(T) != .pointer) @compileError("deinitIfLive expects a pointer!"); + if (T == std.mem.Allocator) @compileError("deinitIfLive expects a pointer to an allocator owner, not std.mem.Allocator!"); + } + + if (live.*) { + _ = allocator.deinit(); + live.* = false; + } +} diff --git a/src/root.zig b/src/root.zig index ea614b5..eb9a515 100644 --- a/src/root.zig +++ b/src/root.zig @@ -4,7 +4,9 @@ pub const deps = struct { pub const config = @import("config/config.zig"); +pub const memory = @import("memory/util.zig"); test { _ = config; + _ = memory; }