Segfault with http.Client after fetch() and move

Hello all,

I ran into this bug while working on a project and got down to this minimal reproduction:

const std = @import("std");

fn getClient(a: std.mem.Allocator) !std.http.Client {
    // Initialize client
    var client: std.http.Client = .{ .allocator = a };

    // Perform a fetch
    _ = try client.fetch(.{ .location = .{ .url = "https://smlavine.com" } });

    // Return client
    return client;
}

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    defer _ = gpa.deinit();
    const a = gpa.allocator();

    var client = try getClient(a);
    client.deinit();
}

Results in this segfault:

minimal-crash-repro$ zig run src/fetch-sections.zig
Segmentation fault at address 0x7fdbd05ef000
/usr/lib/zig/std/mem/Allocator.zig:430:26: 0x1112c38 in free__anon_18432 (std.zig)
    @memset(non_const_ptr[0..bytes_len], undefined);
                         ^
/usr/lib/zig/std/http/Client.zig:351:21: 0x11a1607 in destroy (std.zig)
            gpa.free(base[0..allocLen(c.client, c.host_len)]);
                    ^
/usr/lib/zig/std/http/Client.zig:407:28: 0x11886f8 in destroy (std.zig)
                tls.destroy();
                           ^
/usr/lib/zig/std/http/Client.zig:186:31: 0x117439f in deinit (std.zig)
            connection.destroy();
                              ^
/usr/lib/zig/std/http/Client.zig:1280:34: 0x1169ec7 in deinit (std.zig)
    client.connection_pool.deinit();
                                 ^
/home/smlavine/Code/projects/rit-class-stats/src/fetch-sections.zig:20:18: 0x1165847 in main (fetch-sections.zig)
    client.deinit();
                 ^
/usr/lib/zig/std/start.zig:627:37: 0x1166049 in posixCallMainAndExit (std.zig)
            const result = root.main() catch |err| {
                                    ^
/usr/lib/zig/std/start.zig:232:5: 0x1165351 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
Aborted                    (core dumped) zig run src/fetch-sections.zig

If I remove the fetch() call, then there is no segfault.

If I remove the Client.deinit() call, there is a leak.

Glancing at the error trace – is there an internal pointer inside Client that is incorrectly outliving the move?

What am I missing here? Am I doing something wrong? On version 0.15.2. Help appreciated, thanks.

The Connection contains pointer to the client https://codeberg.org/ziglang/zig/src/commit/6069161a51c4672b37c87d8cda06999baf5fef2c/lib/std/http/Client.zig#L232

This means you can’t move the client and instead you should pass pointer to it in the functions you plan to use it in instead.

Okay, I suppose I will have to heap-allocate the client in my program then, thanks.

I wonder what the best way to document this behavior in the standard library would be.

You won’t have to heap allocate if you keep your client on the caller level and pass pointers to it instead. You simply can’t move it, that’s all. Moving complex containers in general is something you should avoid.

My particular use-case is wrapping an Iterator struct around the client to perform several paginated GET requests. I am performing a Client.fetch() in my .init() function in order to obtain a max-page count from the API. I have the http.Client as a field on the Iterator struct.

So I should create the Client on the stack of the caller, then pass the reference to my .init() function instead?

Indeed. The field should also store a pointer to the client instead.

1 Like

Okay, thanks.