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: zig-v8:
description: 'zig v8 version to install' description: 'zig v8 version to install'
required: false required: false
default: 'v0.3.0' default: 'v0.3.1'
v8: v8:
description: 'v8 version to install' description: 'v8 version to install'
required: false required: false

View File

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

View File

@@ -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/refs/tags/v0.3.1.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" = .{

View File

@@ -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();
} }

View File

@@ -314,12 +314,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 });
} }
} }

View File

@@ -873,8 +873,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 });
}; };

View File

@@ -258,7 +258,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;
@@ -268,6 +268,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;
@@ -293,8 +301,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 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 // data on a socket we aren't handling, return to caller
return .cdp_socket; return .cdp_socket;
} }

View File

@@ -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);
} }

View File

@@ -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();
} }
} }

View File

@@ -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();
} }

View File

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