| author | |
| committer | |
| log | 55d6341eabf002017587863c92e6d5860527a72a |
| tree | 4bee65b4222a262db9f632b598fd14975fab93db |
| parent | 5b5243b5b7f5d01267afa1241c5847b3507694c1 |
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 | } |
| 929 | 929 | ||
| 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 | } |
| 936 | 937 | ||
| 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 | } |
| 943 | 946 | ||
| 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 | } |
| 951 | 955 | ||
| 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 would | 960 | // 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, |
| 23 | 23 | ||
| 24 | pub const VTable = struct { | 24 | pub const VTable = struct { |
| 25 | /// Sends bytes to the logical sink. A write will only be sent here if it | 25 | /// 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` in | 28 | /// `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 alias | 29 | /// 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; all | 35 | /// `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 so | 37 | /// 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 from | 40 | /// 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 mean | 43 | /// 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 of | 44 | /// 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, |
| 50 | 47 | ||
| 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 of | 52 | /// `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 mean | 55 | /// 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 of | 56 | /// end. A subsequent call may return nonzero, or signal end of stream via |
| 60 | /// stream via `error.WriteFailed`. Caller must check `file_reader` state | 57 | /// `error.WriteFailed`. Caller may check `file_reader` state |
| 61 | /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or | 58 | /// (`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 | }; |
| 75 | 82 | ||
| 76 | pub const Error = error{ | 83 | pub 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 | } |
| 143 | 150 | ||
| 144 | pub fn countSplat(n: usize, data: []const []const u8, splat: usize) usize { | 151 | pub 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 | } |
| 151 | 157 | ||
| 152 | pub fn countSendFileUpperBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize { | 158 | pub 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 | } |
| 156 | 162 | ||
| ... | @@ -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 { |
| 167 | pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | 173 | pub 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 | } |
| 217 | 223 | ||
| 218 | /// Drains all remaining buffered data. | 224 | /// Returns how many bytes were consumed from `header` and `data`. |
| 219 | /// | 225 | pub fn writeSplatHeader( |
| 220 | /// It is legal for `VTable.drain` implementations to refrain from modifying | 226 | w: *Writer, |
| 221 | /// `end`. | 227 | header: []const u8, |
| 222 | pub 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; |
| 226 | 232 | if (new_end <= w.buffer.len) { | |
| 227 | /// Calls `VTable.drain` but hides the last `preserve_length` bytes from the | 233 | @memcpy(w.buffer[w.end..][0..header.len], header); |
| 228 | /// implementation, keeping them buffered. | 234 | w.end = new_end; |
| 229 | pub 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. | ||
| 244 | pub 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 | } |
| 285 | 252 | ||
| 286 | pub fn drainToLimit( | 253 | /// Equivalent to `writeSplatHeader` but writes at most `limit` bytes. |
| 287 | noalias w: *Writer, | 254 | pub 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 | } |
| 299 | 268 | ||
| 269 | /// Drains all remaining buffered data. | ||
| 270 | pub fn flush(w: *Writer) Error!void { | ||
| 271 | return w.vtable.flush(w); | ||
| 272 | } | ||
| 273 | |||
| 274 | /// Repeatedly calls `VTable.drain` until `end` is zero. | ||
| 275 | pub 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. | ||
| 281 | pub 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. | ||
| 287 | pub 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 | |||
| 300 | pub fn unusedCapacitySlice(w: *const Writer) []u8 { | 297 | pub 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 | } |
| 674 | 671 | ||
| 675 | /// Forwards a `sendFile` to a second `Writer` instance. `w` is only used for | 672 | /// Returns how many bytes from `header` and `file_reader` were consumed. |
| 676 | /// its buffer, but it has its `end` and `count` adjusted accordingly depending | 673 | pub 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. | ||
| 680 | pub 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 | } |
| 717 | 692 | ||
| 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 | }; |
| 2130 | 2106 | ||
| 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 | } |
| 2173 | 2149 | ||
| 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 { |
| 2186 | 2202 | ||
| 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 | }; |