Skip to content

Commit 8fd9176

Browse files
Merge pull request #291 from lightpanda-io/pumpmessageloop
micro tasks: run micro tasks on isolate every 1 millisecond
2 parents d959ef8 + b037202 commit 8fd9176

File tree

2 files changed

+121
-12
lines changed

2 files changed

+121
-12
lines changed

src/engines/v8/v8.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ pub const VM = struct {
8282
v8.deinitV8Platform();
8383
self.platform.deinit();
8484
}
85+
86+
pub fn pumpMessageLoop(self: *const VM, env: *const Env, wait: bool) bool {
87+
log.debug("pumpMessageLoop", .{});
88+
return self.platform.pumpMessageLoop(env.isolate, wait);
89+
}
8590
};
8691

8792
pub const Env = struct {
@@ -137,9 +142,11 @@ pub const Env = struct {
137142
.globals = globals,
138143
};
139144
NativeContext.init(&self.nat_ctx, alloc, loop, userctx);
145+
self.startMicrotasks();
140146
}
141147

142148
pub fn deinit(self: *Env) void {
149+
self.stopMicrotasks();
143150

144151
// v8 values
145152
// ---------
@@ -174,6 +181,21 @@ pub const Env = struct {
174181
self.nat_ctx.userctx = userctx;
175182
}
176183

184+
pub fn runMicrotasks(self: *const Env) void {
185+
self.isolate.performMicrotasksCheckpoint();
186+
}
187+
188+
fn startMicrotasks(self: *Env) void {
189+
self.runMicrotasks();
190+
self.nat_ctx.loop.zigTimeout(1 * std.time.ns_per_ms, *Env, self, startMicrotasks);
191+
}
192+
193+
fn stopMicrotasks(self: *const Env) void {
194+
// We force a loop reset for all zig callback.
195+
// The goal is to stop the callbacks used for the run micro tasks.
196+
self.nat_ctx.loop.resetZig();
197+
}
198+
177199
// load user-defined Types into Javascript environement
178200
pub fn load(self: *Env, js_types: []usize) anyerror!void {
179201
var tpls: [gen.Types.len]TPL = undefined;

src/loop.zig

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,17 @@ pub const SingleThreaded = struct {
4343
events_nb: *usize,
4444
cbk_error: bool = false,
4545

46-
// ctx_id is incremented each time the loop is reset.
47-
// All context are
48-
ctx_id: u32 = 0,
46+
// js_ctx_id is incremented each time the loop is reset for JS.
47+
// All JS callbacks store an initial js_ctx_id and compare before execution.
48+
// If a ctx is outdated, the callback is ignored.
49+
// This is a weak way to cancel all future JS callbacks.
50+
js_ctx_id: u32 = 0,
51+
52+
// zig_ctx_id is incremented each time the loop is reset for Zig.
53+
// All Zig callbacks store an initial zig_ctx_id and compare before execution.
54+
// If a ctx is outdated, the callback is ignored.
55+
// This is a weak way to cancel all future Zig callbacks.
56+
zig_ctx_id: u32 = 0,
4957

5058
const Self = @This();
5159
pub const Completion = IO.Completion;
@@ -120,7 +128,7 @@ pub const SingleThreaded = struct {
120128
const ContextTimeout = struct {
121129
loop: *Self,
122130
js_cbk: ?JSCallback,
123-
ctx_id: u32,
131+
js_ctx_id: u32,
124132
};
125133

126134
fn timeoutCallback(
@@ -133,7 +141,7 @@ pub const SingleThreaded = struct {
133141
// If the loop's context id has changed, don't call the js callback
134142
// function. The callback's memory has already be cleaned and the
135143
// events nb reset.
136-
if (ctx.ctx_id != ctx.loop.ctx_id) return;
144+
if (ctx.js_ctx_id != ctx.loop.js_ctx_id) return;
137145

138146
const old_events_nb = ctx.loop.removeEvent();
139147
if (builtin.is_test) {
@@ -165,7 +173,7 @@ pub const SingleThreaded = struct {
165173
ctx.* = ContextTimeout{
166174
.loop = self,
167175
.js_cbk = js_cbk,
168-
.ctx_id = self.ctx_id,
176+
.js_ctx_id = self.js_ctx_id,
169177
};
170178
const old_events_nb = self.addEvent();
171179
self.io.timeout(*ContextTimeout, ctx, timeoutCallback, completion, nanoseconds);
@@ -179,7 +187,7 @@ pub const SingleThreaded = struct {
179187
const ContextCancel = struct {
180188
loop: *Self,
181189
js_cbk: ?JSCallback,
182-
ctx_id: u32,
190+
js_ctx_id: u32,
183191
};
184192

185193
fn cancelCallback(
@@ -192,7 +200,7 @@ pub const SingleThreaded = struct {
192200
// If the loop's context id has changed, don't call the js callback
193201
// function. The callback's memory has already be cleaned and the
194202
// events nb reset.
195-
if (ctx.ctx_id != ctx.loop.ctx_id) return;
203+
if (ctx.js_ctx_id != ctx.loop.js_ctx_id) return;
196204

197205
const old_events_nb = ctx.loop.removeEvent();
198206
if (builtin.is_test) {
@@ -226,7 +234,7 @@ pub const SingleThreaded = struct {
226234
ctx.* = ContextCancel{
227235
.loop = self,
228236
.js_cbk = js_cbk,
229-
.ctx_id = self.ctx_id,
237+
.js_ctx_id = self.js_ctx_id,
230238
};
231239

232240
const old_events_nb = self.addEvent();
@@ -241,9 +249,15 @@ pub const SingleThreaded = struct {
241249
self.io.cancel_all();
242250
}
243251

244-
// Reset all existing callbacks.
245-
pub fn reset(self: *Self) void {
246-
self.ctx_id += 1;
252+
// Reset all existing JS callbacks.
253+
pub fn resetJS(self: *Self) void {
254+
self.js_ctx_id += 1;
255+
self.resetEvents();
256+
}
257+
258+
// Reset all existing Zig callbacks.
259+
pub fn resetZig(self: *Self) void {
260+
self.zig_ctx_id += 1;
247261
self.resetEvents();
248262
}
249263

@@ -324,4 +338,77 @@ pub const SingleThreaded = struct {
324338
report("recv done, remaining events: {d}", .{old_events_nb - 1});
325339
}
326340
}
341+
342+
// Zig timeout
343+
344+
const ContextZigTimeout = struct {
345+
loop: *Self,
346+
zig_ctx_id: u32,
347+
348+
context: *anyopaque,
349+
callback: *const fn (
350+
context: ?*anyopaque,
351+
) void,
352+
};
353+
354+
fn zigTimeoutCallback(
355+
ctx: *ContextZigTimeout,
356+
completion: *IO.Completion,
357+
result: IO.TimeoutError!void,
358+
) void {
359+
defer ctx.loop.freeCbk(completion, ctx);
360+
361+
// If the loop's context id has changed, don't call the js callback
362+
// function. The callback's memory has already be cleaned and the
363+
// events nb reset.
364+
if (ctx.zig_ctx_id != ctx.loop.zig_ctx_id) return;
365+
366+
// We don't remove event here b/c we don't want the main loop to wait for
367+
// the timeout is done.
368+
// This is mainly due b/c the usage of zigTimeout is used to process
369+
// background tasks.
370+
//_ = ctx.loop.removeEvent();
371+
372+
result catch |err| {
373+
switch (err) {
374+
error.Canceled => {},
375+
else => log.err("zig timeout callback: {any}", .{err}),
376+
}
377+
return;
378+
};
379+
380+
// callback
381+
ctx.callback(ctx.context);
382+
}
383+
384+
// zigTimeout performs a timeout but the callback is a zig function.
385+
pub fn zigTimeout(
386+
self: *Self,
387+
nanoseconds: u63,
388+
comptime Context: type,
389+
context: Context,
390+
comptime callback: fn (context: Context) void,
391+
) void {
392+
const completion = self.alloc.create(IO.Completion) catch unreachable;
393+
completion.* = undefined;
394+
const ctxtimeout = self.alloc.create(ContextZigTimeout) catch unreachable;
395+
ctxtimeout.* = ContextZigTimeout{
396+
.loop = self,
397+
.zig_ctx_id = self.zig_ctx_id,
398+
.context = context,
399+
.callback = struct {
400+
fn wrapper(ctx: ?*anyopaque) void {
401+
callback(@ptrCast(@alignCast(ctx)));
402+
}
403+
}.wrapper,
404+
};
405+
406+
// We don't add event here b/c we don't want the main loop to wait for
407+
// the timeout is done.
408+
// This is mainly due b/c the usage of zigTimeout is used to process
409+
// background tasks.
410+
// _ = self.addEvent();
411+
412+
self.io.timeout(*ContextZigTimeout, ctxtimeout, zigTimeoutCallback, completion, nanoseconds);
413+
}
327414
};

0 commit comments

Comments
 (0)