Start downloading all synchronous imports ASAP

This changes how non-async module loading works. In general, module loading
is triggered by a v8 callback. We ask it to process a module (a <script type=
module>) and then for every module that it depends on, we get a callback. This
callback expects the nested v8.Module instance, so we need to load it then and
there (as opposed to dynamic imports, where we only have to return a promise).

Previously, we solved this by issuing a blocking HTTP get in each callback. The
HTTP loop was able to continuing downloading already-queued resources, but if
a module depended on 20 nested modules, we'd issue 20 blocking gets one after
the other.

Once a module is compiled, we can ask v8 for a list of its dependent module. We
can them immediately start to download all of those modules. We then evaluate
the original module, which will trigger our callback. At this point, we still
need to block and wait for the response, but we've already started the download
and it's much faster. Sure, for the first module, we might need to wait the same
amount of time, but for the other 19, chances are by the time the callback
executes, we already have it downloaded and ready.
This commit is contained in:
Karl Seguin
2025-09-25 18:05:09 +08:00
parent 2aa4b03673
commit 418dc6fdc2
6 changed files with 159 additions and 134 deletions

View File

@@ -86,9 +86,6 @@ allocator: Allocator,
// request. These wil come and go with each request.
transfer_pool: std.heap.MemoryPool(Transfer),
// see ScriptManager.blockingGet
blocking: Handle,
// To notify registered subscribers of events, the browser sets/nulls this for us.
notification: ?*Notification = null,
@@ -121,16 +118,12 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie
var handles = try Handles.init(allocator, client, ca_blob, &opts);
errdefer handles.deinit(allocator);
var blocking = try Handle.init(client, ca_blob, &opts);
errdefer blocking.deinit();
client.* = .{
.queue = .{},
.active = 0,
.intercepted = 0,
.multi = multi,
.handles = handles,
.blocking = blocking,
.allocator = allocator,
.http_proxy = opts.http_proxy,
.user_agent = opts.user_agent,
@@ -142,7 +135,6 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie
pub fn deinit(self: *Client) void {
self.abort();
self.blocking.deinit();
self.handles.deinit(self.allocator);
_ = c.curl_multi_cleanup(self.multi);
@@ -263,12 +255,6 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
return transfer.fulfill(status, headers, body);
}
// See ScriptManager.blockingGet
pub fn blockingRequest(self: *Client, req: Request) !void {
const transfer = try self.makeTransfer(req);
return self.makeRequest(&self.blocking, transfer);
}
fn makeTransfer(self: *Client, req: Request) !*Transfer {
errdefer req.headers.deinit();
@@ -329,7 +315,6 @@ pub fn changeProxy(self: *Client, proxy: [:0]const u8) !void {
for (self.handles.handles) |*h| {
try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy.ptr));
}
try errorCheck(c.curl_easy_setopt(self.blocking.conn.easy, c.CURLOPT_PROXY, proxy.ptr));
}
// Same restriction as changeProxy. Should be ok since this is only called on
@@ -341,7 +326,6 @@ pub fn restoreOriginalProxy(self: *Client) !void {
for (self.handles.handles) |*h| {
try errorCheck(c.curl_easy_setopt(h.conn.easy, c.CURLOPT_PROXY, proxy));
}
try errorCheck(c.curl_easy_setopt(self.blocking.conn.easy, c.CURLOPT_PROXY, proxy));
}
fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void {
@@ -504,7 +488,7 @@ fn endTransfer(self: *Client, transfer: *Transfer) void {
log.fatal(.http, "Failed to remove handle", .{ .err = err });
};
self.handles.release(self, handle);
self.handles.release(handle);
transfer._handle = null;
self.active -= 1;
}
@@ -563,13 +547,7 @@ const Handles = struct {
return null;
}
fn release(self: *Handles, client: *Client, handle: *Handle) void {
if (handle == &client.blocking) {
// the handle we've reserved for blocking request doesn't participate
// int he in_use/available pools
return;
}
fn release(self: *Handles, handle: *Handle) void {
var node = &handle.node;
self.in_use.remove(node);
node.prev = null;
@@ -747,7 +725,7 @@ pub const Transfer = struct {
fn deinit(self: *Transfer) void {
self.req.headers.deinit();
if (self._handle) |handle| {
self.client.handles.release(self.client, handle);
self.client.handles.release(handle);
}
self.arena.deinit();
self.client.transfer_pool.destroy(self);