From dd99102f4b03ea9ecbae284a2f2fbca1383d460f Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 13 May 2026 13:36:50 +0800 Subject: [PATCH] Defer HTTP completion callbacks to next tick Client.makeRequest used to call self.perform(0) after handing the transfer to libcurl. That perform() does two things: drives curl_multi_perform (so bytes hit the wire) AND drains curl_multi_info_read messages, which is what fires the user-facing header/data/done callbacks. The issue is that, even in non-cache cases, a request could be immediately resolved in libcurl, and thus callbacks executed synchronously. By only calling `curl_multi_perform` on a new request, we prevent this from happening. --- src/browser/HttpClient.zig | 10 +++++++++- src/browser/tests/worker/worker.html | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/browser/HttpClient.zig b/src/browser/HttpClient.zig index 8b78cf4c..7c7bdd5e 100644 --- a/src/browser/HttpClient.zig +++ b/src/browser/HttpClient.zig @@ -611,7 +611,15 @@ fn makeRequest(self: *Client, conn: *http.Connection, transfer: *Transfer) anyer return err; }; } - _ = try self.perform(0); + + // Start the request (and move along any other request). This used to call + // self.perform(0) but that can also execute callbacks. Normally, that + // wouldn't be so bad. But curl can synchronously fire callbacks for the + // request we JUST added, which we do not want (it results in incorrect + // execution). + self.performing = true; + defer self.performing = false; + _ = try self.handles.perform(); } pub const PerformStatus = enum { diff --git a/src/browser/tests/worker/worker.html b/src/browser/tests/worker/worker.html index 2740d7c0..8b15346a 100644 --- a/src/browser/tests/worker/worker.html +++ b/src/browser/tests/worker/worker.html @@ -337,3 +337,26 @@ }); } + +