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 {...@@ -922,37 +922,41 @@ pub const BodyWriter = struct {
922 const bw: *BodyWriter = @fieldParentPtr("writer", w);922 const bw: *BodyWriter = @fieldParentPtr("writer", w);
923 assert(!bw.isEliding());923 assert(!bw.isEliding());
924 const out = bw.http_protocol_output;924 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);
926 bw.state.content_length -= n;926 bw.state.content_length -= n;
927 return n;927 return w.consume(n);
928 }928 }
929929
930 pub fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {930 pub fn noneDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
931 const bw: *BodyWriter = @fieldParentPtr("writer", w);931 const bw: *BodyWriter = @fieldParentPtr("writer", w);
932 assert(!bw.isEliding());932 assert(!bw.isEliding());
933 const out = bw.http_protocol_output;933 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);
935 }936 }
936937
937 /// Returns `null` if size cannot be computed without making any syscalls.938 /// Returns `null` if size cannot be computed without making any syscalls.
938 pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {939 pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
939 const bw: *BodyWriter = @fieldParentPtr("writer", w);940 const bw: *BodyWriter = @fieldParentPtr("writer", w);
940 assert(!bw.isEliding());941 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);
942 }945 }
943946
944 pub fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {947 pub fn contentLengthSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
945 const bw: *BodyWriter = @fieldParentPtr("writer", w);948 const bw: *BodyWriter = @fieldParentPtr("writer", w);
946 assert(!bw.isEliding());949 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);
948 bw.state.content_length -= n;952 bw.state.content_length -= n;
949 return n;953 return w.consume(n);
950 }954 }
951955
952 pub fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {956 pub fn chunkedSendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) Writer.FileError!usize {
953 const bw: *BodyWriter = @fieldParentPtr("writer", w);957 const bw: *BodyWriter = @fieldParentPtr("writer", w);
954 assert(!bw.isEliding());958 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 {
956 // If the file size is unknown, we cannot lower to a `sendFile` since we would960 // If the file size is unknown, we cannot lower to a `sendFile` since we would
957 // have to flush the chunk header before knowing the chunk length.961 // have to flush the chunk header before knowing the chunk length.
958 return error.Unimplemented;962 return error.Unimplemented;
...@@ -965,9 +969,9 @@ pub const BodyWriter = struct {...@@ -965,9 +969,9 @@ pub const BodyWriter = struct {
965 const buffered_len = out.end - off - chunk_header_template.len;969 const buffered_len = out.end - off - chunk_header_template.len;
966 const chunk_len = data_len + buffered_len;970 const chunk_len = data_len + buffered_len;
967 writeHex(out.buffer[off..][0..chunk_len_digits], chunk_len);971 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);
969 chunked.* = .{ .chunk_len = data_len + 2 - n };973 chunked.* = .{ .chunk_len = data_len + 2 - n };
970 return n;974 return w.consume(n);
971 },975 },
972 .chunk_len => |chunk_len| l: switch (chunk_len) {976 .chunk_len => |chunk_len| l: switch (chunk_len) {
973 0 => {977 0 => {
...@@ -989,9 +993,9 @@ pub const BodyWriter = struct {...@@ -989,9 +993,9 @@ pub const BodyWriter = struct {
989 },993 },
990 else => {994 else => {
991 const new_limit = limit.min(.limited(chunk_len - 2));995 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);
993 chunked.chunk_len = chunk_len - n;997 chunked.chunk_len = chunk_len - n;
994 return n;998 return w.consume(n);
995 },999 },
996 },1000 },
997 }1001 }
...@@ -1001,20 +1005,19 @@ pub const BodyWriter = struct {...@@ -1001,20 +1005,19 @@ pub const BodyWriter = struct {
1001 const bw: *BodyWriter = @fieldParentPtr("writer", w);1005 const bw: *BodyWriter = @fieldParentPtr("writer", w);
1002 assert(!bw.isEliding());1006 assert(!bw.isEliding());
1003 const out = bw.http_protocol_output;1007 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);
1005 const chunked = &bw.state.chunked;1009 const chunked = &bw.state.chunked;
1006 state: switch (chunked.*) {1010 state: switch (chunked.*) {
1007 .offset => |offset| {1011 .offset => |offset| {
1008 if (out.unusedCapacityLen() >= data_len) {1012 if (out.unusedCapacityLen() >= data_len) {
1009 assert(data_len == (w.drainTo(out, data, splat) catch unreachable));1013 return w.consume(out.writeSplatHeader(w.buffered(), data, splat) catch unreachable);
1010 return data_len;
1011 }1014 }
1012 const buffered_len = out.end - offset - chunk_header_template.len;1015 const buffered_len = out.end - offset - chunk_header_template.len;
1013 const chunk_len = data_len + buffered_len;1016 const chunk_len = data_len + buffered_len;
1014 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);1017 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);
1016 chunked.* = .{ .chunk_len = data_len + 2 - n };1019 chunked.* = .{ .chunk_len = data_len + 2 - n };
1017 return n;1020 return w.consume(n);
1018 },1021 },
1019 .chunk_len => |chunk_len| l: switch (chunk_len) {1022 .chunk_len => |chunk_len| l: switch (chunk_len) {
1020 0 => {1023 0 => {
...@@ -1035,9 +1038,9 @@ pub const BodyWriter = struct {...@@ -1035,9 +1038,9 @@ pub const BodyWriter = struct {
1035 continue :l 1;1038 continue :l 1;
1036 },1039 },
1037 else => {1040 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));
1039 chunked.chunk_len = chunk_len - n;1042 chunked.chunk_len = chunk_len - n;
1040 return n;1043 return w.consume(n);
1041 },1044 },
1042 },1045 },
1043 }1046 }
lib/std/io/Writer.zig+94-119
...@@ -23,29 +23,26 @@ count: usize = 0,...@@ -23,29 +23,26 @@ count: usize = 0,
2323
24pub const VTable = struct {24pub const VTable = struct {
25 /// Sends bytes to the logical sink. A write will only be sent here if it25 /// 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.
27 ///27 ///
28 /// `buffer[0..end]` is consumed first, followed by each slice of `data` in28 /// `buffer[0..end]` is consumed first, followed by each slice of `data` in
29 /// order. Elements of `data` may alias each other but may not alias29 /// order. Elements of `data` may alias each other but may not alias
30 /// `buffer`.30 /// `buffer`.
31 ///31 ///
32 /// This function modifies `Writer.end` and `Writer.buffer`.32 /// This function modifies `Writer.end` and `Writer.buffer` in an
33 /// implementation-defined manner.
33 ///34 ///
34 /// If `data.len` is zero, it indicates this is a "flush" operation; all35 /// `data.len` must be nonzero.
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.
39 ///36 ///
40 /// The last element of `data` is special. It is repeated as necessary so37 /// The last element of `data` is repeated as necessary so that it is
41 /// that it is written `splat` number of times, which may be zero.38 /// written `splat` number of times, which may be zero.
42 ///39 ///
43 /// Number of bytes actually written is returned, excluding bytes from40 /// Number of bytes consumed from `data` is returned, excluding bytes from
44 /// `buffer`. Bytes from `buffer` are tracked by modifying `end`.41 /// `buffer`.
45 ///42 ///
46 /// Number of bytes returned may be zero, which does not mean43 /// Number of bytes returned may be zero, which does not indicate stream
47 /// end-of-stream. A subsequent call may return nonzero, or signal end of44 /// end. A subsequent call may return nonzero, or signal end of stream via
48 /// stream via `error.WriteFailed`.45 /// `error.WriteFailed`.
49 drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize,46 drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize,
5047
51 /// Copies contents from an open file to the logical sink. `buffer[0..end]`48 /// Copies contents from an open file to the logical sink. `buffer[0..end]`
...@@ -55,9 +52,9 @@ pub const VTable = struct {...@@ -55,9 +52,9 @@ pub const VTable = struct {
55 /// `buffer` because they have already been logically written. Number of52 /// `buffer` because they have already been logically written. Number of
56 /// bytes consumed from `buffer` are tracked by modifying `end`.53 /// bytes consumed from `buffer` are tracked by modifying `end`.
57 ///54 ///
58 /// Number of bytes returned may be zero, which does not necessarily mean55 /// Number of bytes returned may be zero, which does not indicate stream
59 /// end-of-stream. A subsequent call may return nonzero, or signal end of56 /// end. A subsequent call may return nonzero, or signal end of stream via
60 /// stream via `error.WriteFailed`. Caller must check `file_reader` state57 /// `error.WriteFailed`. Caller may check `file_reader` state
61 /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or58 /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or
62 /// write, and whether the file reached the end.59 /// write, and whether the file reached the end.
63 ///60 ///
...@@ -71,6 +68,16 @@ pub const VTable = struct {...@@ -71,6 +68,16 @@ pub const VTable = struct {
71 /// `buffer` does not count towards this limit.68 /// `buffer` does not count towards this limit.
72 limit: Limit,69 limit: Limit,
73 ) FileError!usize = unimplementedSendFile,70 ) 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,
74};81};
7582
76pub const Error = error{83pub const Error = error{
...@@ -141,16 +148,15 @@ pub fn buffered(w: *const Writer) []u8 {...@@ -141,16 +148,15 @@ pub fn buffered(w: *const Writer) []u8 {
141 return w.buffer[0..w.end];148 return w.buffer[0..w.end];
142}149}
143150
144pub fn countSplat(n: usize, data: []const []const u8, splat: usize) usize {151pub fn countSplat(data: []const []const u8, splat: usize) usize {
145 assert(data.len > 0);152 var total: usize = 0;
146 var total: usize = n;
147 for (data[0 .. data.len - 1]) |buf| total += buf.len;153 for (data[0 .. data.len - 1]) |buf| total += buf.len;
148 total += data[data.len - 1].len * splat;154 total += data[data.len - 1].len * splat;
149 return total;155 return total;
150}156}
151157
152pub fn countSendFileUpperBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {158pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {
153 const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() orelse return null);159 const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() catch return null);
154 return std.math.lossyCast(usize, total + n);160 return std.math.lossyCast(usize, total + n);
155}161}
156162
...@@ -167,7 +173,7 @@ pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize {...@@ -167,7 +173,7 @@ pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize {
167pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize {173pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
168 assert(data.len > 0);174 assert(data.len > 0);
169 const buffer = w.buffer;175 const buffer = w.buffer;
170 const count = countSplat(0, data, splat);176 const count = countSplat(data, splat);
171 if (w.end + count > buffer.len) {177 if (w.end + count > buffer.len) {
172 const n = try w.vtable.drain(w, data, splat);178 const n = try w.vtable.drain(w, data, splat);
173 w.count += n;179 w.count += n;
...@@ -215,51 +221,19 @@ pub fn writeSplatLimit(...@@ -215,51 +221,19 @@ pub fn writeSplatLimit(
215 @panic("TODO");221 @panic("TODO");
216}222}
217223
218/// Drains all remaining buffered data.224/// Returns how many bytes were consumed from `header` and `data`.
219///225pub fn writeSplatHeader(
220/// It is legal for `VTable.drain` implementations to refrain from modifying226 w: *Writer,
221/// `end`.227 header: []const u8,
222pub fn flush(w: *Writer) Error!void {228 data: []const []const u8,
223 assert(0 == try w.vtable.drain(w, &.{}, 0));229 splat: usize,
224 if (w.end != 0) assert(w.vtable.drain == &fixedDrain);230) Error!usize {
225}231 const new_end = w.end + header.len;
226232 if (new_end <= w.buffer.len) {
227/// Calls `VTable.drain` but hides the last `preserve_length` bytes from the233 @memcpy(w.buffer[w.end..][0..header.len], header);
228/// implementation, keeping them buffered.234 w.end = new_end;
229pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void {235 w.count += header.len;
230 const temp_end = w.end -| preserve_length;236 return header.len + try writeSplat(w, data, splat);
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 }
263 }237 }
264 var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.238 var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.
265 var i: usize = 1;239 var i: usize = 1;
...@@ -271,32 +245,55 @@ pub fn drainTo(noalias w: *Writer, noalias other: *Writer, data: []const []const...@@ -271,32 +245,55 @@ pub fn drainTo(noalias w: *Writer, noalias other: *Writer, data: []const []const
271 if (vecs.len - i == 0) break;245 if (vecs.len - i == 0) break;
272 }246 }
273 const new_splat = if (vecs[i - 1].ptr == data[data.len - 1].ptr) splat else 1;247 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);248 const n = try w.vtable.drain(w, vecs[0..i], new_splat);
275 other.count += n;249 w.count += n;
276 if (n < header.len) {250 return n;
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;
284}251}
285252
286pub fn drainToLimit(253/// Equivalent to `writeSplatHeader` but writes at most `limit` bytes.
287 noalias w: *Writer,254pub fn writeSplatHeaderLimit(
288 noalias other: *Writer,255 w: *Writer,
256 header: []const u8,
289 data: []const []const u8,257 data: []const []const u8,
290 splat: usize,258 splat: usize,
291 limit: Limit,259 limit: Limit,
292) Error!usize {260) Error!usize {
293 assert(w != other);261 _ = w;
262 _ = header;
294 _ = data;263 _ = data;
295 _ = splat;264 _ = splat;
296 _ = limit;265 _ = limit;
297 @panic("TODO");266 @panic("TODO");
298}267}
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
300pub fn unusedCapacitySlice(w: *const Writer) []u8 {297pub fn unusedCapacitySlice(w: *const Writer) []u8 {
301 return w.buffer[w.end..];298 return w.buffer[w.end..];
302}299}
...@@ -672,47 +669,25 @@ pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!u...@@ -672,47 +669,25 @@ pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!u
672 return w.vtable.sendFile(w, file_reader, limit);669 return w.vtable.sendFile(w, file_reader, limit);
673}670}
674671
675/// Forwards a `sendFile` to a second `Writer` instance. `w` is only used for672/// Returns how many bytes from `header` and `file_reader` were consumed.
676/// its buffer, but it has its `end` and `count` adjusted accordingly depending673pub fn sendFileHeader(
677/// on how much was consumed.674 w: *Writer,
678///675 header: []const u8,
679/// Returns how many bytes from `file_reader` were consumed.
680pub fn sendFileTo(
681 noalias w: *Writer,
682 noalias other: *Writer,
683 file_reader: *File.Reader,676 file_reader: *File.Reader,
684 limit: Limit,677 limit: Limit,
685) FileError!usize {678) FileError!usize {
686 assert(w != other);679 const new_end = w.end + header.len;
687 const header = w.buffered();680 if (new_end <= w.buffer.len) {
688 const new_end = other.end + header.len;681 @memcpy(w.buffer[w.end..][0..header.len], header);
689 if (new_end <= other.buffer.len) {682 w.end = new_end;
690 @memcpy(other.buffer[other.end..][0..header.len], header);683 w.count += header.len;
691 other.end = new_end;684 return header.len + try w.vtable.sendFile(w, file_reader, limit);
692 other.count += header.len;
693 w.end = 0;
694 return other.vtable.sendFile(other, file_reader, limit);
695 }685 }
696 assert(header.len > 0);
697 var vec_buf: [2][]const u8 = .{ header, undefined };
698 var vec_i: usize = 1;
699 const buffered_contents = limit.slice(file_reader.interface.buffered());686 const buffered_contents = limit.slice(file_reader.interface.buffered());
700 if (buffered_contents.len > 0) {687 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
701 vec_buf[vec_i] = buffered_contents;688 w.count += n;
702 vec_i += 1;689 file_reader.interface.toss(n - header.len);
703 }690 return n;
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;
716}691}
717692
718/// Asserts nonzero buffer capacity.693/// Asserts nonzero buffer capacity.
...@@ -2126,6 +2101,7 @@ pub const Allocating = struct {...@@ -2126,6 +2101,7 @@ pub const Allocating = struct {
2126 const vtable: VTable = .{2101 const vtable: VTable = .{
2127 .drain = Allocating.drain,2102 .drain = Allocating.drain,
2128 .sendFile = Allocating.sendFile,2103 .sendFile = Allocating.sendFile,
2104 .flush = noopFlush,
2129 };2105 };
21302106
2131 pub fn deinit(a: *Allocating) void {2107 pub fn deinit(a: *Allocating) void {
...@@ -2172,7 +2148,6 @@ pub const Allocating = struct {...@@ -2172,7 +2148,6 @@ pub const Allocating = struct {
2172 }2148 }
21732149
2174 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {2150 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2175 if (data.len == 0) return 0; // flush
2176 const a: *Allocating = @fieldParentPtr("interface", w);2151 const a: *Allocating = @fieldParentPtr("interface", w);
2177 const gpa = a.allocator;2152 const gpa = a.allocator;
2178 const pattern = data[data.len - 1];2153 const pattern = data[data.len - 1];
lib/std/net.zig+66-49
...@@ -1999,44 +1999,56 @@ pub const Stream = struct {...@@ -1999,44 +1999,56 @@ pub const Stream = struct {
1999 const w: *Writer = @fieldParentPtr("interface", io_w);1999 const w: *Writer = @fieldParentPtr("interface", io_w);
2000 const buffered = io_w.buffered();2000 const buffered = io_w.buffered();
2001 comptime assert(native_os == .windows);2001 comptime assert(native_os == .windows);
2002 var splat_buffer: [splat_buffer_len]u8 = undefined;
2003 var iovecs: [max_buffers_len]windows.WSABUF = undefined;2002 var iovecs: [max_buffers_len]windows.WSABUF = undefined;
2004 var len: u32 = 0;2003 var len: u32 = 0;
2005 if (buffered.len != 0) {2004 if (buffered.len != 0) {
2006 iovecs[len] = .{2005 iovecs[len] = .{
2007 .base = buffered.ptr,2006 .buf = buffered.ptr,
2008 .len = buffered.len,2007 .len = buffered.len,
2009 };2008 };
2010 len += 1;2009 len += 1;
2011 }2010 }
2012 for (data[0..data.len]) |bytes| {2011 for (data) |bytes| {
2013 if (bytes.len == 0) continue;2012 if (bytes.len == 0) continue;
2014 iovecs[len] = .{2013 iovecs[len] = .{
2015 .buf = bytes.ptr,2014 .buf = bytes.ptr,
2016 .len = bytes.len,2015 .len = bytes.len,
2017 };2016 };
2018 len += 1;2017 len += 1;
2018 if (iovecs.len - len == 0) break;
2019 }2019 }
2020 if (len == 0) return 0;
2020 const pattern = data[data.len - 1];2021 const pattern = data[data.len - 1];
2021 switch (splat) {2022 switch (splat) {
2022 0 => len -= 1,2023 0 => if (iovecs[len - 1].buf == data[data.len - 1].ptr) {
2024 len -= 1;
2025 },
2023 1 => {},2026 1 => {},
2024 else => switch (pattern.len) {2027 else => switch (pattern.len) {
2025 0 => {},2028 0 => {},
2026 1 => {2029 1 => memset: {
2027 // Replace the 1-byte buffer with a bigger one.2030 // 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;
2028 const memset_len = @min(splat_buffer.len, splat);2039 const memset_len = @min(splat_buffer.len, splat);
2029 const buf = splat_buffer[0..memset_len];2040 const buf = splat_buffer[0..memset_len];
2030 @memset(buf, pattern[0]);2041 @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;
2032 var remaining_splat = splat - buf.len;2044 var remaining_splat = splat - buf.len;
2033 while (remaining_splat > splat_buffer.len and len < iovecs.len) {2045 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 };
2035 remaining_splat -= splat_buffer.len;2047 remaining_splat -= splat_buffer.len;
2036 len += 1;2048 len += 1;
2037 }2049 }
2038 if (remaining_splat > 0 and len < iovecs.len) {2050 if (remaining_splat > 0 and iovecs.len - len != 0) {
2039 iovecs[len] = .{ .buf = &splat_buffer, .len = remaining_splat };2051 iovecs[len] = .{ .buf = splat_buffer.ptr, .len = remaining_splat };
2040 len += 1;2052 len += 1;
2041 }2053 }
2042 },2054 },
...@@ -2134,48 +2146,52 @@ pub const Stream = struct {...@@ -2134,48 +2146,52 @@ pub const Stream = struct {
2134 .flags = 0,2146 .flags = 0,
2135 };2147 };
2136 };2148 };
2137 if (data.len != 0) {2149 if (msg.iovlen == 0) return 0;
2138 const pattern = data[data.len - 1];2150 const pattern = data[data.len - 1];
2139 switch (splat) {2151 switch (splat) {
2140 0 => if (msg.iovlen != 0 and iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr) {2152 0 => if (iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr) {
2141 msg.iovlen -= 1;2153 msg.iovlen -= 1;
2142 },2154 },
2143 1 => {},2155 1 => {},
2144 else => switch (pattern.len) {2156 else => switch (pattern.len) {
2145 0 => {},2157 0 => {},
2146 1 => memset: {2158 1 => memset: {
2147 // Replace the 1-byte buffer with a bigger one.2159 // 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)2160 if (iovecs[msg.iovlen - 1].base == data[data.len - 1].ptr) msg.iovlen -= 1;
2149 msg.iovlen -= 1;2161 if (iovecs.len - msg.iovlen == 0) break :memset;
2150 if (iovecs.len - msg.iovlen == 0) break :memset;2162 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2151 const splat_buffer = io_w.buffer[io_w.end..];2163 var backup_buffer: [32]u8 = undefined;
2152 const memset_len = @min(splat_buffer.len, splat);2164 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2153 const buf = splat_buffer[0..memset_len];2165 splat_buffer_candidate
2154 @memset(buf, pattern[0]);2166 else
2155 iovecs[msg.iovlen] = .{ .base = buf.ptr, .len = buf.len };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 };
2156 msg.iovlen += 1;2178 msg.iovlen += 1;
2157 var remaining_splat = splat - buf.len;2179 remaining_splat -= splat_buffer.len;
2158 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {2180 }
2159 assert(buf.len == splat_buffer.len);2181 if (remaining_splat > 0 and iovecs.len - msg.iovlen != 0) {
2160 iovecs[msg.iovlen] = .{ .base = splat_buffer.ptr, .len = splat_buffer.len };2182 iovecs[msg.iovlen] = .{ .base = splat_buffer.ptr, .len = remaining_splat };
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 };
2175 msg.iovlen += 1;2183 msg.iovlen += 1;
2176 },2184 }
2177 },2185 },
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 },
2179 }2195 }
2180 const flags = posix.MSG.NOSIGNAL;2196 const flags = posix.MSG.NOSIGNAL;
2181 return io_w.consume(std.posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {2197 return io_w.consume(std.posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
...@@ -2186,7 +2202,8 @@ pub const Stream = struct {...@@ -2186,7 +2202,8 @@ pub const Stream = struct {
21862202
2187 fn sendFile(io_w: *io.Writer, file_reader: *File.Reader, limit: io.Limit) io.Writer.FileError!usize {2203 fn sendFile(io_w: *io.Writer, file_reader: *File.Reader, limit: io.Limit) io.Writer.FileError!usize {
2188 const w: *Writer = @fieldParentPtr("interface", io_w);2204 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);
2190 }2207 }
2191 },2208 },
2192 };2209 };