filter: a failed blocklist download names its phase, cause, status, bytes and elapsed time
Gates / frontend (push) Successful in 2m20s
Gates / test (push) Failing after 2m41s
Gates / test-aarch64 (push) Successful in 8m19s
Gates / package (push) Successful in 4m24s
Gates / container (push) Successful in 16s
CI / gates (push) Failing after 41m33s

This commit is contained in:
2026-09-12 09:58:59 +02:00
parent bf79a22584
commit 52158198cf
7 changed files with 629 additions and 82 deletions
+6 -37
View File
@@ -226,43 +226,12 @@ const Connection = std.http.Client.Connection;
const Request = std.http.Client.Request;
const Response = std.http.Client.Response;
/// `std.Io.Writer` collapses every send failure to `error.WriteFailed` and
/// stashes the cause on the connection's socket writer. Unwrapping it is what
/// keeps `error.Canceled` and the local resource errors out of the peer fault
/// group, exactly as `concreteWrite` does for DoT.
fn sendCause(req: *const Request, err: anyerror) anyerror {
if (err != error.WriteFailed) return err;
const connection = req.connection orelse return err;
return connection.stream_writer.err orelse err;
}
/// `receiveHead` collapses a transport failure to `error.ReadFailed` and names
/// `Connection.getReadError` as the accessor for the concrete cause.
fn headCause(req: *const Request, err: anyerror) anyerror {
if (err != error.ReadFailed) return err;
const connection = req.connection orelse return err;
return readCause(connection, err);
}
/// A body read reports two different kinds of failure through the same
/// `error.ReadFailed`. An HTTP framing fault lands on the response, and only a
/// read that never reached the framing leaves the connection's cause, so the
/// response is consulted first.
fn bodyCause(resp: *const Response, err: anyerror) anyerror {
if (err != error.ReadFailed) return err;
if (resp.bodyErr()) |cause| return cause;
const connection = resp.request.connection orelse return err;
return readCause(connection, err);
}
/// `Connection.getReadError` reads the socket reader's stashed cause with `.?`.
/// On a plain connection that is its only source, so calling it with nothing
/// stashed would panic rather than return null; the guard keeps this unwrap
/// total on the path this client can reach without TLS.
fn readCause(connection: *const Connection, err: anyerror) anyerror {
if (connection.protocol == .plain and connection.stream_reader.err == null) return err;
return connection.getReadError() orelse err;
}
// The unwraps live in `transport.zig` because the blocklist fetcher needs the
// same concrete causes. They are aliased rather than qualified so the call
// sites and the tests below read as they did when they were local.
const sendCause = transport.sendCause;
const headCause = transport.headCause;
const bodyCause = transport.bodyCause;
/// RFC 8484 §6: the response media type is `application/dns-message`. The
/// header may carry parameters (`; charset=…`) and the type is case-insensitive