From 39bd76e07970b6d2ece38c03c81beb7dfb0103ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Tue, 1 Sep 2026 14:00:53 +0200 Subject: [PATCH] http: keep connections alive across navigations CURLMOPT_MAXCONNECTS was never set, so libcurl used its default of 4x the number of easy handles currently attached to the multi. Handles are added and removed per transfer, so between page loads that default collapses to roughly zero and every cached connection is evicted: revisiting a host after browsing elsewhere re-paid connect + TLS every time. Measured over 25 navigations across 5 sites (5 rounds, ReleaseFast), median warm navigation drops from 0.73s to 0.42s on news.ycombinator.com and from 0.71s to 0.42s on github.com; total navigation time 11.1s -> 7.3s. Loading one host repeatedly was already fast and is unchanged; the win is on cross-site browsing, which is what agents and crawlers actually do. --- src/network/http.zig | 3 +++ src/sys/libcurl.zig | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/network/http.zig b/src/network/http.zig index 19295ee59..c7914acb0 100644 --- a/src/network/http.zig +++ b/src/network/http.zig @@ -668,6 +668,9 @@ pub const Handles = struct { errdefer libcurl.curl_multi_cleanup(multi) catch {}; try libcurl.curl_multi_setopt(multi, .max_host_connections, config.httpMaxHostOpen()); + // Default is 4x the attached easy handles, i.e. ~0 between page loads, + // so keepalive connections were evicted on every cross-site navigation. + try libcurl.curl_multi_setopt(multi, .max_connects, 128); return .{ .multi = multi }; } diff --git a/src/sys/libcurl.zig b/src/sys/libcurl.zig index 4ffe54409..dda5ec6e5 100644 --- a/src/sys/libcurl.zig +++ b/src/sys/libcurl.zig @@ -239,6 +239,7 @@ pub const CurlHttpVersion = enum(c_long) { pub const CurlMOption = enum(c.CURLMoption) { max_host_connections = c.CURLMOPT_MAX_HOST_CONNECTIONS, + max_connects = c.CURLMOPT_MAXCONNECTS, }; pub const CurlInfo = enum(c.CURLINFO) { @@ -742,7 +743,7 @@ pub fn curl_multi_cleanup(multi: *CurlM) ErrorMulti!void { pub fn curl_multi_setopt(multi: *CurlM, comptime option: CurlMOption, value: anytype) ErrorMulti!void { const opt: c.CURLMoption = @intFromEnum(option); const code = switch (option) { - .max_host_connections => blk: { + .max_host_connections, .max_connects => blk: { const n: c_long = switch (@typeInfo(@TypeOf(value))) { .comptime_int, .int => @intCast(value), else => @compileError("expected integer for " ++ @tagName(option) ++ ", got " ++ @typeName(@TypeOf(value))),