mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Run the MessageLoop [a lot] more.
Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/152 We previously ran the message loop every 250ms. This commit changes it to run on every tick (much more frequently). It also runs microtasks after draining the message loop (since it can generate microtasks). Also, we use to run microtasks after each script execution. Now we drain the message Loop + microtasks. We still only drain the microtasks when executing v8 callbacks. As part of this change, we also adjust our wait time based on whether or not there are pending background tasks in v8 in order to try to execute them (in general) and in a timely manner. The goal is to ensure that tasks v8 enqueued on the foreground thread are executed promptly. This change is particularly useful for calls to webassembly as compilation happens in the background and eventually requires the message loop to be drained to continue. Previously, if a script did `await WebAssembly.instantiate(....)`, there was a good chance we'd never finish the code - we'd wait too long to run the message loop AND, after running it, we wouldn't necessarily resolve the promise.
This commit is contained in:
@@ -6,8 +6,9 @@
|
|||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.v8 = .{
|
.v8 = .{
|
||||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.0.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/8c7e5df8b93e7cbd42f8f1c4ac24aaa7f05cd098.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH69R6BADRXsnhjA8wNnfKfLQACF1I7CSTZvsMAvp8",
|
.hash = "v8-0.0.0-xddH64J7BAC81mkf6G9RbEJxS-W3TIRl5iFnShwbqCqy",
|
||||||
|
|
||||||
},
|
},
|
||||||
//.v8 = .{ .path = "../zig-v8-fork" },
|
//.v8 = .{ .path = "../zig-v8-fork" },
|
||||||
.@"boringssl-zig" = .{
|
.@"boringssl-zig" = .{
|
||||||
|
|||||||
@@ -92,10 +92,24 @@ pub fn runMicrotasks(self: *Browser) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn runMacrotasks(self: *Browser) !?u64 {
|
pub fn runMacrotasks(self: *Browser) !?u64 {
|
||||||
return try self.env.runMacrotasks();
|
const env = &self.env;
|
||||||
|
|
||||||
|
const time_to_next = try self.env.runMacrotasks();
|
||||||
|
env.pumpMessageLoop();
|
||||||
|
|
||||||
|
// either of the above could have queued more microtasks
|
||||||
|
env.runMicrotasks();
|
||||||
|
|
||||||
|
return time_to_next;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runMessageLoop(self: *const Browser) void {
|
pub fn hasBackgroundTasks(self: *Browser) bool {
|
||||||
self.env.pumpMessageLoop();
|
return self.env.hasBackgroundTasks();
|
||||||
|
}
|
||||||
|
pub fn waitForBackgroundTasks(self: *Browser) void {
|
||||||
|
self.env.waitForBackgroundTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runIdleTasks(self: *const Browser) void {
|
||||||
self.env.runIdleTasks();
|
self.env.runIdleTasks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,12 +311,12 @@ pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void {
|
|||||||
if (comptime builtin.is_test == false) {
|
if (comptime builtin.is_test == false) {
|
||||||
// HTML test runner manually calls these as necessary
|
// HTML test runner manually calls these as necessary
|
||||||
try self.js.scheduler.add(session.browser, struct {
|
try self.js.scheduler.add(session.browser, struct {
|
||||||
fn runMessageLoop(ctx: *anyopaque) !?u32 {
|
fn runIdleTasks(ctx: *anyopaque) !?u32 {
|
||||||
const b: *@import("Browser.zig") = @ptrCast(@alignCast(ctx));
|
const b: *@import("Browser.zig") = @ptrCast(@alignCast(ctx));
|
||||||
b.runMessageLoop();
|
b.runIdleTasks();
|
||||||
return 250;
|
return 200;
|
||||||
}
|
}
|
||||||
}.runMessageLoop, 250, .{ .name = "page.messageLoop" });
|
}.runIdleTasks, 200, .{ .name = "page.runIdleTasks", .low_priority = true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -862,8 +862,7 @@ pub const Script = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
// We should run microtasks even if script execution fails.
|
local.runMacrotasks(); // also runs microtasks
|
||||||
local.runMicrotasks();
|
|
||||||
_ = page.js.scheduler.run() catch |err| {
|
_ = page.js.scheduler.run() catch |err| {
|
||||||
log.err(.page, "scheduler", .{ .err = err });
|
log.err(.page, "scheduler", .{ .err = err });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
|
|||||||
std.debug.assert(http_client.intercepted == 0);
|
std.debug.assert(http_client.intercepted == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ms = ms_to_next_task orelse blk: {
|
const ms: u64 = ms_to_next_task orelse blk: {
|
||||||
if (wait_ms - ms_remaining < 100) {
|
if (wait_ms - ms_remaining < 100) {
|
||||||
if (comptime builtin.is_test) {
|
if (comptime builtin.is_test) {
|
||||||
return .done;
|
return .done;
|
||||||
@@ -267,6 +267,14 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
|
|||||||
// background jobs.
|
// background jobs.
|
||||||
break :blk 50;
|
break :blk 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browser.hasBackgroundTasks()) {
|
||||||
|
// _we_ have nothing to run, but v8 is working on
|
||||||
|
// background tasks. We'll wait for them.
|
||||||
|
browser.waitForBackgroundTasks();
|
||||||
|
break :blk 20;
|
||||||
|
}
|
||||||
|
|
||||||
// No http transfers, no cdp extra socket, no
|
// No http transfers, no cdp extra socket, no
|
||||||
// scheduled tasks, we're done.
|
// scheduled tasks, we're done.
|
||||||
return .done;
|
return .done;
|
||||||
@@ -292,8 +300,14 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
|
|||||||
// an cdp_socket registered with the http client).
|
// an cdp_socket registered with the http client).
|
||||||
// We should continue to run lowPriority tasks, so we
|
// We should continue to run lowPriority tasks, so we
|
||||||
// minimize how long we'll poll for network I/O.
|
// minimize how long we'll poll for network I/O.
|
||||||
const ms_to_wait = @min(200, @min(ms_remaining, ms_to_next_task orelse 200));
|
var ms_to_wait = @min(200, ms_to_next_task orelse 200);
|
||||||
if (try http_client.tick(ms_to_wait) == .cdp_socket) {
|
if (ms_to_wait > 10 and browser.hasBackgroundTasks()) {
|
||||||
|
// if we have bakcground tasks, we don't want ot wait too
|
||||||
|
// long for a message from the client. We want to go back
|
||||||
|
// to the top of the loop and run macrotasks.
|
||||||
|
ms_to_wait = 10;
|
||||||
|
}
|
||||||
|
if (try http_client.tick(@min(ms_remaining, ms_to_wait)) == .cdp_socket) {
|
||||||
// data on a socket we aren't handling, return to caller
|
// data on a socket we aren't handling, return to caller
|
||||||
return .cdp_socket;
|
return .cdp_socket;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ pub fn deinit(self: *Context) void {
|
|||||||
env.isolate.notifyContextDisposed();
|
env.isolate.notifyContextDisposed();
|
||||||
// There can be other tasks associated with this context that we need to
|
// There can be other tasks associated with this context that we need to
|
||||||
// purge while the context is still alive.
|
// purge while the context is still alive.
|
||||||
env.pumpMessageLoop();
|
_ = env.pumpMessageLoop();
|
||||||
v8.v8__MicrotaskQueue__DELETE(self.microtask_queue);
|
v8.v8__MicrotaskQueue__DELETE(self.microtask_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -397,10 +397,23 @@ pub fn pumpMessageLoop(self: *const Env) void {
|
|||||||
|
|
||||||
const isolate = self.isolate.handle;
|
const isolate = self.isolate.handle;
|
||||||
const platform = self.platform.handle;
|
const platform = self.platform.handle;
|
||||||
while (v8.v8__Platform__PumpMessageLoop(platform, isolate, false)) {
|
while (v8.v8__Platform__PumpMessageLoop(platform, isolate, false)) {}
|
||||||
if (comptime IS_DEBUG) {
|
}
|
||||||
log.debug(.browser, "pumpMessageLoop", .{});
|
|
||||||
}
|
pub fn hasBackgroundTasks(self: *const Env) bool {
|
||||||
|
return v8.v8__Isolate__HasPendingBackgroundTasks(self.isolate.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn waitForBackgroundTasks(self: *Env) void {
|
||||||
|
var hs: v8.HandleScope = undefined;
|
||||||
|
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate.handle);
|
||||||
|
defer v8.v8__HandleScope__DESTRUCT(&hs);
|
||||||
|
|
||||||
|
const isolate = self.isolate.handle;
|
||||||
|
const platform = self.platform.handle;
|
||||||
|
while (v8.v8__Isolate__HasPendingBackgroundTasks(isolate)) {
|
||||||
|
_ = v8.v8__Platform__PumpMessageLoop(platform, isolate, true);
|
||||||
|
self.runMicrotasks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ pub fn createTypedArray(self: *const Local, comptime array_type: js.ArrayType, s
|
|||||||
return .init(self, size);
|
return .init(self, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn runMacrotasks(self: *const Local) void {
|
||||||
|
const env = self.ctx.env;
|
||||||
|
env.pumpMessageLoop();
|
||||||
|
env.runMicrotasks(); // macrotasks can cause microtasks to queue
|
||||||
|
}
|
||||||
|
|
||||||
pub fn runMicrotasks(self: *const Local) void {
|
pub fn runMicrotasks(self: *const Local) void {
|
||||||
self.ctx.env.runMicrotasks();
|
self.ctx.env.runMicrotasks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,17 +436,18 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
const browser = &self.cdp.browser;
|
const browser = &self.cdp.browser;
|
||||||
|
const env = &browser.env;
|
||||||
|
|
||||||
// Drain microtasks makes sure we don't have inspector's callback
|
// Drain microtasks makes sure we don't have inspector's callback
|
||||||
// in progress before deinit.
|
// in progress before deinit.
|
||||||
browser.env.runMicrotasks();
|
env.runMicrotasks();
|
||||||
|
|
||||||
// resetContextGroup detach the inspector from all contexts.
|
// resetContextGroup detach the inspector from all contexts.
|
||||||
// It append async tasks, so we make sure we run the message loop
|
// It append async tasks, so we make sure we run the message loop
|
||||||
// before deinit it.
|
// before deinit it.
|
||||||
browser.env.inspector.?.resetContextGroup();
|
env.inspector.?.resetContextGroup();
|
||||||
browser.runMessageLoop();
|
_ = env.pumpMessageLoop();
|
||||||
browser.env.inspector.?.stopSession();
|
env.inspector.?.stopSession();
|
||||||
|
|
||||||
// abort all intercepted requests before closing the sesion/page
|
// abort all intercepted requests before closing the sesion/page
|
||||||
// since some of these might callback into the page/scriptmanager
|
// since some of these might callback into the page/scriptmanager
|
||||||
|
|||||||
Reference in New Issue
Block a user