From 955e9ca9afb41d66d41e998803f6fcfc8fc4330f Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Mon, 27 Jul 2026 00:43:35 -0500 Subject: [PATCH] release: v0.3.22 encodeAlloc and writeAlloc write into an allocating writer, whose only failure is allocation -- but std.Io.Writer names it WriteFailed, so exhaustion reached callers looking like a problem with the data. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 4 ++++ build.zig.zon | 2 +- src/internal/repo/car.zig | 9 ++++++++- src/internal/repo/cbor.zig | 9 ++++++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 862740f..2972b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # changelog +## 0.3.22 + +- **fix**: `cbor.encodeAlloc` and `car.writeAlloc` translate the allocating writer's `WriteFailed` back into `OutOfMemory`. Both write into `std.Io.Writer.Allocating`, whose only failure mode *is* allocation, but `std.Io.Writer` reports it as `WriteFailed` — so exhaustion reached callers wearing the name of a data problem. Same class as 0.3.21, one layer down: found when stream's allocation-failure sweep walked past the CAR header fix and landed here. The remaining `Allocating` users in oauth/xrpc/streaming share the shape but not the consequence, and are untouched. + ## 0.3.21 - **fix**: CAR header parsing no longer reports allocation failure as `InvalidHeader`. Both `readWithOptions` and `streamBlocks` wrapped `cbor.decodeAll` in a bare `catch return error.InvalidHeader`, collapsing `OutOfMemory` into a structural verdict about the file. Consumers treat a malformed CAR as a permanent property of the source and retire it, so under memory pressure a perfectly good repository was discarded and its PDS blamed. Found downstream in stream, where the same class of bug in `prepareRepo` retired repositories permanently at whole-network scale; an allocation-failure sweep over `read` now pins the classification. diff --git a/build.zig.zon b/build.zig.zon index b126a08..29b3e74 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .zat, - .version = "0.3.21", + .version = "0.3.22", .fingerprint = 0x8da9db57ee82fbe4, .minimum_zig_version = "0.16.0-dev.3070+b22eb176b", .dependencies = .{ diff --git a/src/internal/repo/car.zig b/src/internal/repo/car.zig index 60d7924..c001115 100644 --- a/src/internal/repo/car.zig +++ b/src/internal/repo/car.zig @@ -386,7 +386,14 @@ pub fn write(allocator: Allocator, writer: anytype, c: Car) !void { pub fn writeAlloc(allocator: Allocator, c: Car) ![]u8 { var aw: std.Io.Writer.Allocating = .init(allocator); errdefer aw.deinit(); - try write(allocator, &aw.writer, c); + // The sink is memory, so the writer's only failure mode is allocation. + // std.Io.Writer reports that as WriteFailed, which reads to callers as a + // problem with the data rather than with us -- and callers retire a source + // permanently on a structural verdict. + write(allocator, &aw.writer, c) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + else => return err, + }; return try aw.toOwnedSlice(); } diff --git a/src/internal/repo/cbor.zig b/src/internal/repo/cbor.zig index 85730e5..c255799 100644 --- a/src/internal/repo/cbor.zig +++ b/src/internal/repo/cbor.zig @@ -565,7 +565,14 @@ pub fn encode(allocator: Allocator, writer: anytype, value: Value) !void { pub fn encodeAlloc(allocator: Allocator, value: Value) ![]u8 { var aw: std.Io.Writer.Allocating = .init(allocator); errdefer aw.deinit(); - try encode(allocator, &aw.writer, value); + // The sink is memory, so the writer's only failure mode is allocation. + // std.Io.Writer reports that as WriteFailed, which reads to callers as a + // problem with the data rather than with us -- and callers retire a source + // permanently on a structural verdict. + encode(allocator, &aw.writer, value) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + else => return err, + }; return try aw.toOwnedSlice(); } -- 2.51.2