From c42adf2d947d9c4aaf87bae5b0672b7a2af11784 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Tue, 28 Jul 2026 15:47:16 -0500 Subject: [PATCH] main: move value parsers into internal/runtime/cli Go-duration, boolean and size parsing are pure functions of their input and belong beside the flag derivation, not inline in the entry point. Moves the eight parsers and the four tests that pin their upstream semantics. main.zig 1416 -> 1263 lines. The 856-line `pub fn main` is untouched: that is argument dispatch and wiring, and collapsing it onto ServeOptions is a behavioural change that deserves its own pass rather than riding along with a file move. Co-Authored-By: Claude Opus 5 (1M context) --- src/internal/runtime/cli.zig | 164 ++++++++++++++++++++++++++++++ src/main.zig | 189 ++++------------------------------- 2 files changed, 182 insertions(+), 171 deletions(-) diff --git a/src/internal/runtime/cli.zig b/src/internal/runtime/cli.zig index 8d33e20..1d882ee 100644 --- a/src/internal/runtime/cli.zig +++ b/src/internal/runtime/cli.zig @@ -207,3 +207,167 @@ pub const ServeOptions = struct { .log_format = .{ .serve = false }, }; }; + +// === value parsers === +// +// Go-duration, boolean and size syntax, lifted out of main.zig. These are the +// shapes upstream's flag library accepts, and they are pure functions of their +// input, so they belong beside the flag derivation rather than inline in a +// 1,400-line entry point. + +const Io = std.Io; +const server_mod = @import("../serve/server.zig"); + +pub fn parseDurationNanoseconds(raw: []const u8) !u64 { + return parseDurationMagnitude(raw, std.math.maxInt(i64)); +} +pub fn parseDurationMagnitude(raw: []const u8, limit: u128) !u64 { + if (raw.len == 0) return error.InvalidDuration; + var i: usize = 0; + if (raw[i] == '+') { + i += 1; + if (i == raw.len) return error.InvalidDuration; + } else if (raw[i] == '-') return error.InvalidDuration; + if (std.mem.eql(u8, raw[i..], "0")) return 0; + + const Unit = struct { name: []const u8, nanoseconds: u64 }; + const units = [_]Unit{ + .{ .name = "ns", .nanoseconds = 1 }, + .{ .name = "us", .nanoseconds = std.time.ns_per_us }, + .{ .name = "µs", .nanoseconds = std.time.ns_per_us }, + .{ .name = "μs", .nanoseconds = std.time.ns_per_us }, + .{ .name = "ms", .nanoseconds = std.time.ns_per_ms }, + .{ .name = "s", .nanoseconds = std.time.ns_per_s }, + .{ .name = "m", .nanoseconds = std.time.ns_per_min }, + .{ .name = "h", .nanoseconds = std.time.ns_per_hour }, + }; + var total: u128 = 0; + var tokens: usize = 0; + while (i < raw.len) : (tokens += 1) { + var whole: u128 = 0; + var digits: usize = 0; + while (i < raw.len and std.ascii.isDigit(raw[i])) : (i += 1) { + const digit = raw[i] - '0'; + if (whole > (limit - digit) / 10) return error.DurationOverflow; + whole = whole * 10 + digit; + digits += 1; + } + + var fraction: u128 = 0; + var fraction_scale: u128 = 1; + if (i < raw.len and raw[i] == '.') { + i += 1; + while (i < raw.len and std.ascii.isDigit(raw[i])) : (i += 1) { + digits += 1; + // Nanosecond resolution never needs more than 18 decimal + // places for the largest supported unit; Go truncates below + // one nanosecond as well. + if (fraction_scale < 1_000_000_000_000_000_000) { + fraction = fraction * 10 + (raw[i] - '0'); + fraction_scale *= 10; + } + } + } + if (digits == 0) return error.InvalidDuration; + + var unit: ?Unit = null; + for (units) |candidate| { + if (std.mem.startsWith(u8, raw[i..], candidate.name)) { + unit = candidate; + i += candidate.name.len; + break; + } + } + const selected = unit orelse return error.InvalidDuration; + if (whole > limit / selected.nanoseconds) return error.DurationOverflow; + var component = whole * selected.nanoseconds; + component += (fraction * selected.nanoseconds) / fraction_scale; + if (component > limit - total) return error.DurationOverflow; + total += component; + } + if (tokens == 0) return error.InvalidDuration; + return @intCast(total); +} +pub fn parseDurationCeilSeconds(raw: []const u8) !u64 { + const nanoseconds = try parseDurationNanoseconds(raw); + return (nanoseconds + std.time.ns_per_s - 1) / std.time.ns_per_s; +} +pub fn parseGoDuration(raw: []const u8) !Io.Duration { + if (raw.len > 0 and raw[0] == '-') { + if (raw.len == 1 or raw[1] == '+' or raw[1] == '-') return error.InvalidDuration; + const negative_limit: u128 = @as(u128, std.math.maxInt(i64)) + 1; + const magnitude = try parseDurationMagnitude(raw[1..], negative_limit); + return .fromNanoseconds(-@as(i96, magnitude)); + } + return .fromNanoseconds(@intCast(try parseDurationNanoseconds(raw))); +} +pub fn effectiveHttpShutdownDuration(configured: Io.Duration) Io.Duration { + return if (configured.nanoseconds <= 0) .fromSeconds(30) else configured; +} +pub fn effectiveSubscribeSlowWindowNanoseconds(raw: []const u8) !u64 { + const default = server_mod.default_slow_window_ns; + if (raw.len > 0 and raw[0] == '-') { + // Go's DurationFlag accepts a negative duration, then subscribe's + // defaults replace every non-positive value. Parse the magnitude so + // malformed negative values still fail instead of becoming defaults. + _ = try parseDurationNanoseconds(raw[1..]); + return default; + } + const configured = try parseDurationNanoseconds(raw); + return if (configured == 0) default else configured; +} +test "subscribe slow window uses the upstream non-positive sentinel" { + const default = server_mod.default_slow_window_ns; + try std.testing.expectEqual(default, try effectiveSubscribeSlowWindowNanoseconds("0")); + try std.testing.expectEqual(default, try effectiveSubscribeSlowWindowNanoseconds("-250ms")); + try std.testing.expectEqual(@as(u64, 250 * std.time.ns_per_ms), try effectiveSubscribeSlowWindowNanoseconds("250ms")); + try std.testing.expectError(error.InvalidDuration, effectiveSubscribeSlowWindowNanoseconds("-garbage")); +} +test "shutdown durations preserve Go's signed CLI and HTTP default semantics" { + try std.testing.expectEqual(@as(i96, 5 * std.time.ns_per_s), (try parseGoDuration("5s")).nanoseconds); + try std.testing.expectEqual(@as(i96, -250 * std.time.ns_per_ms), (try parseGoDuration("-250ms")).nanoseconds); + try std.testing.expectEqual(@as(i96, 0), (try parseGoDuration("-0")).nanoseconds); + try std.testing.expectEqual(@as(i96, std.math.maxInt(i64)), (try parseGoDuration("9223372036854775807ns")).nanoseconds); + try std.testing.expectEqual(@as(i96, std.math.minInt(i64)), (try parseGoDuration("-9223372036854775808ns")).nanoseconds); + try std.testing.expectEqual(@as(i96, 30 * std.time.ns_per_s), effectiveHttpShutdownDuration(.zero).nanoseconds); + try std.testing.expectEqual(@as(i96, 30 * std.time.ns_per_s), effectiveHttpShutdownDuration(.fromSeconds(-1)).nanoseconds); + try std.testing.expectEqual(@as(i96, 3 * std.time.ns_per_s), effectiveHttpShutdownDuration(.fromSeconds(3)).nanoseconds); + try std.testing.expectError(error.InvalidDuration, parseGoDuration("-garbage")); + try std.testing.expectError(error.InvalidDuration, parseGoDuration("-+1s")); + try std.testing.expectError(error.DurationOverflow, parseGoDuration("9223372036854775808ns")); + try std.testing.expectError(error.DurationOverflow, parseGoDuration("-9223372036854775809ns")); +} +test "segment cache duration matches Go duration and ceil policy" { + try testing.expectEqual(@as(u64, 0), try parseDurationCeilSeconds("0")); + try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds("500ms")); + try testing.expectEqual(@as(u64, 2), try parseDurationCeilSeconds("1500ms")); + try testing.expectEqual(@as(u64, 3600), try parseDurationCeilSeconds("1h")); + try testing.expectEqual(@as(u64, 5400), try parseDurationCeilSeconds("1h30m")); + try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds(".1s")); + try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds("1us")); + try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds("1µs")); + try testing.expectError(error.InvalidDuration, parseDurationCeilSeconds("-1s")); + try testing.expectError(error.InvalidDuration, parseDurationCeilSeconds("1")); + try testing.expectError(error.InvalidDuration, parseDurationCeilSeconds("wat")); + try testing.expectEqual(@as(u64, 1_500_000_000), try parseDurationNanoseconds("1.5s")); +} +pub fn parseNonNegativeUsize(raw: []const u8) !usize { + if (raw.len > 0 and raw[0] == '-') return error.BadArgs; + return std.fmt.parseInt(usize, raw, 10); +} +pub fn parseBool(raw: []const u8) !bool { + if (std.mem.eql(u8, raw, "1") or std.mem.eql(u8, raw, "t") or std.mem.eql(u8, raw, "T") or + std.mem.eql(u8, raw, "true") or std.mem.eql(u8, raw, "TRUE") or std.mem.eql(u8, raw, "True")) return true; + if (std.mem.eql(u8, raw, "0") or std.mem.eql(u8, raw, "f") or std.mem.eql(u8, raw, "F") or + std.mem.eql(u8, raw, "false") or std.mem.eql(u8, raw, "FALSE") or std.mem.eql(u8, raw, "False")) return false; + return error.InvalidBoolean; +} +test "boolean flags match Go strconv syntax" { + try std.testing.expect(try parseBool("true")); + try std.testing.expect(try parseBool("T")); + try std.testing.expect(try parseBool("1")); + try std.testing.expect(!(try parseBool("false"))); + try std.testing.expect(!(try parseBool("F"))); + try std.testing.expect(!(try parseBool("0"))); + try std.testing.expectError(error.InvalidBoolean, parseBool("yes")); +} \ No newline at end of file diff --git a/src/main.zig b/src/main.zig index 9835964..ad7e75b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -31,6 +31,7 @@ const operational = @import("internal/runtime/operational.zig"); const inspect_all = @import("internal/runtime/inspect_all.zig"); const logging = @import("internal/runtime/logging.zig"); const environment = @import("internal/runtime/environment.zig"); +const cli = @import("internal/runtime/cli.zig"); const observability = @import("internal/runtime/observability.zig"); const build_options = @import("build_options"); @@ -178,153 +179,19 @@ test "merge discovery skip is explicit or automatic for either debug selection" /// Parse the non-negative subset of Go's time.ParseDuration used by upstream's /// duration flags. Like Go, fractional values are truncated below 1 ns. -fn parseDurationNanoseconds(raw: []const u8) !u64 { - return parseDurationMagnitude(raw, std.math.maxInt(i64)); -} -fn parseDurationMagnitude(raw: []const u8, limit: u128) !u64 { - if (raw.len == 0) return error.InvalidDuration; - var i: usize = 0; - if (raw[i] == '+') { - i += 1; - if (i == raw.len) return error.InvalidDuration; - } else if (raw[i] == '-') return error.InvalidDuration; - if (std.mem.eql(u8, raw[i..], "0")) return 0; - - const Unit = struct { name: []const u8, nanoseconds: u64 }; - const units = [_]Unit{ - .{ .name = "ns", .nanoseconds = 1 }, - .{ .name = "us", .nanoseconds = std.time.ns_per_us }, - .{ .name = "µs", .nanoseconds = std.time.ns_per_us }, - .{ .name = "μs", .nanoseconds = std.time.ns_per_us }, - .{ .name = "ms", .nanoseconds = std.time.ns_per_ms }, - .{ .name = "s", .nanoseconds = std.time.ns_per_s }, - .{ .name = "m", .nanoseconds = std.time.ns_per_min }, - .{ .name = "h", .nanoseconds = std.time.ns_per_hour }, - }; - var total: u128 = 0; - var tokens: usize = 0; - while (i < raw.len) : (tokens += 1) { - var whole: u128 = 0; - var digits: usize = 0; - while (i < raw.len and std.ascii.isDigit(raw[i])) : (i += 1) { - const digit = raw[i] - '0'; - if (whole > (limit - digit) / 10) return error.DurationOverflow; - whole = whole * 10 + digit; - digits += 1; - } - - var fraction: u128 = 0; - var fraction_scale: u128 = 1; - if (i < raw.len and raw[i] == '.') { - i += 1; - while (i < raw.len and std.ascii.isDigit(raw[i])) : (i += 1) { - digits += 1; - // Nanosecond resolution never needs more than 18 decimal - // places for the largest supported unit; Go truncates below - // one nanosecond as well. - if (fraction_scale < 1_000_000_000_000_000_000) { - fraction = fraction * 10 + (raw[i] - '0'); - fraction_scale *= 10; - } - } - } - if (digits == 0) return error.InvalidDuration; - - var unit: ?Unit = null; - for (units) |candidate| { - if (std.mem.startsWith(u8, raw[i..], candidate.name)) { - unit = candidate; - i += candidate.name.len; - break; - } - } - const selected = unit orelse return error.InvalidDuration; - if (whole > limit / selected.nanoseconds) return error.DurationOverflow; - var component = whole * selected.nanoseconds; - component += (fraction * selected.nanoseconds) / fraction_scale; - if (component > limit - total) return error.DurationOverflow; - total += component; - } - if (tokens == 0) return error.InvalidDuration; - return @intCast(total); -} -fn parseDurationCeilSeconds(raw: []const u8) !u64 { - const nanoseconds = try parseDurationNanoseconds(raw); - return (nanoseconds + std.time.ns_per_s - 1) / std.time.ns_per_s; -} /// Full signed Go duration surface used by urfave/cli DurationFlag. The /// non-negative parser above remains the implementation shared by flags whose /// downstream types cannot represent negative durations. -fn parseGoDuration(raw: []const u8) !Io.Duration { - if (raw.len > 0 and raw[0] == '-') { - if (raw.len == 1 or raw[1] == '+' or raw[1] == '-') return error.InvalidDuration; - const negative_limit: u128 = @as(u128, std.math.maxInt(i64)) + 1; - const magnitude = try parseDurationMagnitude(raw[1..], negative_limit); - return .fromNanoseconds(-@as(i96, magnitude)); - } - return .fromNanoseconds(@intCast(try parseDurationNanoseconds(raw))); -} /// net/http Server.New applies this default when its configured timeout is /// non-positive; the CLI's websocket client-drain duration does not. -fn effectiveHttpShutdownDuration(configured: Io.Duration) Io.Duration { - return if (configured.nanoseconds <= 0) .fromSeconds(30) else configured; -} -fn effectiveSubscribeSlowWindowNanoseconds(raw: []const u8) !u64 { - const default = server_mod.default_slow_window_ns; - if (raw.len > 0 and raw[0] == '-') { - // Go's DurationFlag accepts a negative duration, then subscribe's - // defaults replace every non-positive value. Parse the magnitude so - // malformed negative values still fail instead of becoming defaults. - _ = try parseDurationNanoseconds(raw[1..]); - return default; - } - const configured = try parseDurationNanoseconds(raw); - return if (configured == 0) default else configured; -} -test "subscribe slow window uses the upstream non-positive sentinel" { - const default = server_mod.default_slow_window_ns; - try std.testing.expectEqual(default, try effectiveSubscribeSlowWindowNanoseconds("0")); - try std.testing.expectEqual(default, try effectiveSubscribeSlowWindowNanoseconds("-250ms")); - try std.testing.expectEqual(@as(u64, 250 * std.time.ns_per_ms), try effectiveSubscribeSlowWindowNanoseconds("250ms")); - try std.testing.expectError(error.InvalidDuration, effectiveSubscribeSlowWindowNanoseconds("-garbage")); -} -test "shutdown durations preserve Go's signed CLI and HTTP default semantics" { - try std.testing.expectEqual(@as(i96, 5 * std.time.ns_per_s), (try parseGoDuration("5s")).nanoseconds); - try std.testing.expectEqual(@as(i96, -250 * std.time.ns_per_ms), (try parseGoDuration("-250ms")).nanoseconds); - try std.testing.expectEqual(@as(i96, 0), (try parseGoDuration("-0")).nanoseconds); - try std.testing.expectEqual(@as(i96, std.math.maxInt(i64)), (try parseGoDuration("9223372036854775807ns")).nanoseconds); - try std.testing.expectEqual(@as(i96, std.math.minInt(i64)), (try parseGoDuration("-9223372036854775808ns")).nanoseconds); - try std.testing.expectEqual(@as(i96, 30 * std.time.ns_per_s), effectiveHttpShutdownDuration(.zero).nanoseconds); - try std.testing.expectEqual(@as(i96, 30 * std.time.ns_per_s), effectiveHttpShutdownDuration(.fromSeconds(-1)).nanoseconds); - try std.testing.expectEqual(@as(i96, 3 * std.time.ns_per_s), effectiveHttpShutdownDuration(.fromSeconds(3)).nanoseconds); - try std.testing.expectError(error.InvalidDuration, parseGoDuration("-garbage")); - try std.testing.expectError(error.InvalidDuration, parseGoDuration("-+1s")); - try std.testing.expectError(error.DurationOverflow, parseGoDuration("9223372036854775808ns")); - try std.testing.expectError(error.DurationOverflow, parseGoDuration("-9223372036854775809ns")); -} -test "segment cache duration matches Go duration and ceil policy" { - const testing = std.testing; - try testing.expectEqual(@as(u64, 0), try parseDurationCeilSeconds("0")); - try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds("500ms")); - try testing.expectEqual(@as(u64, 2), try parseDurationCeilSeconds("1500ms")); - try testing.expectEqual(@as(u64, 3600), try parseDurationCeilSeconds("1h")); - try testing.expectEqual(@as(u64, 5400), try parseDurationCeilSeconds("1h30m")); - try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds(".1s")); - try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds("1us")); - try testing.expectEqual(@as(u64, 1), try parseDurationCeilSeconds("1µs")); - try testing.expectError(error.InvalidDuration, parseDurationCeilSeconds("-1s")); - try testing.expectError(error.InvalidDuration, parseDurationCeilSeconds("1")); - try testing.expectError(error.InvalidDuration, parseDurationCeilSeconds("wat")); - try testing.expectEqual(@as(u64, 1_500_000_000), try parseDurationNanoseconds("1.5s")); -} fn writeVersion(io: Io, args: []const []const u8) !void { if (args.len != 0) return error.BadArgs; @@ -348,11 +215,11 @@ fn runInspectSegment(allocator: std.mem.Allocator, io: Io, args: []const []const if (i >= args.len) return error.BadArgs; mode = parseBlocksMode(args[i]) orelse return error.BadArgs; } else if (std.mem.startsWith(u8, arg, "--blocks-truncate=")) { - truncate = try parseNonNegativeUsize(arg["--blocks-truncate=".len..]); + truncate = try cli.parseNonNegativeUsize(arg["--blocks-truncate=".len..]); } else if (std.mem.eql(u8, arg, "--blocks-truncate")) { i += 1; if (i >= args.len) return error.BadArgs; - truncate = try parseNonNegativeUsize(args[i]); + truncate = try cli.parseNonNegativeUsize(args[i]); } else if (std.mem.startsWith(u8, arg, "--")) { return error.BadArgs; } else if (path != null) { @@ -385,11 +252,11 @@ fn runInspectAll(allocator: std.mem.Allocator, io: Io, args: []const []const u8) } else if (std.mem.eql(u8, arg, "--skip-unsealed")) { skip_unsealed = true; } else if (std.mem.startsWith(u8, arg, "--collections-truncate=")) { - truncate = try parseNonNegativeUsize(arg["--collections-truncate=".len..]); + truncate = try cli.parseNonNegativeUsize(arg["--collections-truncate=".len..]); } else if (std.mem.eql(u8, arg, "--collections-truncate")) { i += 1; if (i >= args.len) return error.BadArgs; - truncate = try parseNonNegativeUsize(args[i]); + truncate = try cli.parseNonNegativeUsize(args[i]); } else { return error.BadArgs; } @@ -415,28 +282,8 @@ fn parseBlocksMode(raw: []const u8) ?operational.BlocksMode { return null; } -fn parseNonNegativeUsize(raw: []const u8) !usize { - if (raw.len > 0 and raw[0] == '-') return error.BadArgs; - return std.fmt.parseInt(usize, raw, 10); -} -fn parseBool(raw: []const u8) !bool { - if (std.mem.eql(u8, raw, "1") or std.mem.eql(u8, raw, "t") or std.mem.eql(u8, raw, "T") or - std.mem.eql(u8, raw, "true") or std.mem.eql(u8, raw, "TRUE") or std.mem.eql(u8, raw, "True")) return true; - if (std.mem.eql(u8, raw, "0") or std.mem.eql(u8, raw, "f") or std.mem.eql(u8, raw, "F") or - std.mem.eql(u8, raw, "false") or std.mem.eql(u8, raw, "FALSE") or std.mem.eql(u8, raw, "False")) return false; - return error.InvalidBoolean; -} -test "boolean flags match Go strconv syntax" { - try std.testing.expect(try parseBool("true")); - try std.testing.expect(try parseBool("T")); - try std.testing.expect(try parseBool("1")); - try std.testing.expect(!(try parseBool("false"))); - try std.testing.expect(!(try parseBool("F"))); - try std.testing.expect(!(try parseBool("0"))); - try std.testing.expectError(error.InvalidBoolean, parseBool("yes")); -} const BindAddress = struct { address: Io.net.IpAddress, @@ -691,9 +538,9 @@ pub fn main(init: std.process.Init.Minimal) !void { debug_addr_raw = if (configured.len == 0) null else configured; if (public_addr_raw == null) public_addr_raw = ":8080"; } else if (std.mem.startsWith(u8, arg, "--shutdown-timeout=")) { - shutdown_timeout = try parseGoDuration(arg["--shutdown-timeout=".len..]); + shutdown_timeout = try cli.parseGoDuration(arg["--shutdown-timeout=".len..]); } else if (std.mem.startsWith(u8, arg, "--client-drain-timeout=")) { - client_drain_timeout = try parseGoDuration(arg["--client-drain-timeout=".len..]); + client_drain_timeout = try cli.parseGoDuration(arg["--client-drain-timeout=".len..]); } else if (std.mem.startsWith(u8, arg, "--otel-service-name=")) { otel_service_name = arg["--otel-service-name=".len..]; } else if (std.mem.eql(u8, arg, "--otel-service-name")) { @@ -738,7 +585,7 @@ pub fn main(init: std.process.Init.Minimal) !void { } else if (std.mem.startsWith(u8, arg, "--relay-http=")) { relay_http = arg["--relay-http=".len..]; } else if (std.mem.startsWith(u8, arg, "--compaction-interval=")) { - compaction_interval_ns = try parseDurationNanoseconds(arg["--compaction-interval=".len..]); + compaction_interval_ns = try cli.parseDurationNanoseconds(arg["--compaction-interval=".len..]); } else if (std.mem.startsWith(u8, arg, "--compaction-tombstone-cap=")) { compaction_tombstone_cap = try std.fmt.parseInt(usize, arg["--compaction-tombstone-cap=".len..], 10); } else if (std.mem.startsWith(u8, arg, "--compaction-rewrite-workers=")) { @@ -747,7 +594,7 @@ pub fn main(init: std.process.Init.Minimal) !void { const seconds = try std.fmt.parseInt(u64, arg["--retry-interval=".len..], 10); retry_interval_ns = try std.math.mul(u64, seconds, std.time.ns_per_s); } else if (std.mem.startsWith(u8, arg, "--failed-repo-retry-interval=")) { - retry_interval_ns = try parseDurationNanoseconds(arg["--failed-repo-retry-interval=".len..]); + retry_interval_ns = try cli.parseDurationNanoseconds(arg["--failed-repo-retry-interval=".len..]); } else if (std.mem.startsWith(u8, arg, "--failed-repo-retry-workers=")) { const configured = try std.fmt.parseInt(usize, arg["--failed-repo-retry-workers=".len..], 10); retry_workers = if (configured == 0) retry_mod.default_workers else configured; @@ -755,12 +602,12 @@ pub fn main(init: std.process.Init.Minimal) !void { const configured = try std.fmt.parseInt(usize, arg["--failed-repo-retry-host-workers=".len..], 10); retry_host_workers = if (configured == 0) retry_mod.default_host_workers else configured; } else if (std.mem.startsWith(u8, arg, "--failed-repo-retry-max-delay=")) { - const configured = try parseDurationNanoseconds(arg["--failed-repo-retry-max-delay=".len..]); + const configured = try cli.parseDurationNanoseconds(arg["--failed-repo-retry-max-delay=".len..]); retry_max_delay_ns = if (configured == 0) retry_mod.default_max_delay_ns else configured; } else if (std.mem.startsWith(u8, arg, "--segment-cache-max-age=")) { - segment_cache_max_age_s = try parseDurationCeilSeconds(arg["--segment-cache-max-age=".len..]); + segment_cache_max_age_s = try cli.parseDurationCeilSeconds(arg["--segment-cache-max-age=".len..]); } else if (std.mem.startsWith(u8, arg, "--cursor-lookback=")) { - cursor_lookback_ns = try parseDurationNanoseconds(arg["--cursor-lookback=".len..]); + cursor_lookback_ns = try cli.parseDurationNanoseconds(arg["--cursor-lookback=".len..]); } else if (std.mem.startsWith(u8, arg, "--subscribe-read-log-retention-bytes=")) { const configured = try std.fmt.parseInt(i64, arg["--subscribe-read-log-retention-bytes=".len..], 10); subscribe_read_log_retention_bytes = if (configured <= 0) @@ -774,7 +621,7 @@ pub fn main(init: std.process.Init.Minimal) !void { else std.math.cast(usize, configured) orelse return error.Overflow; } else if (std.mem.startsWith(u8, arg, "--subscribe-slow-window=")) { - subscribe_slow_window_ns = try effectiveSubscribeSlowWindowNanoseconds(arg["--subscribe-slow-window=".len..]); + subscribe_slow_window_ns = try cli.effectiveSubscribeSlowWindowNanoseconds(arg["--subscribe-slow-window=".len..]); } else if (std.mem.startsWith(u8, arg, "--subscribe-slow-min-rate=")) { const configured = try std.fmt.parseFloat(f64, arg["--subscribe-slow-min-rate=".len..]); subscribe_slow_min_rate = if (configured <= 0) server_mod.default_slow_min_rate else configured; @@ -834,13 +681,13 @@ pub fn main(init: std.process.Init.Minimal) !void { } else if (std.mem.eql(u8, arg, "--skip-merge-discovery")) { skip_merge_discovery = true; } else if (std.mem.startsWith(u8, arg, "--skip-merge-discovery=")) { - skip_merge_discovery = try parseBool(arg["--skip-merge-discovery=".len..]); + skip_merge_discovery = try cli.parseBool(arg["--skip-merge-discovery=".len..]); } else if (std.mem.eql(u8, arg, "--no-verify")) { do_verify = false; } else if (std.mem.eql(u8, arg, "--disable-repo-action-rate-limits")) { repo_action_rate_limits = false; } else if (std.mem.startsWith(u8, arg, "--disable-repo-action-rate-limits=")) { - repo_action_rate_limits = !(try parseBool(arg["--disable-repo-action-rate-limits=".len..])); + repo_action_rate_limits = !(try cli.parseBool(arg["--disable-repo-action-rate-limits=".len..])); } else if (std.mem.eql(u8, arg, "--stdout")) { to_stdout = true; } else if (std.mem.eql(u8, arg, "serve") and !explicit_serve) { @@ -1060,7 +907,7 @@ pub fn main(init: std.process.Init.Minimal) !void { .max_conn = 4096, .max_message_size = 10_000_000, .websocket_shutdown_grace = client_drain_timeout, - .http_shutdown_grace = effectiveHttpShutdownDuration(shutdown_timeout), + .http_shutdown_grace = cli.effectiveHttpShutdownDuration(shutdown_timeout), // Match upstream v1's coder/websocket defaults exactly. The handler // declines this per connection for v2 and custom-zstd subscribers. .compression = .{ @@ -1089,7 +936,7 @@ pub fn main(init: std.process.Init.Minimal) !void { .max_conn = 256, .max_message_size = 1024, .websocket_shutdown_grace = client_drain_timeout, - .http_shutdown_grace = effectiveHttpShutdownDuration(shutdown_timeout), + .http_shutdown_grace = cli.effectiveHttpShutdownDuration(shutdown_timeout), .compression = null, .handshake = .{ .max_size = 16 * 1024, .max_headers = 64 }, }) @@ -1375,7 +1222,7 @@ fn shutdownTracing(runtime: *observability.Runtime, configured: Io.Duration) !vo } fn shutdownTimeoutMillis(configured: Io.Duration) u64 { - const timeout = effectiveHttpShutdownDuration(configured); + const timeout = cli.effectiveHttpShutdownDuration(configured); return @intCast(@divFloor(timeout.nanoseconds + std.time.ns_per_ms - 1, std.time.ns_per_ms)); } -- 2.51.2