Skip to content

Commit b692c5d

Browse files
committed
nonblocking dynamic imports
Allows dynamic imports to be loading asynchronously. I know reddit isnt the best example, since it doesn't fully load, but this reduced the load time from ~7.2s to ~4.8s.
1 parent eff7d58 commit b692c5d

File tree

5 files changed

+340
-75
lines changed

5 files changed

+340
-75
lines changed

src/browser/ScriptManager.zig

Lines changed: 112 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ client: *Http.Client,
6767
allocator: Allocator,
6868
buffer_pool: BufferPool,
6969
script_pool: std.heap.MemoryPool(PendingScript),
70+
async_module_pool: std.heap.MemoryPool(AsyncModule),
7071

7172
const OrderList = std.DoublyLinkedList;
7273

@@ -85,13 +86,15 @@ pub fn init(browser: *Browser, page: *Page) ScriptManager {
8586
.static_scripts_done = false,
8687
.buffer_pool = BufferPool.init(allocator, 5),
8788
.script_pool = std.heap.MemoryPool(PendingScript).init(allocator),
89+
.async_module_pool = std.heap.MemoryPool(AsyncModule).init(allocator),
8890
};
8991
}
9092

9193
pub fn deinit(self: *ScriptManager) void {
9294
self.reset();
9395
self.buffer_pool.deinit();
9496
self.script_pool.deinit();
97+
self.async_module_pool.deinit();
9598
}
9699

97100
pub fn reset(self: *ScriptManager) void {
@@ -256,7 +259,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
256259
// Unlike external modules which can only ever be executed after releasing an
257260
// http handle, these are executed without there necessarily being a free handle.
258261
// Thus, Http/Client.zig maintains a dedicated handle for these calls.
259-
pub fn blockingGet(self: *ScriptManager, url: [:0]const u8) !BlockingResult {
262+
pub fn blockingGet(self: *ScriptManager, url: [:0]const u8) !GetResult {
260263
std.debug.assert(self.is_blocking == false);
261264

262265
self.is_blocking = true;
@@ -302,6 +305,34 @@ pub fn blockingGet(self: *ScriptManager, url: [:0]const u8) !BlockingResult {
302305
}
303306
}
304307

308+
pub fn getAsyncModule(self: *ScriptManager, url: [:0]const u8, cb: AsyncModule.Callback, cb_data: *anyopaque) !void {
309+
const async = try self.async_module_pool.create();
310+
errdefer self.async_module_pool.destroy(async);
311+
312+
async.* = .{
313+
.cb = cb,
314+
.manager = self,
315+
.cb_data = cb_data,
316+
};
317+
318+
var headers = try self.client.newHeaders();
319+
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
320+
321+
try self.client.request(.{
322+
.url = url,
323+
.method = .GET,
324+
.headers = headers,
325+
.cookie_jar = self.page.cookie_jar,
326+
.ctx = async,
327+
.resource_type = .script,
328+
.start_callback = if (log.enabled(.http, .debug)) AsyncModule.startCallback else null,
329+
.header_callback = AsyncModule.headerCallback,
330+
.data_callback = AsyncModule.dataCallback,
331+
.done_callback = AsyncModule.doneCallback,
332+
.error_callback = AsyncModule.errorCallback,
333+
});
334+
}
335+
305336
pub fn staticScriptsDone(self: *ScriptManager) void {
306337
std.debug.assert(self.static_scripts_done == false);
307338
self.static_scripts_done = true;
@@ -594,7 +625,7 @@ const Script = struct {
594625
.javascript => _ = js_context.eval(content, url) catch break :blk false,
595626
.module => {
596627
// We don't care about waiting for the evaluation here.
597-
_ = js_context.module(content, url, cacheable) catch break :blk false;
628+
js_context.module(false, content, url, cacheable) catch break :blk false;
598629
},
599630
}
600631
break :blk true;
@@ -760,7 +791,7 @@ const Blocking = struct {
760791
const State = union(enum) {
761792
running: void,
762793
err: anyerror,
763-
done: BlockingResult,
794+
done: GetResult,
764795
};
765796

766797
fn startCallback(transfer: *Http.Transfer) !void {
@@ -814,19 +845,93 @@ const Blocking = struct {
814845
fn errorCallback(ctx: *anyopaque, err: anyerror) void {
815846
var self: *Blocking = @ptrCast(@alignCast(ctx));
816847
self.state = .{ .err = err };
817-
self.buffer_pool.release(self.buffer);
848+
if (self.buffer.items.len > 0) {
849+
self.buffer_pool.release(self.buffer);
850+
}
851+
}
852+
};
853+
854+
pub const AsyncModule = struct {
855+
cb: Callback,
856+
cb_data: *anyopaque,
857+
manager: *ScriptManager,
858+
buffer: std.ArrayListUnmanaged(u8) = .{},
859+
860+
pub const Callback = *const fn (ptr: *anyopaque, result: anyerror!GetResult) void;
861+
862+
fn startCallback(transfer: *Http.Transfer) !void {
863+
log.debug(.http, "script fetch start", .{ .req = transfer, .async = true });
864+
}
865+
866+
fn headerCallback(transfer: *Http.Transfer) !void {
867+
const header = &transfer.response_header.?;
868+
log.debug(.http, "script header", .{
869+
.req = transfer,
870+
.async = true,
871+
.status = header.status,
872+
.content_type = header.contentType(),
873+
});
874+
875+
if (header.status != 200) {
876+
return error.InvalidStatusCode;
877+
}
878+
879+
var self: *AsyncModule = @ptrCast(@alignCast(transfer.ctx));
880+
self.buffer = self.manager.buffer_pool.get();
881+
}
882+
883+
fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void {
884+
// too verbose
885+
// log.debug(.http, "script data chunk", .{
886+
// .req = transfer,
887+
// .blocking = true,
888+
// });
889+
890+
var self: *AsyncModule = @ptrCast(@alignCast(transfer.ctx));
891+
self.buffer.appendSlice(self.manager.allocator, data) catch |err| {
892+
log.err(.http, "SM.dataCallback", .{
893+
.err = err,
894+
.len = data.len,
895+
.ascyn = true,
896+
.transfer = transfer,
897+
});
898+
return err;
899+
};
900+
}
901+
902+
fn doneCallback(ctx: *anyopaque) !void {
903+
var self: *AsyncModule = @ptrCast(@alignCast(ctx));
904+
defer self.manager.async_module_pool.destroy(self);
905+
self.cb(self.cb_data, .{
906+
.buffer = self.buffer,
907+
.buffer_pool = &self.manager.buffer_pool,
908+
});
909+
}
910+
911+
fn errorCallback(ctx: *anyopaque, err: anyerror) void {
912+
var self: *AsyncModule = @ptrCast(@alignCast(ctx));
913+
914+
if (err != error.Abort) {
915+
self.cb(self.cb_data, err);
916+
}
917+
918+
if (self.buffer.items.len > 0) {
919+
self.manager.buffer_pool.release(self.buffer);
920+
}
921+
922+
self.manager.async_module_pool.destroy(self);
818923
}
819924
};
820925

821-
pub const BlockingResult = struct {
926+
pub const GetResult = struct {
822927
buffer: std.ArrayListUnmanaged(u8),
823928
buffer_pool: *BufferPool,
824929

825-
pub fn deinit(self: *BlockingResult) void {
930+
pub fn deinit(self: *GetResult) void {
826931
self.buffer_pool.release(self.buffer);
827932
}
828933

829-
pub fn src(self: *const BlockingResult) []const u8 {
934+
pub fn src(self: *const GetResult) []const u8 {
830935
return self.buffer.items;
831936
}
832937
};

src/browser/mimalloc.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ pub fn getRSS() i64 {
6262
const data = writer.written();
6363
const index = std.mem.indexOf(u8, data, "rss: ") orelse return -1;
6464
const sep = std.mem.indexOfScalarPos(u8, data, index + 5, ' ') orelse return -2;
65-
const value = std.fmt.parseFloat(f64, data[index+5..sep]) catch return -3;
66-
const unit = data[sep+1..];
65+
const value = std.fmt.parseFloat(f64, data[index + 5 .. sep]) catch return -3;
66+
const unit = data[sep + 1 ..];
6767
if (std.mem.startsWith(u8, unit, "KiB,")) {
6868
return @as(i64, @intFromFloat(value)) * 1024;
6969
}

src/browser/page.zig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,14 @@ pub const Page = struct {
255255
try Node.prepend(head, &[_]Node.NodeOrText{.{ .node = parser.elementToNode(base) }});
256256
}
257257

258-
pub fn fetchModuleSource(ctx: *anyopaque, src: [:0]const u8) !ScriptManager.BlockingResult {
258+
pub fn fetchModuleSource(ctx: *anyopaque, url: [:0]const u8) !ScriptManager.GetResult {
259259
const self: *Page = @ptrCast(@alignCast(ctx));
260-
return self.script_manager.blockingGet(src);
260+
return self.script_manager.blockingGet(url);
261+
}
262+
263+
pub fn fetchAsyncModuleSource(ctx: *anyopaque, url: [:0]const u8, cb: ScriptManager.AsyncModule.Callback, cb_data: *anyopaque) !void {
264+
const self: *Page = @ptrCast(@alignCast(ctx));
265+
return self.script_manager.getAsyncModule(url, cb, cb_data);
261266
}
262267

263268
pub fn wait(self: *Page, wait_ms: i32) Session.WaitResult {

0 commit comments

Comments
 (0)