Pump the http_client queue after perform, not just before

Client.tick drains self.queue (assigning conns to queued transfers) only
at the start. When perform / processMessages releases a batch of conns
back to the pool, those conns sit idle until the next tick — a queued
transfer that could have run this tick waits one Runner iteration
(~20 ms in the test runner) for no reason. Adds a second drainQueue
call after perform so newly-freed conns get picked up immediately.

In practice this matters whenever httpMaxHostOpen / httpMaxConcurrent
is exceeded — pages with N > limit subresources had each "wave" of
queue overflow paying one extra tick of latency.
This commit is contained in:
Karl Seguin
2026-05-13 17:58:49 +08:00
parent c79dd2bf1f
commit 625e240f5a

View File

@@ -379,19 +379,27 @@ pub fn abortRequests(_: *Client, owner: *Owner) void {
}
pub fn tick(self: *Client, timeout_ms: u32) !PerformStatus {
try self.drainQueue();
const status = try self.perform(@intCast(timeout_ms));
// perform/processMessages just released a batch of connections back to
// the pool. Drain again so queued transfers can use them this tick
// instead of waiting for the next runner iteration.
try self.drainQueue();
return status;
}
fn drainQueue(self: *Client) !void {
while (self.queue.popFirst()) |queue_node| {
const transfer: *Transfer = @fieldParentPtr("_node", queue_node);
const conn = self.network.getConnection() orelse {
self.queue.prepend(queue_node);
break;
return;
};
// Cleared only after we've successfully obtained a connection;
// if we put the node back, _queued stays true.
transfer._queued = false;
try self.makeRequest(conn, transfer);
}
return self.perform(@intCast(timeout_ms));
}
// last layer