More aggressive timer cleanup

When a timer is cleared, e.g. clearInterval, we flag the task are deleted and
maintain the entry in window._timers. When run, the task is ignored and deleted
from _timers.

This can result in prematurely rejecting timers due to `TooManyTimeout`. One
pattern I've seen is a RAF associated with an element where the RAF is cleared
(cancelAnimationFrame) if already registered. This can quickly result in
TooManyTimers.

This commit removes the timer from _timers as soon as it's canceled. It doesn't
fully eliminate the chance of TooManyTimeout, but it does reduce it.
This commit is contained in:
Karl Seguin
2026-03-21 11:38:16 +08:00
parent b4b7a7d58a
commit 8526770e9f

View File

@@ -285,23 +285,23 @@ pub fn queueMicrotask(_: *Window, cb: js.Function, page: *Page) void {
} }
pub fn clearTimeout(self: *Window, id: u32) void { pub fn clearTimeout(self: *Window, id: u32) void {
var sc = self._timers.get(id) orelse return; var sc = self._timers.fetchRemove(id) orelse return;
sc.removed = true; sc.value.removed = true;
} }
pub fn clearInterval(self: *Window, id: u32) void { pub fn clearInterval(self: *Window, id: u32) void {
var sc = self._timers.get(id) orelse return; var sc = self._timers.fetchRemove(id) orelse return;
sc.removed = true; sc.value.removed = true;
} }
pub fn clearImmediate(self: *Window, id: u32) void { pub fn clearImmediate(self: *Window, id: u32) void {
var sc = self._timers.get(id) orelse return; var sc = self._timers.fetchRemove(id) orelse return;
sc.removed = true; sc.value.removed = true;
} }
pub fn cancelAnimationFrame(self: *Window, id: u32) void { pub fn cancelAnimationFrame(self: *Window, id: u32) void {
var sc = self._timers.get(id) orelse return; var sc = self._timers.fetchRemove(id) orelse return;
sc.removed = true; sc.value.removed = true;
} }
const RequestIdleCallbackOpts = struct { const RequestIdleCallbackOpts = struct {
@@ -319,8 +319,8 @@ pub fn requestIdleCallback(self: *Window, cb: js.Function.Temp, opts_: ?RequestI
} }
pub fn cancelIdleCallback(self: *Window, id: u32) void { pub fn cancelIdleCallback(self: *Window, id: u32) void {
var sc = self._timers.get(id) orelse return; var sc = self._timers.fetchRemove(id) orelse return;
sc.removed = true; sc.value.removed = true;
} }
pub fn reportError(self: *Window, err: js.Value, page: *Page) !void { pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
@@ -704,7 +704,6 @@ const ScheduleCallback = struct {
const window = page.window; const window = page.window;
if (self.removed) { if (self.removed) {
_ = window._timers.remove(self.timer_id);
self.deinit(); self.deinit();
return null; return null;
} }