Skip to content

Commit c0a433d

Browse files
committed
check-mirrors: dump error trace if fetch returns error
1 parent bd3d49e commit c0a433d

File tree

1 file changed

+82
-15
lines changed

1 file changed

+82
-15
lines changed

check-mirrors/main.zig

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ const File = struct {
6060
ok: u64,
6161
content_mismatch,
6262
bad_status: std.http.Status,
63-
err: anyerror,
63+
fetch_error: GetResult.FetchError,
6464

6565
pub fn format(res: Result, comptime fmt: []const u8, options: std.fmt.FormatOptions, w: anytype) !void {
6666
if (fmt.len != 0) std.fmt.invalidFmtError(fmt, res);
6767
_ = options;
6868
switch (res) {
6969
.ok => |query_ns| try w.print("{}", .{std.fmt.fmtDuration(query_ns)}),
7070
.content_mismatch => try w.writeAll(":warning: incorrect contents"),
71-
.err => |err| try w.print(":warning: error: {s}", .{@errorName(err)}),
71+
.fetch_error => |fe| try w.print(":warning: error: {s}", .{@errorName(fe.err)}),
7272
.bad_status => |s| try w.print(":warning: bad status: {d} {s}", .{ @intFromEnum(s), s.phrase() orelse "?" }),
7373
}
7474
}
@@ -107,7 +107,10 @@ pub fn main() Allocator.Error!u8 {
107107

108108
var arena_state: std.heap.ArenaAllocator = .init(gpa);
109109
defer arena_state.deinit();
110-
const arena = arena_state.allocator();
110+
var thread_safe_arena_state: std.heap.ThreadSafeAllocator = .{
111+
.child_allocator = arena_state.allocator(),
112+
};
113+
const arena = thread_safe_arena_state.allocator();
111114

112115
const args = std.process.argsAlloc(arena) catch @panic("argsAlloc failed");
113116
if (args.len != 3) @panic("usage: check-mirrors <mirrors.ziggy> <out.md>");
@@ -132,7 +135,7 @@ pub fn main() Allocator.Error!u8 {
132135
break :mirrors mirrors;
133136
};
134137

135-
const download_json_raw: []const u8 = try httpGetZsf(arena, &http_client, download_json_url);
138+
const download_json_raw: []const u8 = try httpGetZsf(arena, arena, &http_client, download_json_url);
136139
const download_json: std.json.Value = std.json.parseFromSliceLeaky(std.json.Value, arena, download_json_raw, .{}) catch |err| {
137140
std.debug.panic("failed to parse download json: {s}", .{@errorName(err)});
138141
};
@@ -200,6 +203,9 @@ pub fn main() Allocator.Error!u8 {
200203
defer out_al.deinit(gpa);
201204
const out = out_al.writer(gpa);
202205

206+
var error_traces_out: std.ArrayListUnmanaged(u8) = .empty;
207+
defer error_traces_out.deinit(gpa);
208+
203209
try out.writeAll("## Tarballs\n\n");
204210

205211
// Write a table header that looks like this (minus the whitespace on the second line):
@@ -218,6 +224,21 @@ pub fn main() Allocator.Error!u8 {
218224
for (mirrors) |m| {
219225
if (m.file_result != .ok) any_failures = true;
220226
try out.print(" {} |", .{m.file_result});
227+
trace: {
228+
if (builtin.strip_debug_info) break :trace;
229+
const fe = switch (m.file_result) {
230+
.fetch_error => |fe| fe,
231+
else => break :trace,
232+
};
233+
const ert = fe.ert orelse break :trace;
234+
const self_info = std.debug.getSelfDebugInfo() catch break :trace;
235+
try error_traces_out.append(gpa, '\n');
236+
std.debug.writeStackTrace(ert, error_traces_out.writer(gpa), self_info, .no_color) catch |err| switch (err) {
237+
error.OutOfMemory => |e| return e,
238+
else => {},
239+
};
240+
try error_traces_out.append(gpa, '\n');
241+
}
221242
}
222243
}
223244
try out.writeAll("\n| **Avg. time** | |");
@@ -229,6 +250,11 @@ pub fn main() Allocator.Error!u8 {
229250
m.ns_div = 0;
230251
}
231252

253+
if (error_traces_out.items.len > 0) {
254+
try out.print("\n\n### Error Traces\n\n```{s}```", .{error_traces_out.items});
255+
error_traces_out.clearRetainingCapacity();
256+
}
257+
232258
try out.writeAll("\n\n## Signatures\n\n");
233259

234260
// Same table header as before
@@ -245,6 +271,21 @@ pub fn main() Allocator.Error!u8 {
245271
for (mirrors) |m| {
246272
if (m.file_result != .ok) any_failures = true;
247273
try out.print(" {} |", .{m.file_result});
274+
trace: {
275+
if (builtin.strip_debug_info) break :trace;
276+
const fe = switch (m.file_result) {
277+
.fetch_error => |fe| fe,
278+
else => break :trace,
279+
};
280+
const ert = fe.ert orelse break :trace;
281+
const self_info = std.debug.getSelfDebugInfo() catch break :trace;
282+
try error_traces_out.append(gpa, '\n');
283+
std.debug.writeStackTrace(ert, error_traces_out.writer(gpa), self_info, .no_color) catch |err| switch (err) {
284+
error.OutOfMemory => |e| return e,
285+
else => {},
286+
};
287+
try error_traces_out.append(gpa, '\n');
288+
}
248289
}
249290
}
250291
try out.writeAll("\n| **Avg. time** | |");
@@ -254,6 +295,11 @@ pub fn main() Allocator.Error!u8 {
254295
// No need to reset, we're not doing any more checks
255296
}
256297

298+
if (error_traces_out.items.len > 0) {
299+
try out.print("\n\n### Error Traces\n\n```{s}```", .{error_traces_out.items});
300+
error_traces_out.clearRetainingCapacity();
301+
}
302+
257303
try out.writeByte('\n');
258304

259305
{
@@ -289,18 +335,16 @@ fn checkFile(
289335
else
290336
try std.fmt.allocPrint(arena, master_tarball_template, .{file.name});
291337

292-
const trusted_data = try httpGetZsf(gpa, http_client, trusted_url);
338+
const trusted_data = try httpGetZsf(gpa, arena, http_client, trusted_url);
293339
defer gpa.free(trusted_data);
294340

295341
var wg: std.Thread.WaitGroup = .{};
296342

297343
var oom: std.atomic.Value(bool) = .init(false);
298344
for (mirrors) |*m| {
299-
// `arena` isn't thread-safe, so alloc the URL now.
300-
const url = try std.fmt.allocPrint(arena, "{s}/{s}", .{ m.url, file.name });
301345
wg.spawnManager(
302346
checkOneFile,
303-
.{ gpa, http_client, m, url, trusted_data, &oom, prog_node },
347+
.{ gpa, arena, http_client, m, file, trusted_data, &oom, prog_node },
304348
);
305349
}
306350
wg.wait();
@@ -309,16 +353,22 @@ fn checkFile(
309353

310354
fn checkOneFile(
311355
gpa: Allocator,
356+
arena: Allocator,
312357
http_client: *std.http.Client,
313358
mirror: *Mirror,
314-
url: []const u8,
359+
file: *const File,
315360
trusted_data: []const u8,
316361
oom: *std.atomic.Value(bool),
317362
prog_node: std.Progress.Node,
318363
) void {
319364
const mirror_prog_node = prog_node.start(mirror.username, 0);
320365
defer mirror_prog_node.end();
321-
mirror.file_result = if (httpGet(gpa, http_client, url)) |get_result| switch (get_result) {
366+
367+
const url = std.fmt.allocPrint(arena, "{s}/{s}", .{ mirror.url, file.name }) catch |err| switch (err) {
368+
error.OutOfMemory => return oom.store(true, .monotonic),
369+
};
370+
371+
mirror.file_result = if (httpGet(gpa, arena, http_client, url)) |get_result| switch (get_result) {
322372
.success => |success| res: {
323373
defer gpa.free(success.data);
324374
if (std.mem.eql(u8, success.data, trusted_data)) {
@@ -329,24 +379,30 @@ fn checkOneFile(
329379
break :res .content_mismatch;
330380
}
331381
},
332-
.err => |err| .{ .err = err },
382+
.fetch_error => |fe| .{ .fetch_error = fe },
333383
.bad_status => |s| .{ .bad_status = s },
334384
} else |err| switch (err) {
335385
error.OutOfMemory => return oom.store(true, .monotonic),
336386
};
337387
}
338388

339389
const GetResult = union(enum) {
340-
err: anyerror,
390+
fetch_error: FetchError,
341391
bad_status: std.http.Status,
342392
success: struct {
343393
/// Allocated into `gpa`, caller must free
344394
data: []u8,
345395
query_ns: u64,
346396
},
397+
const FetchError = struct {
398+
err: anyerror,
399+
/// `ert.instruction_addresses` is allocated into `arena`.
400+
ert: ?std.builtin.StackTrace,
401+
};
347402
};
348403
fn httpGet(
349404
gpa: Allocator,
405+
arena: Allocator,
350406
http_client: *std.http.Client,
351407
url: []const u8,
352408
) Allocator.Error!GetResult {
@@ -358,7 +414,16 @@ fn httpGet(
358414
.location = .{ .url = url },
359415
.response_storage = .{ .dynamic = &buf },
360416
.max_append_size = 512 * 1024 * 1024,
361-
}) catch |err| return .{ .err = err };
417+
}) catch |err| {
418+
const ert: ?std.builtin.StackTrace = if (@errorReturnTrace()) |ert| ert: {
419+
const new_addrs = try arena.dupe(usize, ert.instruction_addresses);
420+
break :ert .{ .index = ert.index, .instruction_addresses = new_addrs };
421+
} else null;
422+
return .{ .fetch_error = .{
423+
.err = err,
424+
.ert = ert,
425+
} };
426+
};
362427
if (res.status != .ok) {
363428
return .{ .bad_status = res.status };
364429
}
@@ -369,11 +434,12 @@ fn httpGet(
369434
}
370435
fn httpGetZsf(
371436
gpa: Allocator,
437+
arena: Allocator,
372438
http_client: *std.http.Client,
373439
url: []const u8,
374440
) Allocator.Error![]u8 {
375-
switch (try httpGet(gpa, http_client, url)) {
376-
.err => |err| std.debug.panic("fetch '{s}' failed: {s}", .{ url, @errorName(err) }),
441+
switch (try httpGet(gpa, arena, http_client, url)) {
442+
.fetch_error => |fe| std.debug.panic("fetch '{s}' failed: {s}", .{ url, @errorName(fe.err) }),
377443
.bad_status => |status| std.debug.panic("fetch '{s}' failed: bad status: {d} {s}", .{ url, @intFromEnum(status), status.phrase() orelse "?" }),
378444
.success => |res| return res.data,
379445
}
@@ -451,4 +517,5 @@ fn releaseTarballVersion(basename: []const u8) []const u8 {
451517
const std = @import("std");
452518
const Allocator = std.mem.Allocator;
453519
const assert = std.debug.assert;
520+
const builtin = @import("builtin");
454521
const ziggy = @import("ziggy");

0 commit comments

Comments
 (0)