diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 797d88c5..329a8728 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -873,7 +873,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { self.scope = Scope{ .handle_scope = handle_scope, .arena = self.scope_arena.allocator(), - .call_arena = self.call_arena.allocator(), + .call_arena = self.scope_arena.allocator(), }; _ = try self._mapZigInstanceToJs(self.context.getGlobal(), global); } @@ -1128,7 +1128,19 @@ pub fn Env(comptime S: type, comptime types: anytype) type { inline for (fields, 0..) |f, i| { js_args[i] = try executor.zigValueToJs(@field(aargs, f.name)); } - _ = self.func.castToFunction().call(executor.context, js_this, &js_args); + + const result = self.func.castToFunction().call(executor.context, js_this, &js_args); + if (result == null) { + return error.JSExecCallback; + } + } + + // debug/helper to print the source of the JS callback + fn printFunc(self: *const @This()) !void { + const executor = self.executor; + const value = self.func.castToFunction().toValue(); + const src = try valueToString(executor.call_arena.allocator(), value, executor.isolate, executor.context); + std.debug.print("{s}\n", .{src}); } }; @@ -1405,7 +1417,8 @@ fn Caller(comptime E: type) type { } fn deinit(self: *Self) void { - _ = self.executor.call_arena.reset(.{ .retain_with_limit = 4096 }); + _ = self; + // _ = self.executor.call_arena.reset(.{ .retain_with_limit = 4096 }); } fn constructor(self: *Self, comptime named_function: anytype, info: v8.FunctionCallbackInfo) !void { diff --git a/src/runtime/loop.zig b/src/runtime/loop.zig index db23d10b..797b371e 100644 --- a/src/runtime/loop.zig +++ b/src/runtime/loop.zig @@ -17,6 +17,7 @@ // along with this program. If not, see . const std = @import("std"); +const builtin = @import("builtin"); const MemoryPool = std.heap.MemoryPool; pub const IO = @import("tigerbeetle-io").IO; @@ -35,8 +36,12 @@ const log = std.log.scoped(.loop); pub const Loop = struct { alloc: std.mem.Allocator, // TODO: unmanaged version ? io: IO, + + // both events_nb are used to track how many callbacks are to be called. + // We use these counters to wait until all the events are finished. js_events_nb: usize, zig_events_nb: usize, + cbk_error: bool = false, // js_ctx_id is incremented each time the loop is reset for JS. @@ -51,6 +56,11 @@ pub const Loop = struct { // This is a weak way to cancel all future Zig callbacks. zig_ctx_id: u32 = 0, + // The MacOS event loop doesn't support cancellation. We use this to track + // cancellation ids and, on the timeout callback, we can can check here + // to see if it's been cancelled. + cancelled: std.AutoHashMapUnmanaged(usize, void), + cancel_pool: MemoryPool(ContextCancel), timeout_pool: MemoryPool(ContextTimeout), event_callback_pool: MemoryPool(EventCallbackContext), @@ -65,6 +75,7 @@ pub const Loop = struct { pub fn init(alloc: std.mem.Allocator) !Self { return Self{ .alloc = alloc, + .cancelled = .{}, .io = try IO.init(32, 0), .js_events_nb = 0, .zig_events_nb = 0, @@ -75,6 +86,12 @@ pub const Loop = struct { } pub fn deinit(self: *Self) void { + // first disable callbacks for existing events. + // We don't want a callback re-create a setTimeout, it could create an + // infinite loop on wait for events. + self.resetJS(); + self.resetZig(); + // run tail events. We do run the tail events to ensure all the // contexts are correcly free. while (self.eventsNb(.js) > 0 or self.eventsNb(.zig) > 0) { @@ -83,11 +100,14 @@ pub const Loop = struct { break; }; } - self.cancelAll(); + if (comptime CANCEL_SUPPORTED) { + self.io.cancel_all(); + } self.io.deinit(); self.cancel_pool.deinit(); self.timeout_pool.deinit(); self.event_callback_pool.deinit(); + self.cancelled.deinit(self.alloc); } // Retrieve all registred I/O events completed by OS kernel, @@ -131,9 +151,6 @@ pub const Loop = struct { fn eventsNb(self: *Self, comptime event: Event) usize { return @atomicLoad(usize, self.eventsPtr(event), .seq_cst); } - fn resetEvents(self: *Self, comptime event: Event) void { - @atomicStore(usize, self.eventsPtr(event), 0, .unordered); - } // JS callbacks APIs // ----------------- @@ -158,6 +175,12 @@ pub const Loop = struct { loop.alloc.destroy(completion); } + if (comptime CANCEL_SUPPORTED == false) { + if (loop.cancelled.remove(@intFromPtr(completion))) { + return; + } + } + // If the loop's context id has changed, don't call the js callback // function. The callback's memory has already be cleaned and the // events nb reset. @@ -175,7 +198,7 @@ pub const Loop = struct { // js callback if (ctx.js_cbk) |*js_cbk| { js_cbk.call(null) catch { - ctx.loop.cbk_error = true; + loop.cbk_error = true; }; } } @@ -234,19 +257,26 @@ pub const Loop = struct { // js callback if (ctx.js_cbk) |*js_cbk| { js_cbk.call(null) catch { - ctx.loop.cbk_error = true; + loop.cbk_error = true; }; } } pub fn cancel(self: *Self, id: usize, js_cbk: ?JSCallback) !void { - if (IO.supports_cancel == false) { + const alloc = self.alloc; + if (comptime CANCEL_SUPPORTED == false) { + try self.cancelled.put(alloc, id, {}); + if (js_cbk) |cbk| { + cbk.call(null) catch { + self.cbk_error = true; + }; + } return; } const comp_cancel: *IO.Completion = @ptrFromInt(id); - const completion = try self.alloc.create(Completion); - errdefer self.alloc.destroy(completion); + const completion = try alloc.create(Completion); + errdefer alloc.destroy(completion); completion.* = undefined; const ctx = self.alloc.create(ContextCancel) catch unreachable; @@ -260,18 +290,17 @@ pub const Loop = struct { self.io.cancel_one(*ContextCancel, ctx, cancelCallback, completion, comp_cancel); } - fn cancelAll(self: *Self) void { - self.resetEvents(.js); - self.resetEvents(.zig); - self.io.cancel_all(); - } - // Reset all existing JS callbacks. + // The existing events will happen and their memory will be cleanup but the + // corresponding callbacks will not be called. pub fn resetJS(self: *Self) void { self.js_ctx_id += 1; + self.cancelled.clearRetainingCapacity(); } // Reset all existing Zig callbacks. + // The existing events will happen and their memory will be cleanup but the + // corresponding callbacks will not be called. pub fn resetZig(self: *Self) void { self.zig_ctx_id += 1; } @@ -365,6 +394,7 @@ pub const Loop = struct { const ContextZigTimeout = struct { loop: *Self, zig_ctx_id: u32, + context: *anyopaque, callback: *const fn ( context: ?*anyopaque, @@ -431,3 +461,9 @@ const EventCallbackContext = struct { ctx: *anyopaque, loop: *Loop, }; + +const CANCEL_SUPPORTED = switch (builtin.target.os.tag) { + .linux => true, + .macos, .tvos, .watchos, .ios => false, + else => @compileError("IO is not supported for platform"), +}; diff --git a/src/wpt/run.zig b/src/wpt/run.zig index bb017f89..6823aaf9 100644 --- a/src/wpt/run.zig +++ b/src/wpt/run.zig @@ -35,7 +35,7 @@ pub fn run(arena: Allocator, comptime dir: []const u8, f: []const u8, loader: *F const html = blk: { const file = try std.fs.cwd().openFile(f, .{}); defer file.close(); - break :blk try file.readToEndAlloc(arena, 16 * 1024); + break :blk try file.readToEndAlloc(arena, 128 * 1024); }; const dirname = fspath.dirname(f[dir.len..]) orelse unreachable;