From 32d7db1668723a638640b496be8eb9e0cbe60a84 Mon Sep 17 00:00:00 2001 From: Wesley Ray Date: Sat, 1 Aug 2026 18:22:23 -0400 Subject: [PATCH] Arm the watchdog for every mode, not just snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The watchdog was spawned inside snapshot.run, so legacy highlights mode ran without it. That is the mode NickelMenu invokes, against the endpoint that stopped responding and hung a run for 16 days — so the exact failure being fixed was still reachable from the device's menu. Moves the spawn into main, ahead of mode dispatch, so both paths are covered. Co-Authored-By: Claude Opus 5 --- src/main.zig | 6 +++++- src/snapshot.zig | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main.zig b/src/main.zig index 077529e..cd7b3bd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -169,15 +169,19 @@ pub fn main() !void { std.fmt.parseInt(u64, args[4], 10) catch snapshot.default_timeout_secs else snapshot.default_timeout_secs; + try snapshot.startWatchdog(timeout); return snapshot.run( allocator, args[2], if (args.len >= 4) args[3] else null, snapshot.default_snapshot_path, - timeout, ); } + // Legacy mode uploads too, so it needs the same protection: the endpoint it posts to + // is what hung a run for 16 days. + try snapshot.startWatchdog(snapshot.default_timeout_secs); + // `highlights [endpoint]`, or the legacy ` [endpoint]` form. const legacy = std.mem.eql(u8, args[1], "highlights"); const rest = if (legacy) args[2..] else args[1..]; diff --git a/src/snapshot.zig b/src/snapshot.zig index 4f74e90..cfef56c 100644 --- a/src/snapshot.zig +++ b/src/snapshot.zig @@ -35,6 +35,13 @@ fn watchdog(secs: u64) void { std.process.exit(2); } +/// Arm the watchdog for the current process. Every mode needs this, not just snapshot: +/// legacy highlights mode POSTs to the same kind of endpoint and hangs the same way. +pub fn startWatchdog(secs: u64) !void { + const guard = try std.Thread.spawn(.{}, watchdog, .{secs}); + guard.detach(); +} + fn sha256Hex(data: []const u8, out: *[64]u8) void { var digest: [32]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(data, &digest, .{}); @@ -97,16 +104,13 @@ fn writeSnapshot(allocator: std.mem.Allocator, db_path: []const u8, snap_path: [ if (c.sqlite3_close(db) != c.SQLITE_OK) return error.DatabaseCloseFailed; } +/// Caller is expected to have armed `startWatchdog` already. pub fn run( allocator: std.mem.Allocator, db_path: []const u8, endpoint: ?[]const u8, snap_path: []const u8, - timeout_secs: u64, ) !void { - const guard = try std.Thread.spawn(.{}, watchdog, .{timeout_secs}); - guard.detach(); - try writeSnapshot(allocator, db_path, snap_path); // Database handle is released from here on.