mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Allow [schedule] tasks to have finalizers
There's no guarantee that a task will ever be run. A page can be shutdown by the user or timeout or an error. Scheduler cleanup relies on the underlying page.arena. This forces all tasks to rely on the page.arena as they have no way to clean themselves. This commit allows tasks to register a finalizer which is guaranteed to be called when the scheduler is shutdown. The window ScheduleCallback, PostMessageCallback now use an arena from the ArenaPool rather than the page.arena and use the task finalizer to ensure the arena is released on shutdown.
This commit is contained in:
@@ -234,6 +234,10 @@ pub fn deinit(self: *Page) void {
|
|||||||
// stats.print(&stream) catch unreachable;
|
// stats.print(&stream) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This can release JS objects, so we need to do this while the js.Context
|
||||||
|
// is still around.
|
||||||
|
self.scheduler.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
// some MicroTasks might be referencing the page, we need to drain it while
|
// some MicroTasks might be referencing the page, we need to drain it while
|
||||||
// the page still exists
|
// the page still exists
|
||||||
@@ -266,6 +270,8 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
const browser = self._session.browser;
|
const browser = self._session.browser;
|
||||||
|
|
||||||
if (comptime initializing == false) {
|
if (comptime initializing == false) {
|
||||||
|
self.scheduler.deinit();
|
||||||
|
|
||||||
browser.env.destroyContext(self.js);
|
browser.env.destroyContext(self.js);
|
||||||
|
|
||||||
// removing a context can trigger finalizers, so we can only check for
|
// removing a context can trigger finalizers, so we can only check for
|
||||||
@@ -281,7 +287,6 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
// We force a garbage collection between page navigations to keep v8
|
// We force a garbage collection between page navigations to keep v8
|
||||||
// memory usage as low as possible.
|
// memory usage as low as possible.
|
||||||
browser.env.memoryPressureNotification(.moderate);
|
browser.env.memoryPressureNotification(.moderate);
|
||||||
|
|
||||||
self._script_manager.shutdown = true;
|
self._script_manager.shutdown = true;
|
||||||
browser.http_client.abort();
|
browser.http_client.abort();
|
||||||
self._script_manager.deinit();
|
self._script_manager.deinit();
|
||||||
|
|||||||
@@ -47,9 +47,15 @@ pub fn init(allocator: std.mem.Allocator) Scheduler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Scheduler) void {
|
||||||
|
finalizeTasks(&self.low_priority);
|
||||||
|
finalizeTasks(&self.high_priority);
|
||||||
|
}
|
||||||
|
|
||||||
const AddOpts = struct {
|
const AddOpts = struct {
|
||||||
name: []const u8 = "",
|
name: []const u8 = "",
|
||||||
low_priority: bool = false,
|
low_priority: bool = false,
|
||||||
|
finalizer: ?Finalizer = null,
|
||||||
};
|
};
|
||||||
pub fn add(self: *Scheduler, ctx: *anyopaque, cb: Callback, run_in_ms: u32, opts: AddOpts) !void {
|
pub fn add(self: *Scheduler, ctx: *anyopaque, cb: Callback, run_in_ms: u32, opts: AddOpts) !void {
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
@@ -63,6 +69,7 @@ pub fn add(self: *Scheduler, ctx: *anyopaque, cb: Callback, run_in_ms: u32, opts
|
|||||||
.callback = cb,
|
.callback = cb,
|
||||||
.sequence = seq,
|
.sequence = seq,
|
||||||
.name = opts.name,
|
.name = opts.name,
|
||||||
|
.finalizer = opts.finalizer,
|
||||||
.run_at = milliTimestamp(.monotonic) + run_in_ms,
|
.run_at = milliTimestamp(.monotonic) + run_in_ms,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -105,12 +112,23 @@ fn runQueue(self: *Scheduler, queue: *Queue) !?u64 {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalizeTasks(queue: *Queue) void {
|
||||||
|
var it = queue.iterator();
|
||||||
|
while (it.next()) |t| {
|
||||||
|
if (t.finalizer) |func| {
|
||||||
|
func(t.ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Task = struct {
|
const Task = struct {
|
||||||
run_at: u64,
|
run_at: u64,
|
||||||
sequence: u64,
|
sequence: u64,
|
||||||
ctx: *anyopaque,
|
ctx: *anyopaque,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
callback: Callback,
|
callback: Callback,
|
||||||
|
finalizer: ?Finalizer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Callback = *const fn (ctx: *anyopaque) anyerror!?u32;
|
const Callback = *const fn (ctx: *anyopaque) anyerror!?u32;
|
||||||
|
const Finalizer = *const fn (ctx: *anyopaque) void;
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ const CSSStyleProperties = @import("css/CSSStyleProperties.zig");
|
|||||||
const CustomElementRegistry = @import("CustomElementRegistry.zig");
|
const CustomElementRegistry = @import("CustomElementRegistry.zig");
|
||||||
const Selection = @import("Selection.zig");
|
const Selection = @import("Selection.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Window = @This();
|
const Window = @This();
|
||||||
|
|
||||||
_proto: *EventTarget,
|
_proto: *EventTarget,
|
||||||
@@ -353,18 +355,21 @@ pub fn postMessage(self: *Window, message: js.Value.Global, target_origin: ?[]co
|
|||||||
_ = target_origin;
|
_ = target_origin;
|
||||||
|
|
||||||
// postMessage queues a task (not a microtask), so use the scheduler
|
// postMessage queues a task (not a microtask), so use the scheduler
|
||||||
const origin = try self._location.getOrigin(page);
|
const arena = try page.getArena(.{ .debug = "Window.schedule" });
|
||||||
const callback = try page._factory.create(PostMessageCallback{
|
errdefer page.releaseArena(arena);
|
||||||
.window = self,
|
|
||||||
.message = message,
|
|
||||||
.origin = try page.arena.dupe(u8, origin),
|
|
||||||
.page = page,
|
|
||||||
});
|
|
||||||
errdefer page._factory.destroy(callback);
|
|
||||||
|
|
||||||
|
const origin = try self._location.getOrigin(page);
|
||||||
|
const callback = try arena.create(PostMessageCallback);
|
||||||
|
callback.* = .{
|
||||||
|
.page = page,
|
||||||
|
.arena = arena,
|
||||||
|
.message = message,
|
||||||
|
.origin = try arena.dupe(u8, origin),
|
||||||
|
};
|
||||||
try page.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
try page.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||||
.name = "postMessage",
|
.name = "postMessage",
|
||||||
.low_priority = false,
|
.low_priority = false,
|
||||||
|
.finalizer = PostMessageCallback.cancelled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,13 +513,16 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
|
|||||||
return error.TooManyTimeout;
|
return error.TooManyTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const arena = try page.getArena(.{ .debug = "Window.schedule" });
|
||||||
|
errdefer page.releaseArena(arena);
|
||||||
|
|
||||||
const timer_id = self._timer_id +% 1;
|
const timer_id = self._timer_id +% 1;
|
||||||
self._timer_id = timer_id;
|
self._timer_id = timer_id;
|
||||||
|
|
||||||
const params = opts.params;
|
const params = opts.params;
|
||||||
var persisted_params: []js.Value.Temp = &.{};
|
var persisted_params: []js.Value.Temp = &.{};
|
||||||
if (params.len > 0) {
|
if (params.len > 0) {
|
||||||
persisted_params = try page.arena.dupe(js.Value.Temp, params);
|
persisted_params = try arena.dupe(js.Value.Temp, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gop = try self._timers.getOrPut(page.arena, timer_id);
|
const gop = try self._timers.getOrPut(page.arena, timer_id);
|
||||||
@@ -524,21 +532,23 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
|
|||||||
}
|
}
|
||||||
errdefer _ = self._timers.remove(timer_id);
|
errdefer _ = self._timers.remove(timer_id);
|
||||||
|
|
||||||
const callback = try page._factory.create(ScheduleCallback{
|
const callback = try arena.create(ScheduleCallback);
|
||||||
|
callback.* = .{
|
||||||
.cb = cb,
|
.cb = cb,
|
||||||
.page = page,
|
.page = page,
|
||||||
|
.arena = arena,
|
||||||
.mode = opts.mode,
|
.mode = opts.mode,
|
||||||
.name = opts.name,
|
.name = opts.name,
|
||||||
.timer_id = timer_id,
|
.timer_id = timer_id,
|
||||||
.params = persisted_params,
|
.params = persisted_params,
|
||||||
.repeat_ms = if (opts.repeat) if (delay_ms == 0) 1 else delay_ms else null,
|
.repeat_ms = if (opts.repeat) if (delay_ms == 0) 1 else delay_ms else null,
|
||||||
});
|
};
|
||||||
gop.value_ptr.* = callback;
|
gop.value_ptr.* = callback;
|
||||||
errdefer page._factory.destroy(callback);
|
|
||||||
|
|
||||||
try page.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{
|
try page.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{
|
||||||
.name = opts.name,
|
.name = opts.name,
|
||||||
.low_priority = opts.low_priority,
|
.low_priority = opts.low_priority,
|
||||||
|
.finalizer = ScheduleCallback.cancelled,
|
||||||
});
|
});
|
||||||
|
|
||||||
return timer_id;
|
return timer_id;
|
||||||
@@ -556,13 +566,11 @@ const ScheduleCallback = struct {
|
|||||||
|
|
||||||
cb: js.Function.Temp,
|
cb: js.Function.Temp,
|
||||||
|
|
||||||
page: *Page,
|
|
||||||
|
|
||||||
params: []const js.Value.Temp,
|
|
||||||
|
|
||||||
removed: bool = false,
|
|
||||||
|
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
page: *Page,
|
||||||
|
arena: Allocator,
|
||||||
|
removed: bool = false,
|
||||||
|
params: []const js.Value.Temp,
|
||||||
|
|
||||||
const Mode = enum {
|
const Mode = enum {
|
||||||
idle,
|
idle,
|
||||||
@@ -570,19 +578,26 @@ const ScheduleCallback = struct {
|
|||||||
animation_frame,
|
animation_frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn cancelled(ctx: *anyopaque) void {
|
||||||
|
var self: *ScheduleCallback = @ptrCast(@alignCast(ctx));
|
||||||
|
self.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
fn deinit(self: *ScheduleCallback) void {
|
fn deinit(self: *ScheduleCallback) void {
|
||||||
self.page.js.release(self.cb);
|
self.page.js.release(self.cb);
|
||||||
for (self.params) |param| {
|
for (self.params) |param| {
|
||||||
self.page.js.release(param);
|
self.page.js.release(param);
|
||||||
}
|
}
|
||||||
self.page._factory.destroy(self);
|
self.page.releaseArena(self.arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *ScheduleCallback = @ptrCast(@alignCast(ctx));
|
const self: *ScheduleCallback = @ptrCast(@alignCast(ctx));
|
||||||
const page = self.page;
|
const page = self.page;
|
||||||
|
const window = page.window;
|
||||||
|
|
||||||
if (self.removed) {
|
if (self.removed) {
|
||||||
_ = page.window._timers.remove(self.timer_id);
|
_ = window._timers.remove(self.timer_id);
|
||||||
self.deinit();
|
self.deinit();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -599,7 +614,7 @@ const ScheduleCallback = struct {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
.animation_frame => {
|
.animation_frame => {
|
||||||
ls.toLocal(self.cb).call(void, .{page.window._performance.now()}) catch |err| {
|
ls.toLocal(self.cb).call(void, .{window._performance.now()}) catch |err| {
|
||||||
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -614,35 +629,43 @@ const ScheduleCallback = struct {
|
|||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
defer self.deinit();
|
defer self.deinit();
|
||||||
_ = page.window._timers.remove(self.timer_id);
|
_ = window._timers.remove(self.timer_id);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostMessageCallback = struct {
|
const PostMessageCallback = struct {
|
||||||
window: *Window,
|
|
||||||
message: js.Value.Global,
|
|
||||||
origin: []const u8,
|
|
||||||
page: *Page,
|
page: *Page,
|
||||||
|
arena: Allocator,
|
||||||
|
origin: []const u8,
|
||||||
|
message: js.Value.Global,
|
||||||
|
|
||||||
fn deinit(self: *PostMessageCallback) void {
|
fn deinit(self: *PostMessageCallback) void {
|
||||||
self.page._factory.destroy(self);
|
self.page.releaseArena(self.arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancelled(ctx: *anyopaque) void {
|
||||||
|
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||||
|
self.page.releaseArena(self.arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||||
defer self.deinit();
|
defer self.deinit();
|
||||||
|
|
||||||
|
const page = self.page;
|
||||||
|
const window = page.window;
|
||||||
|
|
||||||
const message_event = try MessageEvent.initTrusted("message", .{
|
const message_event = try MessageEvent.initTrusted("message", .{
|
||||||
.data = self.message,
|
.data = self.message,
|
||||||
.origin = self.origin,
|
.origin = self.origin,
|
||||||
.source = self.window,
|
.source = window,
|
||||||
.bubbles = false,
|
.bubbles = false,
|
||||||
.cancelable = false,
|
.cancelable = false,
|
||||||
}, self.page);
|
}, page);
|
||||||
|
|
||||||
const event = message_event.asEvent();
|
const event = message_event.asEvent();
|
||||||
try self.page._event_manager.dispatch(self.window.asEventTarget(), event);
|
try page._event_manager.dispatch(window.asEventTarget(), event);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user