Merge pull request #1651 from lightpanda-io/more_pump_message_loop

Run the MessageLoop [a lot] more.
This commit is contained in:
Karl Seguin
2026-02-26 11:35:11 +08:00
committed by GitHub
11 changed files with 73 additions and 25 deletions

View File

@@ -13,7 +13,7 @@ inputs:
zig-v8:
description: 'zig v8 version to install'
required: false
default: 'v0.3.0'
default: 'v0.3.1'
v8:
description: 'v8 version to install'
required: false

View File

@@ -3,7 +3,7 @@ FROM debian:stable-slim
ARG MINISIG=0.12
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
ARG V8=14.0.365.4
ARG ZIG_V8=v0.3.0
ARG ZIG_V8=v0.3.1
ARG TARGETPLATFORM
RUN apt-get update -yq && \

View File

@@ -6,8 +6,9 @@
.minimum_zig_version = "0.15.2",
.dependencies = .{
.v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.0.tar.gz",
.hash = "v8-0.0.0-xddH69R6BADRXsnhjA8wNnfKfLQACF1I7CSTZvsMAvp8",
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.1.tar.gz",
.hash = "v8-0.0.0-xddH64J7BAC81mkf6G9RbEJxS-W3TIRl5iFnShwbqCqy",
},
//.v8 = .{ .path = "../zig-v8-fork" },
.@"boringssl-zig" = .{

View File

@@ -92,10 +92,24 @@ pub fn runMicrotasks(self: *Browser) void {
}
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 {
self.env.pumpMessageLoop();
pub fn hasBackgroundTasks(self: *Browser) bool {
return self.env.hasBackgroundTasks();
}
pub fn waitForBackgroundTasks(self: *Browser) void {
self.env.waitForBackgroundTasks();
}
pub fn runIdleTasks(self: *const Browser) void {
self.env.runIdleTasks();
}

View File

@@ -314,12 +314,12 @@ pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void {
if (comptime builtin.is_test == false) {
// HTML test runner manually calls these as necessary
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));
b.runMessageLoop();
return 250;
b.runIdleTasks();
return 200;
}
}.runMessageLoop, 250, .{ .name = "page.messageLoop" });
}.runIdleTasks, 200, .{ .name = "page.runIdleTasks", .low_priority = true });
}
}

View File

@@ -873,8 +873,7 @@ pub const Script = struct {
}
defer {
// We should run microtasks even if script execution fails.
local.runMicrotasks();
local.runMacrotasks(); // also runs microtasks
_ = page.js.scheduler.run() catch |err| {
log.err(.page, "scheduler", .{ .err = err });
};

View File

@@ -258,7 +258,7 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
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 (comptime builtin.is_test) {
return .done;
@@ -268,6 +268,14 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
// background jobs.
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
// scheduled tasks, we're done.
return .done;
@@ -293,8 +301,14 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
// an cdp_socket registered with the http client).
// We should continue to run lowPriority tasks, so we
// 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));
if (try http_client.tick(ms_to_wait) == .cdp_socket) {
var ms_to_wait = @min(200, ms_to_next_task orelse 200);
if (ms_to_wait > 10 and browser.hasBackgroundTasks()) {
// if we have background tasks, we don't want to 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
return .cdp_socket;
}

View File

@@ -237,7 +237,7 @@ pub fn deinit(self: *Context) void {
env.isolate.notifyContextDisposed();
// There can be other tasks associated with this context that we need to
// purge while the context is still alive.
env.pumpMessageLoop();
_ = env.pumpMessageLoop();
v8.v8__MicrotaskQueue__DELETE(self.microtask_queue);
}

View File

@@ -397,10 +397,23 @@ pub fn pumpMessageLoop(self: *const Env) void {
const isolate = self.isolate.handle;
const platform = self.platform.handle;
while (v8.v8__Platform__PumpMessageLoop(platform, isolate, false)) {
if (comptime IS_DEBUG) {
log.debug(.browser, "pumpMessageLoop", .{});
}
while (v8.v8__Platform__PumpMessageLoop(platform, isolate, false)) {}
}
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();
}
}

View File

@@ -82,6 +82,12 @@ pub fn createTypedArray(self: *const Local, comptime array_type: js.ArrayType, s
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 {
self.ctx.env.runMicrotasks();
}

View File

@@ -436,17 +436,18 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn deinit(self: *Self) void {
const browser = &self.cdp.browser;
const env = &browser.env;
// Drain microtasks makes sure we don't have inspector's callback
// in progress before deinit.
browser.env.runMicrotasks();
env.runMicrotasks();
// resetContextGroup detach the inspector from all contexts.
// It append async tasks, so we make sure we run the message loop
// before deinit it.
browser.env.inspector.?.resetContextGroup();
browser.runMessageLoop();
browser.env.inspector.?.stopSession();
env.inspector.?.resetContextGroup();
_ = env.pumpMessageLoop();
env.inspector.?.stopSession();
// abort all intercepted requests before closing the sesion/page
// since some of these might callback into the page/scriptmanager