Skip to content

std.http.Client hangs indefinitely on response body sizes > 4096 bytes #15710

Closed
@ultd

Description

@ultd

Zig Version

0.11.0-dev.3107+6547d2331

Steps to Reproduce and Observed Behavior

Make a https request using the std.http.Client and set transfer_encoding to .chunked on the request like so:

        var client: http.Client = .{ .allocator = allocator };
        var req = try client.request(http.Method.POST, http_endpoint, default_http_headers, .{});
        req.transfer_encoding = .chunked;
        defer req.deinit();

        try req.start();
        try req.writer().writeAll(reqBody);  // reqBody is just []u8
        try req.finish();
        try req.wait();

        if (req.response.status != http.Status.ok) {
            return Error.ResponseNotStatusOk;
        }

        const respBody = try req.reader().readAllAlloc(self.allocator, 12 * 1024 * 1024); // 12Mb max size set
        defer self.allocator.free(body);

        // do something with respBody

Once you surpass a certain body size (greater than 4096 i believe), it will remain stuck in an infinite loop. The response body size of the request which causes this issue is much smaller than 12Mb - it's around 3.4Mb. If I make request under 4Kb it seems to work fine.

I tracked the bug to BufferedConnection.readAtLeast and it seems it's stuck in the while (out_index < len) loop.

I logged values of the vars that affect this loop like so:

pub fn readAtLeast(bconn: *BufferedConnection, buffer: []u8, len: usize) ReadError!usize {
        var out_index: u16 = 0;
        while (out_index < len) {
            std.log.debug("readAtLeast.while - begin", .{});
            const available = bconn.read_end - bconn.read_start;
            const left = buffer.len - out_index;

            if (available > 0) {
                const can_read = @intCast(u16, @min(available, left));
                std.log.debug("readAtLeast.while - available: {}, left: {}, can_read: {}", .{ available, left, can_read });

                @memcpy(buffer[out_index..][0..can_read], bconn.read_buf[bconn.read_start..][0..can_read]);
                out_index += can_read;
                bconn.read_start += can_read;

                continue;
            }
            std.log.debug("readAtLeast.while - passed continue", .{});

            if (left > bconn.read_buf.len) {
                // skip the buffer if the output is large enough
                return bconn.conn.read(buffer[out_index..]);
            }

            try bconn.fill();
            std.log.debug("readAtLeast.while - end", .{});
        }
        std.log.debug("readAtLeast - returning", .{});

        return out_index;
    }

and it produces these logs continuously:

...
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
debug: readAtLeast.while - begin
debug: readAtLeast.while - available: 12536, left: 0, can_read: 0
...

Also, sometimes it doesn't get stuck in loop and returns this error instead (~20% of the time):

thread 1290939 panic: @memcpy arguments have non-equal lengths
/Users/bkk/bin/zig/lib/std/crypto/tls/Client.zig:1062:25: 0x104c0671b in readvAdvanced__anon_13130 (main)
            @memcpy(frag[0..in], first);
                        ^
/Users/bkk/bin/zig/lib/std/crypto/tls/Client.zig:898:53: 0x104c046cf in readvAtLeast__anon_13129 (main)
        var amt = try c.readvAdvanced(stream, iovecs[vec_i..]);
                                                    ^
/Users/bkk/bin/zig/lib/std/crypto/tls/Client.zig:859:24: 0x104c044b3 in readAtLeast__anon_13127 (main)
    return readvAtLeast(c, stream, &iovecs, len);
                       ^
/Users/bkk/bin/zig/lib/std/crypto/tls/Client.zig:864:23: 0x104c043d3 in read__anon_13126 (main)
    return readAtLeast(c, stream, buffer, 1);
                      ^
/Users/bkk/bin/zig/lib/std/http/Client.zig:173:46: 0x104bc9e77 in read (main)
            .tls => conn.tls_client.read(conn.stream, buffer),
                                             ^
/Users/bkk/bin/zig/lib/std/http/Client.zig:267:57: 0x104b7966b in fill (main)
        const nread = try bconn.conn.read(bconn.read_buf[0..]);
                                                        ^
/Users/bkk/bin/zig/lib/std/http/protocol.zig:553:35: 0x104b78f9f in read__anon_7953 (main)
                    try bconn.fill();
                                  ^
/Users/bkk/bin/zig/lib/std/http/Client.zig:686:111: 0x104b7a87b in transferRead (main)
            const amt = try req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip);
                                                                                                              ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:27:31: 0x104bdee07 in read (main)
            return readFn(self.context, buffer);
                              ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:243:50: 0x104d1240b in readByte (main)
            const amt_read = try self.read(result[0..]);
                                                 ^
/Users/bkk/bin/zig/lib/std/compress/deflate/decompressor.zig:822:47: 0x104bdb8d7 in moreBits (main)
            var c = self.inner_reader.readByte() catch |e| {
                                              ^
/Users/bkk/bin/zig/lib/std/compress/deflate/decompressor.zig:412:30: 0x104b8443f in nextBlock (main)
                self.moreBits() catch |e| {
                             ^
/Users/bkk/bin/zig/lib/std/compress/deflate/decompressor.zig:471:26: 0x104be05db in read (main)
                self.step(self) catch |e| {
                         ^
/Users/bkk/bin/zig/lib/std/compress/gzip.zig:128:45: 0x104b88573 in read (main)
            const r = try self.inflater.read(buffer);
                                            ^
/Users/bkk/bin/zig/lib/std/http/Client.zig:814:39: 0x104b87c17 in read (main)
            .gzip => |*gzip| gzip.read(buffer) catch return error.DecompressionFailure,
                                      ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:27:31: 0x104be46b3 in read (main)
            return readFn(self.context, buffer);
                              ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:46:49: 0x104b8929f in readAtLeast (main)
                const amt = try self.read(buffer[index..]);
                                                ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:34:52: 0x104b69727 in readAll (main)
            return readAtLeast(self, buffer, buffer.len);
                                                   ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:80:52: 0x104b6913b in readAllArrayListAligned__anon_6214 (main)
                const bytes_read = try self.readAll(dest_slice);
                                                   ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:65:48: 0x104b68f03 in readAllArrayList (main)
            return self.readAllArrayListAligned(null, array_list, max_append_size);
                                               ^
/Users/bkk/bin/zig/lib/std/io/reader.zig:105:38: 0x104b698e3 in readAllAlloc (main)
            try self.readAllArrayList(&array_list, max_size);

Expected Behavior

It should not hang indefinitely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behaviorstandard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions