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.
This commit is contained in:
Karl Seguin
2026-05-13 13:36:50 +08:00
parent 2fcad23834
commit dd99102f4b
2 changed files with 32 additions and 1 deletions

View File

@@ -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 {

View File

@@ -337,3 +337,26 @@
});
}
</script>
<script id="worker_post_before_load" type=module>
// Per spec, messages posted before the worker has finished loading must
// be buffered and delivered once onmessage is registered. Without the
// pending-message queue this hangs: postMessage fires immediately after
// new Worker, while the worker script's HTTP fetch is still in flight,
// so by the time the worker dispatches the message there is no
// onmessage listener yet and the message gets silently dropped.
{
const state = await testing.async();
const worker = new Worker('./echo-worker.js');
worker.onmessage = function(event) {
state.resolve(event.data);
};
// No setTimeout — post right now while the script is still loading.
worker.postMessage({ greeting: 'before-load' });
await state.done((response) => {
testing.expectEqual('before-load', response.echo.greeting);
testing.expectEqual('worker', response.from);
});
}
</script>