authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-06-27 12:53:52-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:30-07:00
log55d6341eabf002017587863c92e6d5860527a72a
tree4bee65b4222a262db9f632b598fd14975fab93db
parent5b5243b5b7f5d01267afa1241c5847b3507694c1

std.io.Writer: introduce flush

into the vtable also break `drainTo` and `sendFileTo` into lower level primitives `writeSplatHeader` and `sendFileHeader` respectively. these are easier to reason about in drain implementations.

3 files changed, 181 insertions(+), 186 deletions(-)

lib/std/http.zig+21-18
......@@ -922,37 +922,41 @@ pub const BodyWriter = struct {
922922 const bw: *BodyWriter = @fieldParentPtr("writer", w);
923923 assert(!bw.isEliding());
924924 const out = bw.http_protocol_output;
925 const n = try w.drainTo(out, data, splat);
925 const n = try out.writeSplatHeader(w.buffered(), data, splat);
926926 bw.state.content_length -= n;
927 return n;
927 return w.consume(n);
928928 }
929929
930930 pub fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
931931 const bw: *BodyWriter = @fieldParentPtr("writer", w);
932932 assert(!bw.isEliding());
933933 const out = bw.http_protocol_output;
934 return try w.drainTo(out, data, splat);
934 const n = try out.writeSplatHeader(w.buffered(), data, splat);
935 return w.consume(n);
935936 }
936937
937938 /// Returns `null` if size cannot be computed without making any syscalls.
938939 pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
939940 const bw: *BodyWriter = @fieldParentPtr("writer", w);
940941 assert(!bw.isEliding());
941 return w.sendFileTo(bw.http_protocol_output, file_reader, limit);
942 const out = bw.http_protocol_output;
943 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
944 return w.consume(n);
942945 }
943946
944947 pub fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
945948 const bw: *BodyWriter = @fieldParentPtr("writer", w);
946949 assert(!bw.isEliding());
947 const n = try w.sendFileTo(bw.http_protocol_output, file_reader, limit);
950 const out = bw.http_protocol_output;
951 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
948952 bw.state.content_length -= n;
949 return n;
953 return w.consume(n);
950954 }
951955
952956 pub fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
953957 const bw: *BodyWriter = @fieldParentPtr("writer", w);
954958 assert(!bw.isEliding());
955 const data_len = if (file_reader.getSize()) |x| w.end + x else |_| {
959 const data_len = Writer.countSendFileLowerBound(w.end, file_reader, limit) orelse {
956960 // If the file size is unknown, we cannot lower to a `sendFile` since we would
957961 // have to flush the chunk header before knowing the chunk length.
958962 return error.Unimplemented;
......@@ -965,9 +969,9 @@ pub const BodyWriter = struct {
965969 const buffered_len = out.end - off - chunk_header_template.len;
966970 const chunk_len = data_len + buffered_len;
967971 writeHex(out.buffer[off..][0..chunk_len_digits], chunk_len);
968 const n = try w.sendFileTo(out, file_reader, limit);
972 const n = try out.sendFileHeader(w.buffered(), file_reader, limit);
969973 chunked.* = .{ .chunk_len = data_len + 2 - n };
970 return n;
974 return w.consume(n);
971975 },
972976 .chunk_len => |chunk_len| l: switch (chunk_len) {
973977 0 => {
......@@ -989,9 +993,9 @@ pub const BodyWriter = struct {
989993 },
990994 else => {
991995 const new_limit = limit.min(.limited(chunk_len - 2));
992 const n = try w.sendFileTo(out, file_reader, new_limit);
996 const n = try out.sendFileHeader(w.buffered(), file_reader, new_limit);
993997 chunked.chunk_len = chunk_len - n;
994 return n;
998 return w.consume(n);
995999 },
9961000 },
9971001 }
......@@ -1001,20 +1005,19 @@ pub const BodyWriter = struct {
10011005 const bw: *BodyWriter = @fieldParentPtr("writer", w);
10021006 assert(!bw.isEliding());
10031007 const out = bw.http_protocol_output;
1004 const data_len = Writer.countSplat(w.end, data, splat);
1008 const data_len = w.end + Writer.countSplat(data, splat);
10051009 const chunked = &bw.state.chunked;
10061010 state: switch (chunked.*) {
10071011 .offset => |offset| {
10081012 if (out.unusedCapacityLen() >= data_len) {
1009 assert(data_len == (w.drainTo(out, data, splat) catch unreachable));
1010 return data_len;
1013 return w.consume(out.writeSplatHeader(w.buffered(), data, splat) catch unreachable);
10111014 }
10121015 const buffered_len = out.end - offset - chunk_header_template.len;
10131016 const chunk_len = data_len + buffered_len;
10141017 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);
1015 const n = try w.drainTo(out, data, splat);
1018 const n = try out.writeSplatHeader(w.buffered(), data, splat);
10161019 chunked.* = .{ .chunk_len = data_len + 2 - n };
1017 return n;
1020 return w.consume(n);
10181021 },
10191022 .chunk_len => |chunk_len| l: switch (chunk_len) {
10201023 0 => {
......@@ -1035,9 +1038,9 @@ pub const BodyWriter = struct {
10351038 continue :l 1;
10361039 },
10371040 else => {
1038 const n = try w.drainToLimit(out, data, splat, .limited(chunk_len - 2));
1041 const n = try out.writeSplatHeaderLimit(w.buffered(), data, splat, .limited(chunk_len - 2));
10391042 chunked.chunk_len = chunk_len - n;
1040 return n;
1043 return w.consume(n);
10411044 },
10421045 },
10431046 }
lib/std/io/Writer.zig+94-119
......@@ -23,29 +23,26 @@ count: usize = 0,
2323
2424pub const VTable = struct {
2525 /// Sends bytes to the logical sink. A write will only be sent here if it
26 /// could not fit into `buffer`.
26 /// could not fit into `buffer`, or during a `flush` operation.
2727 ///
2828 /// `buffer[0..end]` is consumed first, followed by each slice of `data` in
2929 /// order. Elements of `data` may alias each other but may not alias
3030 /// `buffer`.
3131 ///
32 /// This function modifies `Writer.end` and `Writer.buffer`.
32 /// This function modifies `Writer.end` and `Writer.buffer` in an
33 /// implementation-defined manner.
3334 ///
34 /// If `data.len` is zero, it indicates this is a "flush" operation; all
35 /// remaining buffered data must be logically consumed. Generally, this
36 /// means that `end` will be set to zero before returning, however, it is
37 /// legal for implementations to manage that data differently. There may be
38 /// subsequent calls to `drain` and `sendFile` after a flush operation.
35 /// `data.len` must be nonzero.
3936 ///
40 /// The last element of `data` is special. It is repeated as necessary so
41 /// that it is written `splat` number of times, which may be zero.
37 /// The last element of `data` is repeated as necessary so that it is
38 /// written `splat` number of times, which may be zero.
4239 ///
43 /// Number of bytes actually written is returned, excluding bytes from
44 /// `buffer`. Bytes from `buffer` are tracked by modifying `end`.
40 /// Number of bytes consumed from `data` is returned, excluding bytes from
41 /// `buffer`.
4542 ///
46 /// Number of bytes returned may be zero, which does not mean
47 /// end-of-stream. A subsequent call may return nonzero, or signal end of
48 /// stream via `error.WriteFailed`.
43 /// Number of bytes returned may be zero, which does not indicate stream
44 /// end. A subsequent call may return nonzero, or signal end of stream via
45 /// `error.WriteFailed`.
4946 drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize,
5047
5148 /// Copies contents from an open file to the logical sink. `buffer[0..end]`
......@@ -55,9 +52,9 @@ pub const VTable = struct {
5552 /// `buffer` because they have already been logically written. Number of
5653 /// bytes consumed from `buffer` are tracked by modifying `end`.
5754 ///
58 /// Number of bytes returned may be zero, which does not necessarily mean
59 /// end-of-stream. A subsequent call may return nonzero, or signal end of
60 /// stream via `error.WriteFailed`. Caller must check `file_reader` state
55 /// Number of bytes returned may be zero, which does not indicate stream
56 /// end. A subsequent call may return nonzero, or signal end of stream via
57 /// `error.WriteFailed`. Caller may check `file_reader` state
6158 /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or
6259 /// write, and whether the file reached the end.
6360 ///
......@@ -71,6 +68,16 @@ pub const VTable = struct {
7168 /// `buffer` does not count towards this limit.
7269 limit: Limit,
7370 ) FileError!usize = unimplementedSendFile,
71
72 /// Consumes all remaining buffer.
73 ///
74 /// The default flush implementation calls drain repeatedly until `end` is
75 /// zero, however it is legal for implementations to manage `end`
76 /// differently. For instance, `Allocating` flush is a no-op.
77 ///
78 /// There may be subsequent calls to `drain` and `sendFile` after a `flush`
79 /// operation.
80 flush: *const fn (w: *Writer) Error!void = defaultFlush,
7481};
7582
7683pub const Error = error{
......@@ -141,16 +148,15 @@ pub fn buffered(w: *const Writer) []u8 {
141148 return w.buffer[0..w.end];
142149}
143150
144pub fn countSplat(n: usize, data: []const []const u8, splat: usize) usize {
145 assert(data.len > 0);
146 var total: usize = n;
151pub fn countSplat(data: []const []const u8, splat: usize) usize {
152 var total: usize = 0;
147153 for (data[0 .. data.len - 1]) |buf| total += buf.len;
148154 total += data[data.len - 1].len * splat;
149155 return total;
150156}
151157
152pub fn countSendFileUpperBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {
153 const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() orelse return null);
158pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {
159 const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() catch return null);
154160 return std.math.lossyCast(usize, total + n);
155161}
156162
......@@ -167,7 +173,7 @@ pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize {
167173pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
168174 assert(data.len > 0);
169175 const buffer = w.buffer;
170 const count = countSplat(0, data, splat);
176 const count = countSplat(data, splat);
171177 if (w.end + count > buffer.len) {
172178 const n = try w.vtable.drain(w, data, splat);
173179 w.count += n;
......@@ -215,51 +221,19 @@ pub fn writeSplatLimit(
215221 @panic("TODO");
216222}
217223
218/// Drains all remaining buffered data.
219///
220/// It is legal for `VTable.drain` implementations to refrain from modifying
221/// `end`.
222pub fn flush(w: *Writer) Error!void {
223 assert(0 == try w.vtable.drain(w, &.{}, 0));
224 if (w.end != 0) assert(w.vtable.drain == &fixedDrain);
225}
226
227/// Calls `VTable.drain` but hides the last `preserve_length` bytes from the
228/// implementation, keeping them buffered.
229pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void {
230 const temp_end = w.end -| preserve_length;
231 const preserved = w.buffer[temp_end..w.end];
232 w.end = temp_end;
233 defer w.end += preserved.len;
234 assert(0 == try w.vtable.drain(w, &.{""}, 1));
235 assert(w.end <= temp_end + preserved.len);
236 @memmove(w.buffer[w.end..][0..preserved.len], preserved);
237}
238
239/// Forwards a `drain` to a second `Writer` instance. `w` is only used for its
240/// buffer, but it has its `end` and `count` adjusted accordingly depending on
241/// how much was consumed.
242///
243/// Returns how many bytes from `data` were consumed.
244pub fn drainTo(noalias w: *Writer, noalias other: *Writer, data: []const []const u8, splat: usize) Error!usize {
245 assert(w != other);
246 const header = w.buffered();
247 const new_end = other.end + header.len;
248 if (new_end <= other.buffer.len) {
249 @memcpy(other.buffer[other.end..][0..header.len], header);
250 other.end = new_end;
251 other.count += header.len;
252 w.end = 0;
253 const n = try other.vtable.drain(other, data, splat);
254 other.count += n;
255 return n;
256 }
257 if (other.vtable == &VectorWrapper.vtable) {
258 const wrapper: *VectorWrapper = @fieldParentPtr("writer", w);
259 while (wrapper.it.next()) |dest| {
260 _ = dest;
261 @panic("TODO");
262 }
224/// Returns how many bytes were consumed from `header` and `data`.
225pub fn writeSplatHeader(
226 w: *Writer,
227 header: []const u8,
228 data: []const []const u8,
229 splat: usize,
230) Error!usize {
231 const new_end = w.end + header.len;
232 if (new_end <= w.buffer.len) {
233 @memcpy(w.buffer[w.end..][0..header.len], header);
234 w.end = new_end;
235 w.count += header.len;
236 return header.len + try writeSplat(w, data, splat);
263237 }
264238 var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.
265239 var i: usize = 1;
......@@ -271,32 +245,55 @@ pub fn drainTo(noalias w: *Writer, noalias other: *Writer, data: []const []const
271245 if (vecs.len - i == 0) break;
272246 }
273247 const new_splat = if (vecs[i - 1].ptr == data[data.len - 1].ptr) splat else 1;
274 const n = try other.vtable.drain(other, vecs[0..i], new_splat);
275 other.count += n;
276 if (n < header.len) {
277 const remaining = w.buffer[n..w.end];
278 @memmove(w.buffer[0..remaining.len], remaining);
279 w.end = remaining.len;
280 return 0;
281 }
282 defer w.end = 0;
283 return n - header.len;
248 const n = try w.vtable.drain(w, vecs[0..i], new_splat);
249 w.count += n;
250 return n;
284251}
285252
286pub fn drainToLimit(
287 noalias w: *Writer,
288 noalias other: *Writer,
253/// Equivalent to `writeSplatHeader` but writes at most `limit` bytes.
254pub fn writeSplatHeaderLimit(
255 w: *Writer,
256 header: []const u8,
289257 data: []const []const u8,
290258 splat: usize,
291259 limit: Limit,
292260) Error!usize {
293 assert(w != other);
261 _ = w;
262 _ = header;
294263 _ = data;
295264 _ = splat;
296265 _ = limit;
297266 @panic("TODO");
298267}
299268
269/// Drains all remaining buffered data.
270pub fn flush(w: *Writer) Error!void {
271 return w.vtable.flush(w);
272}
273
274/// Repeatedly calls `VTable.drain` until `end` is zero.
275pub fn defaultFlush(w: *Writer) Error!void {
276 const drainFn = w.vtable.drain;
277 while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
278}
279
280/// Does nothing.
281pub fn noopFlush(w: *Writer) Error!void {
282 _ = w;
283}
284
285/// Calls `VTable.drain` but hides the last `preserve_length` bytes from the
286/// implementation, keeping them buffered.
287pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void {
288 const temp_end = w.end -| preserve_length;
289 const preserved = w.buffer[temp_end..w.end];
290 w.end = temp_end;
291 defer w.end += preserved.len;
292 assert(0 == try w.vtable.drain(w, &.{""}, 1));
293 assert(w.end <= temp_end + preserved.len);
294 @memmove(w.buffer[w.end..][0..preserved.len], preserved);
295}
296
300297pub fn unusedCapacitySlice(w: *const Writer) []u8 {
301298 return w.buffer[w.end..];
302299}
......@@ -672,47 +669,25 @@ pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!u
672669 return w.vtable.sendFile(w, file_reader, limit);
673670}
674671
675/// Forwards a `sendFile` to a second `Writer` instance. `w` is only used for
676/// its buffer, but it has its `end` and `count` adjusted accordingly depending
677/// on how much was consumed.
678///
679/// Returns how many bytes from `file_reader` were consumed.
680pub fn sendFileTo(
681 noalias w: *Writer,
682 noalias other: *Writer,
672/// Returns how many bytes from `header` and `file_reader` were consumed.
673pub fn sendFileHeader(
674 w: *Writer,
675 header: []const u8,
683676 file_reader: *File.Reader,
684677 limit: Limit,
685678) FileError!usize {
686 assert(w != other);
687 const header = w.buffered();
688 const new_end = other.end + header.len;
689 if (new_end <= other.buffer.len) {
690 @memcpy(other.buffer[other.end..][0..header.len], header);
691 other.end = new_end;
692 other.count += header.len;
693 w.end = 0;
694 return other.vtable.sendFile(other, file_reader, limit);
679 const new_end = w.end + header.len;
680 if (new_end <= w.buffer.len) {
681 @memcpy(w.buffer[w.end..][0..header.len], header);
682 w.end = new_end;
683 w.count += header.len;
684 return header.len + try w.vtable.sendFile(w, file_reader, limit);
695685 }
696 assert(header.len > 0);
697 var vec_buf: [2][]const u8 = .{ header, undefined };
698 var vec_i: usize = 1;
699686 const buffered_contents = limit.slice(file_reader.interface.buffered());
700 if (buffered_contents.len > 0) {
701 vec_buf[vec_i] = buffered_contents;
702 vec_i += 1;
703 }
704 const n = try other.vtable.drain(other, vec_buf[0..vec_i], 1);
705 other.count += n;
706 if (n < header.len) {
707 const remaining = w.buffer[n..w.end];
708 @memmove(w.buffer[0..remaining.len], remaining);
709 w.end = remaining.len;
710 return 0;
711 }
712 w.end = 0;
713 const tossed = n - header.len;
714 file_reader.interface.toss(tossed);
715 return tossed;
687 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
688 w.count += n;
689 file_reader.interface.toss(n - header.len);
690 return n;
716691}
717692
718693/// Asserts nonzero buffer capacity.
......@@ -2126,6 +2101,7 @@ pub const Allocating = struct {
21262101 const vtable: VTable = .{
21272102 .drain = Allocating.drain,
21282103 .sendFile = Allocating.sendFile,
2104 .flush = noopFlush,
21292105 };
21302106
21312107 pub fn deinit(a: *Allocating) void {
......@@ -2172,7 +2148,6 @@ pub const Allocating = struct {
21722148 }
21732149
21742150 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2175 if (data.len == 0) return 0; // flush
21762151 const a: *Allocating = @fieldParentPtr("interface", w);
21772152 const gpa = a.allocator;
21782153 const pattern = data[data.len - 1];
lib/std/net.zig+66-49
......@@ -1999,44 +1999,56 @@ pub const Stream = struct {
19991999 const w: *Writer = @fieldParentPtr("interface", io_w);
20002000 const buffered = io_w.buffered();
20012001 comptime assert(native_os == .windows);
2002 var splat_buffer: [splat_buffer_len]u8 = undefined;
20032002 var iovecs: [max_buffers_len]windows.WSABUF = undefined;
20042003 var len: u32 = 0;
20052004 if (buffered.len != 0) {
20062005 iovecs[len] = .{
2007 .base = buffered.ptr,
2006 .buf = buffered.ptr,
20082007 .len = buffered.len,
20092008 };
20102009 len += 1;
20112010 }
2012 for (data[0..data.len]) |bytes| {
2011 for (data) |bytes| {
20132012 if (bytes.len == 0) continue;
20142013 iovecs[len] = .{
20152014 .buf = bytes.ptr,
20162015 .len = bytes.len,
20172016 };
20182017 len += 1;
2018 if (iovecs.len - len == 0) break;
20192019 }
2020 if (len == 0) return 0;
20202021 const pattern = data[data.len - 1];
20212022 switch (splat) {
2022 0 => len -= 1,
2023 0 => if (iovecs[len - 1].buf == data[data.len - 1].ptr) {
2024 len -= 1;
2025 },
20232026 1 => {},
20242027 else => switch (pattern.len) {
20252028 0 => {},
2026 1 => {
2029 1 => memset: {
20272030 // Replace the 1-byte buffer with a bigger one.
2031 if (iovecs[len - 1].buf == data[data.len - 1].ptr) len -= 1;
2032 if (iovecs.len - len == 0) break :memset;
2033 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2034 var backup_buffer: [32]u8 = undefined;
2035 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2036 splat_buffer_candidate
2037 else
2038 &backup_buffer;
20282039 const memset_len = @min(splat_buffer.len, splat);
20292040 const buf = splat_buffer[0..memset_len];
20302041 @memset(buf, pattern[0]);
2031 iovecs[len - 1] = .{ .buf = buf.ptr, .len = buf.len };
2042 iovecs[len] = .{ .buf = buf.ptr, .len = buf.len };
2043 len += 1;
20322044 var remaining_splat = splat - buf.len;
20332045 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2034 iovecs[len] = .{ .buf = &splat_buffer, .len = splat_buffer.len };
2046 iovecs[len] = .{ .buf = splat_buffer.ptr, .len = splat_buffer.len };
20352047 remaining_splat -= splat_buffer.len;
20362048 len += 1;
20372049 }
2038 if (remaining_splat > 0 and len < iovecs.len) {
2039 iovecs[len] = .{ .buf = &splat_buffer, .len = remaining_splat };
2050 if (remaining_splat > 0 and iovecs.len - len != 0) {
2051 iovecs[len] = .{ .buf = splat_buffer.ptr, .len = remaining_splat };
20402052 len += 1;
20412053 }
20422054 },
......@@ -2134,48 +2146,52 @@ pub const Stream = struct {
21342146 .flags = 0,
21352147 };
21362148 };
2137 if (data.len != 0) {
2138 const pattern = data[data.len - 1];
2139 switch (splat) {
2140 0 => if (msg.iovlen != 0 and iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr) {
2141 msg.iovlen -= 1;
2142 },
2143 1 => {},
2144 else => switch (pattern.len) {
2145 0 => {},
2146 1 => memset: {
2147 // Replace the 1-byte buffer with a bigger one.
2148 if (msg.iovlen != 0 and iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr)
2149 msg.iovlen -= 1;
2150 if (iovecs.len - msg.iovlen == 0) break :memset;
2151 const splat_buffer = io_w.buffer[io_w.end..];
2152 const memset_len = @min(splat_buffer.len, splat);
2153 const buf = splat_buffer[0..memset_len];
2154 @memset(buf, pattern[0]);
2155 iovecs[msg.iovlen] = .{ .base = buf.ptr, .len = buf.len };
2149 if (msg.iovlen == 0) return 0;
2150 const pattern = data[data.len - 1];
2151 switch (splat) {
2152 0 => if (iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr) {
2153 msg.iovlen -= 1;
2154 },
2155 1 => {},
2156 else => switch (pattern.len) {
2157 0 => {},
2158 1 => memset: {
2159 // Replace the 1-byte buffer with a bigger one.
2160 if (iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr) msg.iovlen -= 1;
2161 if (iovecs.len - msg.iovlen == 0) break :memset;
2162 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2163 var backup_buffer: [32]u8 = undefined;
2164 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2165 splat_buffer_candidate
2166 else
2167 &backup_buffer;
2168 if (splat_buffer.len == 0) break :memset;
2169 const memset_len = @min(splat_buffer.len, splat);
2170 const buf = splat_buffer[0..memset_len];
2171 @memset(buf, pattern[0]);
2172 iovecs[msg.iovlen] = .{ .base = buf.ptr, .len = buf.len };
2173 msg.iovlen += 1;
2174 var remaining_splat = splat - buf.len;
2175 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
2176 assert(buf.len == splat_buffer.len);
2177 iovecs[msg.iovlen] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len };
21562178 msg.iovlen += 1;
2157 var remaining_splat = splat - buf.len;
2158 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
2159 assert(buf.len == splat_buffer.len);
2160 iovecs[msg.iovlen] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len };
2161 msg.iovlen += 1;
2162 remaining_splat -= splat_buffer.len;
2163 }
2164 if (remaining_splat > 0 and iovecs.len - msg.iovlen != 0) {
2165 iovecs[msg.iovlen] = .{ .base = splat_buffer.ptr, .len = remaining_splat };
2166 msg.iovlen += 1;
2167 }
2168 },
2169 else => for (0..splat - 1) |_| {
2170 if (iovecs.len - msg.iovlen == 0) break;
2171 iovecs[msg.iovlen] = .{
2172 .base = pattern.ptr,
2173 .len = pattern.len,
2174 };
2179 remaining_splat -= splat_buffer.len;
2180 }
2181 if (remaining_splat > 0 and iovecs.len - msg.iovlen != 0) {
2182 iovecs[msg.iovlen] = .{ .base = splat_buffer.ptr, .len = remaining_splat };
21752183 msg.iovlen += 1;
2176 },
2184 }
21772185 },
2178 }
2186 else => for (0..splat - 1) |_| {
2187 if (iovecs.len - msg.iovlen == 0) break;
2188 iovecs[msg.iovlen] = .{
2189 .base = pattern.ptr,
2190 .len = pattern.len,
2191 };
2192 msg.iovlen += 1;
2193 },
2194 },
21792195 }
21802196 const flags = posix.MSG.NOSIGNAL;
21812197 return io_w.consume(std.posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
......@@ -2186,7 +2202,8 @@ pub const Stream = struct {
21862202
21872203 fn sendFile(io_w: *io.Writer, file_reader: *File.Reader, limit: io.Limit) io.Writer.FileError!usize {
21882204 const w: *Writer = @fieldParentPtr("interface", io_w);
2189 return io_w.sendFileTo(&w.file_writer.interface, file_reader, limit);
2205 const n = try w.file_writer.interface.sendFileHeader(io_w.buffered(), file_reader, limit);
2206 return io_w.consume(n);
21902207 }
21912208 },
21922209 };