diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig index 3a4fc3be2d..0515da32d3 100644 --- a/src/bake/DevServer.zig +++ b/src/bake/DevServer.zig @@ -549,7 +549,7 @@ pub fn init(options: Options) bun.JSOOM!*DevServer { dev.framework = dev.framework.resolve(&dev.server_transpiler.resolver, &dev.client_transpiler.resolver, options.arena) catch { if (dev.framework.is_built_in_react) try bake.Framework.addReactInstallCommandNote(&dev.log); - return global.throwValue(dev.log.toJSAggregateError(global, bun.String.static("Framework is missing required files!"))); + return global.throwValue(try dev.log.toJSAggregateError(global, bun.String.static("Framework is missing required files!"))); }; errdefer dev.route_lookup.clearAndFree(allocator); diff --git a/src/bun.js/BuildMessage.zig b/src/bun.js/BuildMessage.zig index 71a014b6c7..e29105f147 100644 --- a/src/bun.js/BuildMessage.zig +++ b/src/bun.js/BuildMessage.zig @@ -26,17 +26,15 @@ pub const BuildMessage = struct { return globalThis.throw("BuildMessage is not constructable", .{}); } - pub fn getNotes(this: *BuildMessage, globalThis: *JSC.JSGlobalObject) JSC.JSValue { + pub fn getNotes(this: *BuildMessage, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { const notes = this.msg.notes; const array = JSC.JSValue.createEmptyArray(globalThis, notes.len); for (notes, 0..) |note, i| { - const cloned = note.clone(bun.default_allocator) catch { - return globalThis.throwOutOfMemoryValue(); - }; + const cloned = try note.clone(bun.default_allocator); array.putIndex( globalThis, @intCast(i), - BuildMessage.create(globalThis, bun.default_allocator, logger.Msg{ .data = cloned, .kind = .note }), + try BuildMessage.create(globalThis, bun.default_allocator, logger.Msg{ .data = cloned, .kind = .note }), ); } @@ -63,10 +61,10 @@ pub const BuildMessage = struct { allocator: std.mem.Allocator, msg: logger.Msg, // resolve_result: *const Resolver.Result, - ) JSC.JSValue { - var build_error = allocator.create(BuildMessage) catch unreachable; + ) bun.OOM!JSC.JSValue { + var build_error = try allocator.create(BuildMessage); build_error.* = BuildMessage{ - .msg = msg.clone(allocator) catch unreachable, + .msg = try msg.clone(allocator), // .resolve_result = resolve_result.*, .allocator = allocator, }; diff --git a/src/bun.js/ResolveMessage.zig b/src/bun.js/ResolveMessage.zig index b98f5c2989..e433fb1cb2 100644 --- a/src/bun.js/ResolveMessage.zig +++ b/src/bun.js/ResolveMessage.zig @@ -179,10 +179,10 @@ pub const ResolveMessage = struct { allocator: std.mem.Allocator, msg: logger.Msg, referrer: string, - ) JSC.JSValue { - var resolve_error = allocator.create(ResolveMessage) catch unreachable; + ) bun.OOM!JSC.JSValue { + var resolve_error = try allocator.create(ResolveMessage); resolve_error.* = ResolveMessage{ - .msg = msg.clone(allocator) catch unreachable, + .msg = try msg.clone(allocator), .allocator = allocator, .referrer = Fs.Path.init(referrer), }; diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index 7a69b3452e..42b8e30731 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -1570,7 +1570,7 @@ pub fn resolveMaybeNeedsTrailingSlash( printed, ), }; - res.* = ErrorableString.err(error.NameTooLong, bun.api.ResolveMessage.create(global, VirtualMachine.get().allocator, msg, source_utf8.slice()).asVoid()); + res.* = ErrorableString.err(error.NameTooLong, (try bun.api.ResolveMessage.create(global, VirtualMachine.get().allocator, msg, source_utf8.slice())).asVoid()); return; } @@ -1660,7 +1660,7 @@ pub fn resolveMaybeNeedsTrailingSlash( }; { - res.* = ErrorableString.err(err, bun.api.ResolveMessage.create(global, VirtualMachine.get().allocator, msg, source_utf8.slice()).asVoid()); + res.* = ErrorableString.err(err, (try bun.api.ResolveMessage.create(global, VirtualMachine.get().allocator, msg, source_utf8.slice())).asVoid()); } return; @@ -1704,7 +1704,7 @@ pub fn processFetchLog(globalThis: *JSGlobalObject, specifier: bun.String, refer }; }; { - ret.* = ErrorableResolvedSource.err(err, bun.api.BuildMessage.create(globalThis, globalThis.allocator(), msg).asVoid()); + ret.* = ErrorableResolvedSource.err(err, (bun.api.BuildMessage.create(globalThis, globalThis.allocator(), msg) catch |e| globalThis.takeException(e)).asVoid()); } return; }, @@ -1712,13 +1712,13 @@ pub fn processFetchLog(globalThis: *JSGlobalObject, specifier: bun.String, refer 1 => { const msg = log.msgs.items[0]; ret.* = ErrorableResolvedSource.err(err, switch (msg.metadata) { - .build => bun.api.BuildMessage.create(globalThis, globalThis.allocator(), msg).asVoid(), - .resolve => bun.api.ResolveMessage.create( + .build => (bun.api.BuildMessage.create(globalThis, globalThis.allocator(), msg) catch |e| globalThis.takeException(e)).asVoid(), + .resolve => (bun.api.ResolveMessage.create( globalThis, globalThis.allocator(), msg, referrer.toUTF8(bun.default_allocator).slice(), - ).asVoid(), + ) catch |e| globalThis.takeException(e)).asVoid(), }); return; }, @@ -1731,13 +1731,13 @@ pub fn processFetchLog(globalThis: *JSGlobalObject, specifier: bun.String, refer for (logs, errors) |msg, *current| { current.* = switch (msg.metadata) { - .build => bun.api.BuildMessage.create(globalThis, globalThis.allocator(), msg), + .build => bun.api.BuildMessage.create(globalThis, globalThis.allocator(), msg) catch |e| globalThis.takeException(e), .resolve => bun.api.ResolveMessage.create( globalThis, globalThis.allocator(), msg, referrer.toUTF8(bun.default_allocator).slice(), - ), + ) catch |e| globalThis.takeException(e), }; } diff --git a/src/bun.js/WTFTimer.zig b/src/bun.js/WTFTimer.zig new file mode 100644 index 0000000000..0fc73be4aa --- /dev/null +++ b/src/bun.js/WTFTimer.zig @@ -0,0 +1,138 @@ +const std = @import("std"); +const bun = @import("bun"); +const jsc = bun.jsc; +const VirtualMachine = jsc.VirtualMachine; +const EventLoopTimer = bun.api.Timer.EventLoopTimer; + +/// This is WTF::RunLoop::TimerBase from WebKit +const RunLoopTimer = opaque { + pub fn fire(this: *RunLoopTimer) void { + WTFTimer__fire(this); + } +}; + +/// A timer created by WTF code and invoked by Bun's event loop +const WTFTimer = @This(); + +vm: *VirtualMachine, +run_loop_timer: *RunLoopTimer, +event_loop_timer: EventLoopTimer, +imminent: *std.atomic.Value(?*WTFTimer), +repeat: bool, +lock: bun.Mutex = .{}, + +const new = bun.TrivialNew(WTFTimer); + +pub export fn WTFTimer__runIfImminent(vm: *VirtualMachine) void { + vm.eventLoop().runImminentGCTimer(); +} + +pub fn run(this: *WTFTimer, vm: *VirtualMachine) void { + if (this.event_loop_timer.state == .ACTIVE) { + vm.timer.remove(&this.event_loop_timer); + } + this.runWithoutRemoving(); +} + +inline fn runWithoutRemoving(this: *const WTFTimer) void { + this.run_loop_timer.fire(); +} + +pub fn update(this: *WTFTimer, seconds: f64, repeat: bool) void { + // There's only one of these per VM, and each VM has its own imminent_gc_timer + this.imminent.store(if (seconds == 0) this else null, .seq_cst); + + if (seconds == 0.0) { + return; + } + + const modf = std.math.modf(seconds); + var interval = bun.timespec.now(); + interval.sec += @intFromFloat(modf.ipart); + interval.nsec += @intFromFloat(modf.fpart * std.time.ns_per_s); + if (interval.nsec >= std.time.ns_per_s) { + interval.sec += 1; + interval.nsec -= std.time.ns_per_s; + } + + this.vm.timer.update(&this.event_loop_timer, &interval); + this.repeat = repeat; +} + +pub fn cancel(this: *WTFTimer) void { + this.lock.lock(); + defer this.lock.unlock(); + this.imminent.store(null, .seq_cst); + if (this.event_loop_timer.state == .ACTIVE) { + this.vm.timer.remove(&this.event_loop_timer); + } +} + +pub fn fire(this: *WTFTimer, _: *const bun.timespec, _: *VirtualMachine) EventLoopTimer.Arm { + this.event_loop_timer.state = .FIRED; + this.imminent.store(null, .seq_cst); + this.runWithoutRemoving(); + return if (this.repeat) + .{ .rearm = this.event_loop_timer.next } + else + .disarm; +} + +pub fn deinit(this: *WTFTimer) void { + this.cancel(); + bun.destroy(this); +} + +export fn WTFTimer__create(run_loop_timer: *RunLoopTimer) ?*anyopaque { + if (VirtualMachine.is_bundler_thread_for_bytecode_cache) { + return null; + } + + const vm = VirtualMachine.get(); + + const this = WTFTimer.new(.{ + .vm = vm, + .imminent = &vm.eventLoop().imminent_gc_timer, + .event_loop_timer = .{ + .next = .{ + .sec = std.math.maxInt(i64), + .nsec = 0, + }, + .tag = .WTFTimer, + .state = .CANCELLED, + }, + .run_loop_timer = run_loop_timer, + .repeat = false, + }); + + return this; +} + +export fn WTFTimer__update(this: *WTFTimer, seconds: f64, repeat: bool) void { + this.update(seconds, repeat); +} + +export fn WTFTimer__deinit(this: *WTFTimer) void { + this.deinit(); +} + +export fn WTFTimer__isActive(this: *const WTFTimer) bool { + return this.event_loop_timer.state == .ACTIVE or (this.imminent.load(.seq_cst) orelse return false) == this; +} + +export fn WTFTimer__cancel(this: *WTFTimer) void { + this.cancel(); +} + +export fn WTFTimer__secondsUntilTimer(this: *WTFTimer) f64 { + this.lock.lock(); + defer this.lock.unlock(); + if (this.event_loop_timer.state == .ACTIVE) { + const until = this.event_loop_timer.next.duration(&bun.timespec.now()); + const sec: f64, const nsec: f64 = .{ @floatFromInt(until.sec), @floatFromInt(until.nsec) }; + return sec + nsec / std.time.ns_per_s; + } + return std.math.inf(f64); +} + +extern fn WTFTimer__fire(this: *RunLoopTimer) void; diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index 905868d8be..bcbdb68117 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -199,7 +199,7 @@ pub const TransformTask = struct { pub fn then(this: *TransformTask, promise: *JSC.JSPromise) void { if (this.log.hasAny() or this.err != null) { - const error_value: JSValue = brk: { + const error_value: bun.OOM!JSValue = brk: { if (this.err) |err| { if (!this.log.hasAny()) { break :brk bun.api.BuildMessage.create( @@ -713,7 +713,7 @@ pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) b const allocator = bun.default_allocator; if ((transpiler_options.log.warnings + transpiler_options.log.errors) > 0) { - return globalThis.throwValue(transpiler_options.log.toJS(globalThis, allocator, "Failed to create transpiler")); + return globalThis.throwValue(try transpiler_options.log.toJS(globalThis, allocator, "Failed to create transpiler")); } var log = try allocator.create(logger.Log); @@ -725,7 +725,7 @@ pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) b JSC.VirtualMachine.get().transpiler.env, ) catch |err| { if ((log.warnings + log.errors) > 0) { - return globalThis.throwValue(log.toJS(globalThis, allocator, "Failed to create transpiler")); + return globalThis.throwValue(try log.toJS(globalThis, allocator, "Failed to create transpiler")); } return globalThis.throwError(err, "Error creating transpiler"); @@ -735,7 +735,7 @@ pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) b transpiler.options.env.behavior = .disable; transpiler.configureDefines() catch |err| { if ((log.warnings + log.errors) > 0) { - return globalThis.throwValue(log.toJS(globalThis, allocator, "Failed to load define")); + return globalThis.throwValue(try log.toJS(globalThis, allocator, "Failed to load define")); } return globalThis.throwError(err, "Failed to load define"); }; @@ -864,14 +864,14 @@ pub fn scan(this: *JSTranspiler, globalThis: *JSC.JSGlobalObject, callframe: *JS var parse_result = getParseResult(this, arena.allocator(), code, loader, Transpiler.MacroJSValueType.zero) orelse { if ((this.transpiler.log.warnings + this.transpiler.log.errors) > 0) { - return globalThis.throwValue(this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); + return globalThis.throwValue(try this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); } return globalThis.throw("Failed to parse", .{}); }; if ((this.transpiler.log.warnings + this.transpiler.log.errors) > 0) { - return globalThis.throwValue(this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); + return globalThis.throwValue(try this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); } const exports_label = JSC.ZigString.static("exports"); @@ -1016,14 +1016,14 @@ pub fn transformSync( js_ctx_value, ) orelse { if ((this.transpiler.log.warnings + this.transpiler.log.errors) > 0) { - return globalThis.throwValue(this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); + return globalThis.throwValue(try this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); } return globalThis.throw("Failed to parse code", .{}); }; if ((this.transpiler.log.warnings + this.transpiler.log.errors) > 0) { - return globalThis.throwValue(this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); + return globalThis.throwValue(try this.transpiler.log.toJS(globalThis, globalThis.allocator(), "Parse error")); } var buffer_writer = this.buffer_writer orelse brk: { @@ -1173,7 +1173,7 @@ pub fn scanImports(this: *JSTranspiler, globalThis: *JSC.JSGlobalObject, callfra ) catch |err| { defer this.scan_pass_result.reset(); if ((log.warnings + log.errors) > 0) { - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "Failed to scan imports")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "Failed to scan imports")); } return globalThis.throwError(err, "Failed to scan imports"); @@ -1182,7 +1182,7 @@ pub fn scanImports(this: *JSTranspiler, globalThis: *JSC.JSGlobalObject, callfra defer this.scan_pass_result.reset(); if ((log.warnings + log.errors) > 0) { - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "Failed to scan imports")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "Failed to scan imports")); } const named_imports_value = namedImportsToJS( diff --git a/src/bun.js/api/TOMLObject.zig b/src/bun.js/api/TOMLObject.zig index 8323880bfe..97d8ce7a8a 100644 --- a/src/bun.js/api/TOMLObject.zig +++ b/src/bun.js/api/TOMLObject.zig @@ -31,7 +31,7 @@ pub fn parse( defer input_slice.deinit(); var source = logger.Source.initPathString("input.toml", input_slice.slice()); const parse_result = TOMLParser.parse(&source, &log, allocator, false) catch { - return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to parse toml")); + return globalThis.throwValue(try log.toJS(globalThis, default_allocator, "Failed to parse toml")); }; // for now... @@ -46,7 +46,7 @@ pub fn parse( .mangled_props = null, }, ) catch { - return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml")); + return globalThis.throwValue(try log.toJS(globalThis, default_allocator, "Failed to print toml")); }; const slice = writer.ctx.buffer.slice(); diff --git a/src/bun.js/api/Timer.zig b/src/bun.js/api/Timer.zig index 0e7daf88c7..71b9f443b5 100644 --- a/src/bun.js/api/Timer.zig +++ b/src/bun.js/api/Timer.zig @@ -126,7 +126,11 @@ pub const All = struct { else timespec{ .nsec = 0, .sec = 0 }; - this.uv_timer.start(wait.msUnsigned(), 0, &onUVTimer); + // minimum 1ms + // https://github.com/nodejs/node/blob/f552c86fecd6c2ba9e832ea129b731dd63abdbe2/src/env.cc#L1512 + const wait_ms = @max(1, wait.msUnsigned()); + + this.uv_timer.start(wait_ms, 0, &onUVTimer); if (this.active_timer_count > 0) { this.uv_timer.ref(); @@ -290,53 +294,6 @@ pub const All = struct { } } - const SetRequest = union(Kind) { - setTimeout: u31, - setInterval: u31, - setImmediate, - }; - - fn set( - id: i32, - globalThis: *JSGlobalObject, - callback: JSValue, - request: SetRequest, - arguments_array_or_zero: JSValue, - ) JSC.JSValue { - JSC.markBinding(@src()); - var vm = globalThis.bunVM(); - const kind: Kind = request; - - const js = switch (request) { - .setImmediate => ImmediateObject.init(globalThis, id, callback, arguments_array_or_zero), - .setTimeout, .setInterval => |countdown| TimeoutObject.init(globalThis, id, kind, countdown, callback, arguments_array_or_zero), - }; - - if (vm.isInspectorEnabled()) { - Debugger.didScheduleAsyncCall( - globalThis, - .DOMTimer, - ID.asyncID(.{ .id = id, .kind = kind.big() }), - kind != .setInterval, // single_shot - ); - } - return js; - } - - pub fn setImmediate( - globalThis: *JSGlobalObject, - callback: JSValue, - arguments: JSValue, - ) callconv(.c) JSValue { - JSC.markBinding(@src()); - const id = globalThis.bunVM().timer.last_id; - globalThis.bunVM().timer.last_id +%= 1; - - const wrappedCallback = callback.withAsyncContextIfNeeded(globalThis); - - return set(id, globalThis, wrappedCallback, .setImmediate, arguments); - } - const TimeoutWarning = enum { TimeoutOverflowWarning, TimeoutNegativeWarning, @@ -417,38 +374,71 @@ pub const All = struct { return countdown_int; } - pub fn setTimeout( - globalThis: *JSGlobalObject, - callback: JSValue, + /// Bun.sleep + /// a setTimeout that uses a promise instead of a callback, and interprets the countdown + /// slightly differently for historical reasons (see jsValueToCountdown) + pub fn sleep( + global: *JSGlobalObject, + promise: JSValue, countdown: JSValue, - arguments: JSValue, - overflow_behavior: CountdownOverflowBehavior, ) JSError!JSValue { JSC.markBinding(@src()); - const id = globalThis.bunVM().timer.last_id; - globalThis.bunVM().timer.last_id +%= 1; + bun.debugAssert(promise != .zero and countdown != .zero); + const vm = global.bunVM(); + const id = vm.timer.last_id; + vm.timer.last_id +%= 1; - const countdown_int = try globalThis.bunVM().timer.jsValueToCountdown(globalThis, countdown, overflow_behavior, true); + const countdown_int = try vm.timer.jsValueToCountdown(global, countdown, .clamp, true); + const wrapped_promise = promise.withAsyncContextIfNeeded(global); + return TimeoutObject.init(global, id, .setTimeout, countdown_int, wrapped_promise, .undefined); + } - const wrappedCallback = callback.withAsyncContextIfNeeded(globalThis); + pub fn setImmediate( + global: *JSGlobalObject, + callback: JSValue, + arguments: JSValue, + ) JSError!JSValue { + JSC.markBinding(@src()); + bun.debugAssert(callback != .zero and arguments != .zero); + const vm = global.bunVM(); + const id = vm.timer.last_id; + vm.timer.last_id +%= 1; - return set(id, globalThis, wrappedCallback, .{ .setTimeout = countdown_int }, arguments); + const wrapped_callback = callback.withAsyncContextIfNeeded(global); + return ImmediateObject.init(global, id, wrapped_callback, arguments); + } + + pub fn setTimeout( + global: *JSGlobalObject, + callback: JSValue, + arguments: JSValue, + countdown: JSValue, + ) JSError!JSValue { + JSC.markBinding(@src()); + bun.debugAssert(callback != .zero and arguments != .zero and countdown != .zero); + const vm = global.bunVM(); + const id = vm.timer.last_id; + vm.timer.last_id +%= 1; + + const wrapped_callback = callback.withAsyncContextIfNeeded(global); + const countdown_int = try global.bunVM().timer.jsValueToCountdown(global, countdown, .one_ms, true); + return TimeoutObject.init(global, id, .setTimeout, countdown_int, wrapped_callback, arguments); } pub fn setInterval( - globalThis: *JSGlobalObject, + global: *JSGlobalObject, callback: JSValue, - countdown: JSValue, arguments: JSValue, + countdown: JSValue, ) JSError!JSValue { JSC.markBinding(@src()); - const id = globalThis.bunVM().timer.last_id; - globalThis.bunVM().timer.last_id +%= 1; + bun.debugAssert(callback != .zero and arguments != .zero and countdown != .zero); + const vm = global.bunVM(); + const id = vm.timer.last_id; + vm.timer.last_id +%= 1; - const wrappedCallback = callback.withAsyncContextIfNeeded(globalThis); - - const countdown_int = try globalThis.bunVM().timer.jsValueToCountdown(globalThis, countdown, .one_ms, true); - - return set(id, globalThis, wrappedCallback, .{ .setInterval = countdown_int }, arguments); + const wrapped_callback = callback.withAsyncContextIfNeeded(global); + const countdown_int = try global.bunVM().timer.jsValueToCountdown(global, countdown, .one_ms, true); + return TimeoutObject.init(global, id, .setInterval, countdown_int, wrapped_callback, arguments); } fn removeTimerById(this: *All, id: i32) ?*TimeoutObject { @@ -547,8 +537,9 @@ pub const All = struct { } comptime { - @export(&setImmediate, .{ .name = "Bun__Timer__setImmediate" }); - @export(&JSC.host_fn.wrap5(setTimeout), .{ .name = "Bun__Timer__setTimeout" }); + @export(&JSC.host_fn.wrap3(setImmediate), .{ .name = "Bun__Timer__setImmediate" }); + @export(&JSC.host_fn.wrap3(sleep), .{ .name = "Bun__Timer__sleep" }); + @export(&JSC.host_fn.wrap4(setTimeout), .{ .name = "Bun__Timer__setTimeout" }); @export(&JSC.host_fn.wrap4(setInterval), .{ .name = "Bun__Timer__setInterval" }); @export(&JSC.host_fn.wrap2(clearImmediate), .{ .name = "Bun__Timer__clearImmediate" }); @export(&JSC.host_fn.wrap2(clearTimeout), .{ .name = "Bun__Timer__clearTimeout" }); @@ -582,7 +573,7 @@ pub const TimeoutObject = struct { kind: Kind, interval: u31, callback: JSValue, - arguments_array_or_zero: JSValue, + arguments: JSValue, ) JSValue { // internals are initialized by init() const timeout = bun.new(TimeoutObject, .{ .ref_count = .init(), .internals = undefined }); @@ -595,8 +586,18 @@ pub const TimeoutObject = struct { kind, interval, callback, - arguments_array_or_zero, + arguments, ); + + if (globalThis.bunVM().isInspectorEnabled()) { + Debugger.didScheduleAsyncCall( + globalThis, + .DOMTimer, + ID.asyncID(.{ .id = id, .kind = kind.big() }), + kind != .setInterval, + ); + } + return js_value; } @@ -610,10 +611,6 @@ pub const TimeoutObject = struct { return globalObject.throw("Timeout is not constructible", .{}); } - pub fn runImmediateTask(this: *TimeoutObject, vm: *VirtualMachine) void { - this.internals.runImmediateTask(vm); - } - pub fn toPrimitive(this: *TimeoutObject, _: *JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue { return this.internals.toPrimitive(); } @@ -652,44 +649,24 @@ pub const TimeoutObject = struct { return TimeoutObject.js.callbackGetCached(thisValue).?; } - pub fn set_onTimeout(this: *TimeoutObject, thisValue: JSValue, globalThis: *JSGlobalObject, value: JSValue) bool { + pub fn set_onTimeout(_: *TimeoutObject, thisValue: JSValue, globalThis: *JSGlobalObject, value: JSValue) void { TimeoutObject.js.callbackSetCached(thisValue, globalThis, value); - this.internals.flags.should_destroy_before_firing = !value.toBoolean(); - return true; } pub fn get_idleTimeout(_: *TimeoutObject, thisValue: JSValue, _: *JSGlobalObject) JSValue { return TimeoutObject.js.idleTimeoutGetCached(thisValue).?; } - pub fn set_idleTimeout(this: *TimeoutObject, thisValue: JSValue, globalThis: *JSGlobalObject, value: JSValue) bool { + pub fn set_idleTimeout(_: *TimeoutObject, thisValue: JSValue, globalThis: *JSGlobalObject, value: JSValue) void { TimeoutObject.js.idleTimeoutSetCached(thisValue, globalThis, value); + } - if (value.isNumber()) { - const num = value.toNumber(globalThis) catch |err| switch (err) { - error.JSError => return false, - error.OutOfMemory => { - _ = globalThis.throwOutOfMemoryValue(); - return false; - }, - }; + pub fn get_repeat(_: *TimeoutObject, thisValue: JSValue, _: *JSGlobalObject) JSValue { + return TimeoutObject.js.repeatGetCached(thisValue).?; + } - // cancel if the value is exactly -1 - // https://github.com/nodejs/node/blob/4cd8e1914a503ece778d642e748020e675cf1060/lib/internal/timers.js#L612-L625 - this.internals.flags.should_reschedule_interval = num != -1; - } else { - this.internals.flags.should_reschedule_interval = true; - } - - this.internals.interval = globalThis.bunVM().timer.jsValueToCountdown(globalThis, value, .one_ms, false) catch |err| switch (err) { - error.JSError => return false, - error.OutOfMemory => { - _ = globalThis.throwOutOfMemoryValue(); - return false; - }, - }; - - return true; + pub fn set_repeat(_: *TimeoutObject, thisValue: JSValue, globalThis: *JSGlobalObject, value: JSValue) void { + TimeoutObject.js.repeatSetCached(thisValue, globalThis, value); } pub fn dispose(this: *TimeoutObject, globalThis: *JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue { @@ -719,7 +696,7 @@ pub const ImmediateObject = struct { globalThis: *JSGlobalObject, id: i32, callback: JSValue, - arguments_array_or_zero: JSValue, + arguments: JSValue, ) JSValue { // internals are initialized by init() const immediate = bun.new(ImmediateObject, .{ .ref_count = .init(), .internals = undefined }); @@ -732,8 +709,18 @@ pub const ImmediateObject = struct { .setImmediate, 0, callback, - arguments_array_or_zero, + arguments, ); + + if (globalThis.bunVM().isInspectorEnabled()) { + Debugger.didScheduleAsyncCall( + globalThis, + .DOMTimer, + ID.asyncID(.{ .id = id, .kind = .setImmediate }), + true, + ); + } + return js_value; } @@ -747,8 +734,9 @@ pub const ImmediateObject = struct { return globalObject.throw("Immediate is not constructible", .{}); } - pub fn runImmediateTask(this: *ImmediateObject, vm: *VirtualMachine) void { - this.internals.runImmediateTask(vm); + /// returns true if an exception was thrown + pub fn runImmediateTask(this: *ImmediateObject, vm: *VirtualMachine) bool { + return this.internals.runImmediateTask(vm); } pub fn toPrimitive(this: *ImmediateObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSValue { @@ -790,7 +778,7 @@ pub const TimerObjectInternals = struct { strong_this: JSC.Strong.Optional = .empty, flags: Flags = .{}, - const Flags = packed struct(u34) { + const Flags = packed struct(u32) { /// Whenever a timer is inserted into the heap (which happen on creation or refresh), the global /// epoch is incremented and the new epoch is set on the timer. For timers created by /// JavaScript, the epoch is used to break ties between timers scheduled for the same @@ -813,14 +801,6 @@ pub const TimerObjectInternals = struct { /// Set to `true` only during execution of the JavaScript function so that `_destroyed` can be /// false during the callback, even though the `state` will be `FIRED`. in_callback: bool = false, - - // is set `false` when `_idleTimeout` is set to -1 - // https://github.com/nodejs/node/blob/4cd8e1914a503ece778d642e748020e675cf1060/lib/internal/timers.js#L612-L626 - should_reschedule_interval: bool = true, - - // is set `true` when `_onTimeout` is set to a falsy value - // https://github.com/nodejs/node/blob/4cd8e1914a503ece778d642e748020e675cf1060/lib/internal/timers.js#L578-L592 - should_destroy_before_firing: bool = false, }; fn eventLoopTimer(this: *TimerObjectInternals) *EventLoopTimer { @@ -852,23 +832,24 @@ pub const TimerObjectInternals = struct { } } - extern "c" fn Bun__JSTimeout__call(encodedTimeoutValue: JSValue, globalObject: *JSC.JSGlobalObject) void; + extern "c" fn Bun__JSTimeout__call(globalObject: *JSC.JSGlobalObject, timer: JSValue, callback: JSValue, arguments: JSValue) bool; - pub fn runImmediateTask(this: *TimerObjectInternals, vm: *VirtualMachine) void { + /// returns true if an exception was thrown + pub fn runImmediateTask(this: *TimerObjectInternals, vm: *VirtualMachine) bool { if (this.flags.has_cleared_timer or // unref'd setImmediate callbacks should only run if there are things keeping the event // loop alive other than setImmediates (!this.flags.is_keeping_event_loop_alive and !vm.isEventLoopAliveExcludingImmediates())) { this.deref(); - return; + return false; } - const this_object = this.strong_this.get() orelse { + const timer = this.strong_this.get() orelse { if (Environment.isDebug) { @panic("TimerObjectInternals.runImmediateTask: this_object is null"); } - return; + return false; }; const globalThis = vm.global; this.strong_this.deinit(); @@ -876,17 +857,19 @@ pub const TimerObjectInternals = struct { this.setEnableKeepingEventLoopAlive(vm, false); vm.eventLoop().enter(); - { - this.ref(); - defer this.deref(); + const callback = ImmediateObject.js.callbackGetCached(timer).?; + const arguments = ImmediateObject.js.argumentsGetCached(timer).?; + this.ref(); + const exception_thrown = this.run(globalThis, timer, callback, arguments, this.asyncID(), vm); + this.deref(); - this.run(this_object, globalThis, this.asyncID(), vm); - - if (this.eventLoopTimer().state == .FIRED) { - this.deref(); - } + if (this.eventLoopTimer().state == .FIRED) { + this.deref(); } - vm.eventLoop().exit(); + + vm.eventLoop().exitMaybeDrainMicrotasks(!exception_thrown); + + return exception_thrown; } pub fn asyncID(this: *const TimerObjectInternals) u64 { @@ -896,17 +879,35 @@ pub const TimerObjectInternals = struct { pub fn fire(this: *TimerObjectInternals, _: *const timespec, vm: *JSC.VirtualMachine) EventLoopTimer.Arm { const id = this.id; const kind = this.flags.kind.big(); + const async_id: ID = .{ .id = id, .kind = kind }; const has_been_cleared = this.eventLoopTimer().state == .CANCELLED or this.flags.has_cleared_timer or vm.scriptExecutionStatus() != .running; this.eventLoopTimer().state = .FIRED; const globalThis = vm.global; + const this_object = this.strong_this.get().?; - if (has_been_cleared or this.flags.should_destroy_before_firing) { + const callback, const arguments, var idle_timeout, var repeat = switch (kind) { + .setImmediate => .{ + ImmediateObject.js.callbackGetCached(this_object).?, + ImmediateObject.js.argumentsGetCached(this_object).?, + + .undefined, + .undefined, + }, + .setTimeout, .setInterval => .{ + TimeoutObject.js.callbackGetCached(this_object).?, + TimeoutObject.js.argumentsGetCached(this_object).?, + TimeoutObject.js.idleTimeoutGetCached(this_object).?, + TimeoutObject.js.repeatGetCached(this_object).?, + }, + }; + + if (has_been_cleared or !callback.toBoolean()) { if (vm.isInspectorEnabled()) { - Debugger.didCancelAsyncCall(globalThis, .DOMTimer, ID.asyncID(.{ .id = id, .kind = kind })); + Debugger.didCancelAsyncCall(globalThis, .DOMTimer, ID.asyncID(async_id)); } - + this.setEnableKeepingEventLoopAlive(vm, false); this.flags.has_cleared_timer = true; this.strong_this.deinit(); this.deref(); @@ -914,7 +915,6 @@ pub const TimerObjectInternals = struct { return .disarm; } - const this_object = this.strong_this.get().?; var time_before_call: timespec = undefined; if (kind != .setInterval) { @@ -930,12 +930,20 @@ pub const TimerObjectInternals = struct { this.ref(); defer this.deref(); - this.run(this_object, globalThis, ID.asyncID(.{ .id = id, .kind = kind }), vm); + _ = this.run(globalThis, this_object, callback, arguments, ID.asyncID(async_id), vm); + + switch (kind) { + .setTimeout, .setInterval => { + idle_timeout = TimeoutObject.js.idleTimeoutGetCached(this_object).?; + repeat = TimeoutObject.js.repeatGetCached(this_object).?; + }, + else => {}, + } const is_timer_done = is_timer_done: { // Node doesn't drain microtasks after each timer callback. if (kind == .setInterval) { - if (!this.flags.should_reschedule_interval) { + if (!this.shouldRescheduleTimer(repeat, idle_timeout)) { break :is_timer_done true; } switch (this.eventLoopTimer().state) { @@ -961,8 +969,19 @@ pub const TimerObjectInternals = struct { break :is_timer_done true; }, } - } else if (this.eventLoopTimer().state == .FIRED) { - break :is_timer_done true; + } else { + if (kind == .setTimeout and !repeat.isNull()) { + if (idle_timeout.getNumber()) |num| { + if (num != -1) { + this.convertToInterval(globalThis, this_object, repeat); + break :is_timer_done false; + } + } + } + + if (this.eventLoopTimer().state == .FIRED) { + break :is_timer_done true; + } } break :is_timer_done false; @@ -979,7 +998,22 @@ pub const TimerObjectInternals = struct { return .disarm; } - pub fn run(this: *TimerObjectInternals, this_object: JSC.JSValue, globalThis: *JSC.JSGlobalObject, async_id: u64, vm: *JSC.VirtualMachine) void { + fn convertToInterval(this: *TimerObjectInternals, global: *JSGlobalObject, timer: JSValue, repeat: JSValue) void { + bun.debugAssert(this.flags.kind == .setTimeout); + + const vm = global.bunVM(); + + const new_interval: u31 = if (repeat.getNumber()) |num| if (num < 1 or num > std.math.maxInt(u31)) 1 else @intFromFloat(num) else 1; + + // https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L613 + TimeoutObject.js.idleTimeoutSetCached(timer, global, repeat); + this.strong_this.set(global, timer); + this.flags.kind = .setInterval; + this.interval = new_interval; + this.reschedule(timer, vm); + } + + pub fn run(this: *TimerObjectInternals, globalThis: *JSC.JSGlobalObject, timer: JSValue, callback: JSValue, arguments: JSValue, async_id: u64, vm: *JSC.VirtualMachine) bool { if (vm.isInspectorEnabled()) { Debugger.willDispatchAsyncCall(globalThis, .DOMTimer, async_id); } @@ -993,20 +1027,20 @@ pub const TimerObjectInternals = struct { // Bun__JSTimeout__call handles exceptions. this.flags.in_callback = true; defer this.flags.in_callback = false; - Bun__JSTimeout__call(this_object, globalThis); + return Bun__JSTimeout__call(globalThis, timer, callback, arguments); } pub fn init( this: *TimerObjectInternals, - timer_js: JSValue, - globalThis: *JSGlobalObject, + timer: JSValue, + global: *JSGlobalObject, id: i32, kind: Kind, interval: u31, callback: JSValue, arguments: JSValue, ) void { - const vm = globalThis.bunVM(); + const vm = global.bunVM(); this.* = .{ .id = id, .flags = .{ .kind = kind, .epoch = vm.timer.epoch }, @@ -1014,24 +1048,24 @@ pub const TimerObjectInternals = struct { }; if (kind == .setImmediate) { - if (arguments != .zero) - ImmediateObject.js.argumentsSetCached(timer_js, globalThis, arguments); - ImmediateObject.js.callbackSetCached(timer_js, globalThis, callback); + ImmediateObject.js.argumentsSetCached(timer, global, arguments); + ImmediateObject.js.callbackSetCached(timer, global, callback); const parent: *ImmediateObject = @fieldParentPtr("internals", this); vm.enqueueImmediateTask(parent); this.setEnableKeepingEventLoopAlive(vm, true); // ref'd by event loop parent.ref(); } else { - if (arguments != .zero) - TimeoutObject.js.argumentsSetCached(timer_js, globalThis, arguments); - TimeoutObject.js.callbackSetCached(timer_js, globalThis, callback); - TimeoutObject.js.idleTimeoutSetCached(timer_js, globalThis, JSC.jsNumber(interval)); + TimeoutObject.js.argumentsSetCached(timer, global, arguments); + TimeoutObject.js.callbackSetCached(timer, global, callback); + TimeoutObject.js.idleTimeoutSetCached(timer, global, JSC.jsNumber(interval)); + TimeoutObject.js.repeatSetCached(timer, global, if (kind == .setInterval) JSC.jsNumber(interval) else .null); + // this increments the refcount - this.reschedule(vm); + this.reschedule(timer, vm); } - this.strong_this.set(globalThis, timer_js); + this.strong_this.set(global, timer); } pub fn doRef(this: *TimerObjectInternals, _: *JSC.JSGlobalObject, this_value: JSValue) JSValue { @@ -1040,7 +1074,10 @@ pub const TimerObjectInternals = struct { const did_have_js_ref = this.flags.has_js_ref; this.flags.has_js_ref = true; - if (!did_have_js_ref) { + // https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L256 + // and + // https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L685-L687 + if (!did_have_js_ref and !this.flags.has_cleared_timer) { this.setEnableKeepingEventLoopAlive(JSC.VirtualMachine.get(), true); } @@ -1059,7 +1096,7 @@ pub const TimerObjectInternals = struct { } this.strong_this.set(globalObject, this_value); - this.reschedule(VirtualMachine.get()); + this.reschedule(this_value, VirtualMachine.get()); return this_value; } @@ -1094,9 +1131,22 @@ pub const TimerObjectInternals = struct { } } - pub fn reschedule(this: *TimerObjectInternals, vm: *VirtualMachine) void { + fn shouldRescheduleTimer(this: *TimerObjectInternals, repeat: JSValue, idle_timeout: JSValue) bool { + if (this.flags.kind == .setInterval and repeat.isNull()) return false; + if (idle_timeout.getNumber()) |num| { + if (num == -1) return false; + } + return true; + } + + pub fn reschedule(this: *TimerObjectInternals, timer: JSValue, vm: *VirtualMachine) void { if (this.flags.kind == .setImmediate) return; - if (!this.flags.should_reschedule_interval) return; + + const idle_timeout = TimeoutObject.js.idleTimeoutGetCached(timer).?; + const repeat = TimeoutObject.js.repeatGetCached(timer).?; + + // https://github.com/nodejs/node/blob/a7cbb904745591c9a9d047a364c2c188e5470047/lib/internal/timers.js#L612 + if (!this.shouldRescheduleTimer(repeat, idle_timeout)) return; const now = timespec.msFromNow(this.interval); const was_active = this.eventLoopTimer().state == .ACTIVE; @@ -1458,135 +1508,7 @@ pub const EventLoopTimer = struct { const timespec = bun.timespec; /// A timer created by WTF code and invoked by Bun's event loop -pub const WTFTimer = struct { - /// This is WTF::RunLoop::TimerBase from WebKit - const RunLoopTimer = opaque {}; - - vm: *VirtualMachine, - run_loop_timer: *RunLoopTimer, - event_loop_timer: EventLoopTimer, - imminent: *std.atomic.Value(?*WTFTimer), - repeat: bool, - lock: bun.Mutex = .{}, - - pub const new = bun.TrivialNew(@This()); - - pub fn init(run_loop_timer: *RunLoopTimer, js_vm: *VirtualMachine) *WTFTimer { - const this = WTFTimer.new(.{ - .vm = js_vm, - .imminent = &js_vm.eventLoop().imminent_gc_timer, - .event_loop_timer = .{ - .next = .{ - .sec = std.math.maxInt(i64), - .nsec = 0, - }, - .tag = .WTFTimer, - .state = .CANCELLED, - }, - .run_loop_timer = run_loop_timer, - .repeat = false, - }); - - return this; - } - - pub export fn WTFTimer__runIfImminent(vm: *VirtualMachine) void { - vm.eventLoop().runImminentGCTimer(); - } - - pub fn run(this: *WTFTimer, vm: *VirtualMachine) void { - if (this.event_loop_timer.state == .ACTIVE) { - vm.timer.remove(&this.event_loop_timer); - } - this.runWithoutRemoving(); - } - - inline fn runWithoutRemoving(this: *const WTFTimer) void { - WTFTimer__fire(this.run_loop_timer); - } - - pub fn update(this: *WTFTimer, seconds: f64, repeat: bool) void { - // There's only one of these per VM, and each VM has its own imminent_gc_timer - this.imminent.store(if (seconds == 0) this else null, .seq_cst); - - if (seconds == 0.0) { - return; - } - - const modf = std.math.modf(seconds); - var interval = bun.timespec.now(); - interval.sec += @intFromFloat(modf.ipart); - interval.nsec += @intFromFloat(modf.fpart * std.time.ns_per_s); - if (interval.nsec >= std.time.ns_per_s) { - interval.sec += 1; - interval.nsec -= std.time.ns_per_s; - } - - this.vm.timer.update(&this.event_loop_timer, &interval); - this.repeat = repeat; - } - - pub fn cancel(this: *WTFTimer) void { - this.lock.lock(); - defer this.lock.unlock(); - this.imminent.store(null, .seq_cst); - if (this.event_loop_timer.state == .ACTIVE) { - this.vm.timer.remove(&this.event_loop_timer); - } - } - - pub fn fire(this: *WTFTimer, _: *const bun.timespec, _: *VirtualMachine) EventLoopTimer.Arm { - this.event_loop_timer.state = .FIRED; - this.imminent.store(null, .seq_cst); - this.runWithoutRemoving(); - return if (this.repeat) - .{ .rearm = this.event_loop_timer.next } - else - .disarm; - } - - pub fn deinit(this: *WTFTimer) void { - this.cancel(); - bun.destroy(this); - } - - export fn WTFTimer__create(run_loop_timer: *RunLoopTimer) ?*anyopaque { - if (VirtualMachine.is_bundler_thread_for_bytecode_cache) { - return null; - } - - return init(run_loop_timer, VirtualMachine.get()); - } - - export fn WTFTimer__update(this: *WTFTimer, seconds: f64, repeat: bool) void { - this.update(seconds, repeat); - } - - export fn WTFTimer__deinit(this: *WTFTimer) void { - this.deinit(); - } - - export fn WTFTimer__isActive(this: *const WTFTimer) bool { - return this.event_loop_timer.state == .ACTIVE or (this.imminent.load(.seq_cst) orelse return false) == this; - } - - export fn WTFTimer__cancel(this: *WTFTimer) void { - this.cancel(); - } - - export fn WTFTimer__secondsUntilTimer(this: *WTFTimer) f64 { - this.lock.lock(); - defer this.lock.unlock(); - if (this.event_loop_timer.state == .ACTIVE) { - const until = this.event_loop_timer.next.duration(&bun.timespec.now()); - const sec: f64, const nsec: f64 = .{ @floatFromInt(until.sec), @floatFromInt(until.nsec) }; - return sec + nsec / std.time.ns_per_s; - } - return std.math.inf(f64); - } - - extern fn WTFTimer__fire(this: *RunLoopTimer) void; -}; +pub const WTFTimer = @import("../WTFTimer.zig"); pub const internal_bindings = struct { /// Node.js has some tests that check whether timers fire at the right time. They check this diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 5987ce4b81..c9e4ba6892 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -543,10 +543,9 @@ pub const Listener = struct { return this.strong_data.get() orelse JSValue.jsUndefined(); } - pub fn setData(this: *Listener, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) callconv(.C) bool { + pub fn setData(this: *Listener, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { log("setData()", .{}); this.strong_data.set(globalObject, value); - return true; } const UnixOrHost = union(enum) { @@ -2003,10 +2002,9 @@ fn NewSocket(comptime ssl: bool) type { return JSValue.jsUndefined(); } - pub fn setData(this: *This, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) callconv(.C) bool { + pub fn setData(this: *This, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { log("setData()", .{}); This.js.dataSetCached(this.this_value, globalObject, value); - return true; } pub fn getListener(this: *This, _: *JSC.JSGlobalObject) JSValue { diff --git a/src/bun.js/api/crypto/CryptoHasher.zig b/src/bun.js/api/crypto/CryptoHasher.zig index 759d1fd167..da7464e332 100644 --- a/src/bun.js/api/crypto/CryptoHasher.zig +++ b/src/bun.js/api/crypto/CryptoHasher.zig @@ -135,21 +135,21 @@ pub const CryptoHasher = union(enum) { return globalThis.throw("HMAC has been consumed and is no longer usable", .{}); } - pub fn getByteLength(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject) JSC.JSValue { + pub fn getByteLength(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { return JSC.JSValue.jsNumber(switch (this.*) { .evp => |*inner| inner.size(), .hmac => |inner| if (inner) |hmac| hmac.size() else { - throwHmacConsumed(globalThis) catch return .zero; + return throwHmacConsumed(globalThis); }, .zig => |*inner| inner.digest_length, }); } - pub fn getAlgorithm(this: *CryptoHasher, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + pub fn getAlgorithm(this: *CryptoHasher, globalObject: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { return switch (this.*) { inline .evp, .zig => |*inner| ZigString.fromUTF8(bun.asByteSlice(@tagName(inner.algorithm))).toJS(globalObject), .hmac => |inner| if (inner) |hmac| ZigString.fromUTF8(bun.asByteSlice(@tagName(hmac.algorithm))).toJS(globalObject) else { - throwHmacConsumed(globalObject) catch return .zero; + return throwHmacConsumed(globalObject); }, }; } diff --git a/src/bun.js/api/filesystem_router.zig b/src/bun.js/api/filesystem_router.zig index 261dacc7e6..05e3d620d8 100644 --- a/src/bun.js/api/filesystem_router.zig +++ b/src/bun.js/api/filesystem_router.zig @@ -136,7 +136,7 @@ pub const FileSystemRouter = struct { origin_str.deinit(); arena.deinit(); globalThis.allocator().destroy(arena); - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "reading root directory")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "reading root directory")); } orelse { origin_str.deinit(); arena.deinit(); @@ -154,7 +154,7 @@ pub const FileSystemRouter = struct { origin_str.deinit(); arena.deinit(); globalThis.allocator().destroy(arena); - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "loading routes")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "loading routes")); }; if (try argument.get(globalThis, "origin")) |origin| { @@ -170,7 +170,7 @@ pub const FileSystemRouter = struct { origin_str.deinit(); arena.deinit(); globalThis.allocator().destroy(arena); - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "loading routes")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "loading routes")); } var fs_router = globalThis.allocator().create(FileSystemRouter) catch unreachable; @@ -253,7 +253,7 @@ pub const FileSystemRouter = struct { bustDirCache(this, globalThis); const root_dir_info = vm.transpiler.resolver.readDirInfo(this.router.config.dir) catch { - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "reading root directory")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "reading root directory")); } orelse { arena.deinit(); globalThis.allocator().destroy(arena); @@ -268,7 +268,7 @@ pub const FileSystemRouter = struct { router.loadRoutes(&log, root_dir_info, Resolver, &vm.transpiler.resolver, router.config.dir) catch { arena.deinit(); globalThis.allocator().destroy(arena); - return globalThis.throwValue(log.toJS(globalThis, globalThis.allocator(), "loading routes")); + return globalThis.throwValue(try log.toJS(globalThis, globalThis.allocator(), "loading routes")); }; this.arena.deinit(); @@ -354,10 +354,10 @@ pub const FileSystemRouter = struct { return JSValue.jsNull(); } - pub fn getRoutes(this: *FileSystemRouter, globalThis: *JSC.JSGlobalObject) JSValue { - const paths = this.router.getEntryPoints() catch unreachable; - const names = this.router.getNames() catch unreachable; - var name_strings = bun.default_allocator.alloc(ZigString, names.len * 2) catch unreachable; + pub fn getRoutes(this: *FileSystemRouter, globalThis: *JSC.JSGlobalObject) bun.JSError!JSValue { + const paths = this.router.getEntryPoints(); + const names = this.router.getNames(); + var name_strings = try bun.default_allocator.alloc(ZigString, names.len * 2); defer bun.default_allocator.free(name_strings); var paths_strings = name_strings[names.len..]; for (names, 0..) |name, i| { @@ -619,12 +619,12 @@ pub const MatchedRoute = struct { pub fn getParams( this: *MatchedRoute, globalThis: *JSC.JSGlobalObject, - ) JSC.JSValue { + ) bun.JSError!JSC.JSValue { if (this.route.params.len == 0) return JSValue.createEmptyObject(globalThis, 0); if (this.param_map == null) { - this.param_map = QueryStringMap.initWithScanner( + this.param_map = try QueryStringMap.initWithScanner( globalThis.allocator(), CombinedScanner.init( "", @@ -632,8 +632,7 @@ pub const MatchedRoute = struct { this.route.name, this.route.params, ), - ) catch - unreachable; + ); } return createQueryObject(globalThis, &this.param_map.?); @@ -642,7 +641,7 @@ pub const MatchedRoute = struct { pub fn getQuery( this: *MatchedRoute, globalThis: *JSC.JSGlobalObject, - ) JSC.JSValue { + ) bun.JSError!JSC.JSValue { if (this.route.query_string.len == 0 and this.route.params.len == 0) { return JSValue.createEmptyObject(globalThis, 0); } else if (this.route.query_string.len == 0) { @@ -651,19 +650,15 @@ pub const MatchedRoute = struct { if (this.query_string_map == null) { if (this.route.params.len > 0) { - if (QueryStringMap.initWithScanner(globalThis.allocator(), CombinedScanner.init( + this.query_string_map = try QueryStringMap.initWithScanner(globalThis.allocator(), CombinedScanner.init( this.route.query_string, this.route.pathnameWithoutLeadingSlash(), this.route.name, this.route.params, - ))) |map| { - this.query_string_map = map; - } else |_| {} + )); } else { - if (QueryStringMap.init(globalThis.allocator(), this.route.query_string)) |map| { - this.query_string_map = map; - } else |_| {} + this.query_string_map = try QueryStringMap.init(globalThis.allocator(), this.route.query_string); } } diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index d0a3398e3a..ac36959453 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -8,6 +8,7 @@ const JSGlobalObject = JSC.JSGlobalObject; const Response = bun.webcore.Response; const LOLHTML = bun.LOLHTML; const host_fn = JSC.host_fn; +const JSError = bun.JSError; const SelectorMap = std.ArrayListUnmanaged(*LOLHTML.HTMLSelector); pub const LOLHTMLContext = struct { @@ -1417,17 +1418,14 @@ pub const Comment = struct { this: *Comment, global: *JSGlobalObject, value: JSValue, - ) callconv(.C) bool { + ) JSError!void { if (this.comment == null) - return false; - var text = value.toSlice(global, bun.default_allocator) catch return false; + return; + var text = try value.toSlice(global, bun.default_allocator); defer text.deinit(); this.comment.?.setText(text.slice()) catch { - global.throwValue(createLOLHTMLError(global)) catch {}; - return false; + return global.throwValue(createLOLHTMLError(global)); }; - - return true; } pub fn removed( @@ -1569,17 +1567,14 @@ pub const EndTag = struct { this: *EndTag, global: *JSGlobalObject, value: JSValue, - ) callconv(.C) bool { + ) JSError!void { if (this.end_tag == null) - return false; - var text = value.toSlice(global, bun.default_allocator) catch return false; + return; + var text = try value.toSlice(global, bun.default_allocator); defer text.deinit(); this.end_tag.?.setName(text.slice()) catch { - global.throwValue(createLOLHTMLError(global)) catch {}; - return false; + return global.throwValue(createLOLHTMLError(global)); }; - - return true; } }; @@ -1896,18 +1891,15 @@ pub const Element = struct { this: *Element, global: *JSGlobalObject, value: JSValue, - ) bool { + ) JSError!void { if (this.element == null) - return false; - var text = value.toSlice(global, bun.default_allocator) catch return false; + return; + var text = try value.toSlice(global, bun.default_allocator); defer text.deinit(); this.element.?.setTagName(text.slice()) catch { - global.throwValue(createLOLHTMLError(global)) catch {}; - return false; + return global.throwValue(createLOLHTMLError(global)); }; - - return true; } pub fn getRemoved( diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index b50b389d7c..42679ad7c1 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -5952,7 +5952,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d } } - pub fn getURL(this: *ThisServer, globalThis: *JSGlobalObject) JSC.JSValue { + pub fn getURL(this: *ThisServer, globalThis: *JSGlobalObject) bun.OOM!JSC.JSValue { const fmt = switch (this.config.address) { .unix => |unix| brk: { if (unix.len > 1 and unix[0] == 0) { @@ -5981,7 +5981,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d }, }; - const buf = std.fmt.allocPrint(default_allocator, "{any}", .{fmt}) catch bun.outOfMemory(); + const buf = try std.fmt.allocPrint(default_allocator, "{any}", .{fmt}); defer default_allocator.free(buf); var value = bun.String.createUTF8(buf); diff --git a/src/bun.js/api/server/NodeHTTPResponse.zig b/src/bun.js/api/server/NodeHTTPResponse.zig index 136c81d2b4..960a9f3e6f 100644 --- a/src/bun.js/api/server/NodeHTTPResponse.zig +++ b/src/bun.js/api/server/NodeHTTPResponse.zig @@ -918,14 +918,12 @@ fn writeOrEnd( } } -pub fn setOnWritable(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) bool { +pub fn setOnWritable(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) void { if (this.isDone() or value == .undefined) { js.onWritableSetCached(thisValue, globalObject, .undefined); } else { js.onWritableSetCached(thisValue, globalObject, value.withAsyncContextIfNeeded(globalObject)); } - - return true; } pub fn getOnWritable(_: *NodeHTTPResponse, thisValue: JSC.JSValue, _: *JSC.JSGlobalObject) JSC.JSValue { @@ -939,9 +937,9 @@ pub fn getOnAbort(this: *NodeHTTPResponse, thisValue: JSC.JSValue, _: *JSC.JSGlo return js.onAbortedGetCached(thisValue) orelse .undefined; } -pub fn setOnAbort(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) bool { +pub fn setOnAbort(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) void { if (this.flags.socket_closed) { - return true; + return; } if (this.isDone() or value == .undefined) { @@ -949,8 +947,6 @@ pub fn setOnAbort(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: } else { js.onAbortedSetCached(thisValue, globalObject, value.withAsyncContextIfNeeded(globalObject)); } - - return true; } pub fn getOnData(_: *NodeHTTPResponse, thisValue: JSC.JSValue, _: *JSC.JSGlobalObject) JSC.JSValue { @@ -965,9 +961,8 @@ pub fn getUpgraded(this: *NodeHTTPResponse, _: *JSC.JSGlobalObject) JSC.JSValue return JSC.jsBoolean(this.flags.upgraded); } -pub fn setHasCustomOnData(this: *NodeHTTPResponse, _: *JSC.JSGlobalObject, value: JSValue) bool { +pub fn setHasCustomOnData(this: *NodeHTTPResponse, _: *JSC.JSGlobalObject, value: JSValue) void { this.flags.hasCustomOnData = value.toBoolean(); - return true; } fn clearOnDataCallback(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject) void { @@ -983,7 +978,7 @@ fn clearOnDataCallback(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalOb } } -pub fn setOnData(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) bool { +pub fn setOnData(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) void { if (value == .undefined or this.flags.ended or this.flags.socket_closed or this.body_read_state == .none or this.flags.is_data_buffered_during_pause_last) { js.onDataSetCached(thisValue, globalObject, .undefined); defer { @@ -1000,7 +995,7 @@ pub fn setOnData(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: }, .none => {}, } - return true; + return; } js.onDataSetCached(thisValue, globalObject, value.withAsyncContextIfNeeded(globalObject)); @@ -1012,8 +1007,6 @@ pub fn setOnData(this: *NodeHTTPResponse, thisValue: JSC.JSValue, globalObject: this.ref(); this.body_read_ref.ref(globalObject.bunVM()); } - - return true; } pub fn write(this: *NodeHTTPResponse, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { diff --git a/src/bun.js/api/server/ServerWebSocket.zig b/src/bun.js/api/server/ServerWebSocket.zig index d2c8f5c188..b5fc2b1ac4 100644 --- a/src/bun.js/api/server/ServerWebSocket.zig +++ b/src/bun.js/api/server/ServerWebSocket.zig @@ -1034,10 +1034,9 @@ pub fn setData( this: *ServerWebSocket, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue, -) callconv(.C) bool { +) void { log("setData()", .{}); js.dataSetCached(this.this_value, globalObject, value); - return true; } pub fn getReadyState( @@ -1132,20 +1131,19 @@ pub fn getBinaryType( }; } -pub fn setBinaryType(this: *ServerWebSocket, globalThis: *JSC.JSGlobalObject, value: JSC.JSValue) callconv(.C) bool { +pub fn setBinaryType(this: *ServerWebSocket, globalThis: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!void { log("setBinaryType()", .{}); - const btype = JSC.ArrayBuffer.BinaryType.fromJSValue(globalThis, value) catch return false; + const btype = try JSC.ArrayBuffer.BinaryType.fromJSValue(globalThis, value); switch (btype orelse // some other value which we don't support .Float64Array) { .ArrayBuffer, .Buffer, .Uint8Array => |val| { this.flags.binary_type = val; - return true; + return; }, else => { - globalThis.throw("binaryType must be either \"uint8array\" or \"arraybuffer\" or \"nodebuffer\"", .{}) catch {}; - return false; + return globalThis.throw("binaryType must be either \"uint8array\" or \"arraybuffer\" or \"nodebuffer\"", .{}); }, } } diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index 59bd982702..af9a78fcfd 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -455,7 +455,7 @@ JSC_DEFINE_HOST_FUNCTION(functionBunSleep, } JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure()); - Bun__Timer__setTimeout(globalObject, JSValue::encode(promise), JSC::JSValue::encode(millisecondsValue), {}, Bun::CountdownOverflowBehavior::Clamp); + Bun__Timer__sleep(globalObject, JSValue::encode(promise), JSC::JSValue::encode(millisecondsValue)); return JSC::JSValue::encode(promise); } diff --git a/src/bun.js/bindings/JSPromise.zig b/src/bun.js/bindings/JSPromise.zig index 6249b5d408..0d1676d416 100644 --- a/src/bun.js/bindings/JSPromise.zig +++ b/src/bun.js/bindings/JSPromise.zig @@ -5,6 +5,7 @@ const JSValue = JSC.JSValue; const JSGlobalObject = JSC.JSGlobalObject; const VM = JSC.VM; const String = bun.String; +const JSError = bun.JSError; pub const JSPromise = opaque { pub const Status = enum(u32) { @@ -267,7 +268,7 @@ pub const JSPromise = opaque { JSC__JSPromise__resolve(this, globalThis, value); } - pub fn reject(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) void { + pub fn reject(this: *JSPromise, globalThis: *JSGlobalObject, value: JSError!JSValue) void { if (comptime bun.Environment.isDebug) { const loop = JSC.VirtualMachine.get().eventLoop(); loop.debug.js_call_count_outside_tick_queue += @as(usize, @intFromBool(!loop.debug.is_inside_tick_queue)); @@ -276,7 +277,9 @@ pub const JSPromise = opaque { } } - JSC__JSPromise__reject(this, globalThis, value); + const err = value catch |err| globalThis.takeException(err); + + JSC__JSPromise__reject(this, globalThis, err); } pub fn rejectAsHandled(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) void { diff --git a/src/bun.js/bindings/NodeTimerObject.cpp b/src/bun.js/bindings/NodeTimerObject.cpp index 5b41011b73..0840f62528 100644 --- a/src/bun.js/bindings/NodeTimerObject.cpp +++ b/src/bun.js/bindings/NodeTimerObject.cpp @@ -16,96 +16,72 @@ namespace Bun { using namespace JSC; -template -void callInternal(T* timeout, JSGlobalObject* globalObject) +static bool call(JSGlobalObject* globalObject, JSValue timerObject, JSValue callbackValue, JSValue argumentsValue) { - static_assert(std::is_same_v || std::is_same_v, - "wrong type passed to callInternal"); - auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue callbackValue = timeout->m_callback.get(); - JSCell* callbackCell = callbackValue.isCell() ? callbackValue.asCell() : nullptr; - if (!callbackCell) { - Bun__reportUnhandledError(globalObject, JSValue::encode(createNotAFunctionError(globalObject, callbackValue))); - return; - } - JSValue restoreAsyncContext {}; JSC::InternalFieldTuple* asyncContextData = nullptr; - if (auto* wrapper = jsDynamicCast(callbackCell)) { - callbackCell = wrapper->callback.get().asCell(); + if (auto* wrapper = jsDynamicCast(callbackValue)) { + callbackValue = wrapper->callback.get(); asyncContextData = globalObject->m_asyncContextData.get(); restoreAsyncContext = asyncContextData->getInternalField(0); asyncContextData->putInternalField(vm, 0, wrapper->context.get()); } - switch (callbackCell->type()) { - case JSC::JSPromiseType: { + if (auto* promise = jsDynamicCast(callbackValue)) { // This was a Bun.sleep() call - auto promise = jsCast(callbackCell); promise->resolve(globalObject, jsUndefined()); - break; - } - - default: { - auto callData = JSC::getCallData(callbackCell); + } else { + auto callData = JSC::getCallData(callbackValue); if (callData.type == CallData::Type::None) { Bun__reportUnhandledError(globalObject, JSValue::encode(createNotAFunctionError(globalObject, callbackValue))); - return; + return true; } MarkedArgumentBuffer args; - if (timeout->m_arguments) { - JSValue argumentsValue = timeout->m_arguments.get(); - auto* butterfly = jsDynamicCast(argumentsValue); - + if (auto* butterfly = jsDynamicCast(argumentsValue)) { // If it's a JSImmutableButterfly, there is more than 1 argument. - if (butterfly) { - unsigned length = butterfly->length(); - args.ensureCapacity(length); - for (unsigned i = 0; i < length; ++i) { - args.append(butterfly->get(i)); - } - } else { - // Otherwise, it's a single argument. - args.append(argumentsValue); + unsigned length = butterfly->length(); + args.ensureCapacity(length); + for (unsigned i = 0; i < length; ++i) { + args.append(butterfly->get(i)); } + } else if (!argumentsValue.isUndefined()) { + // Otherwise, it's a single argument. + args.append(argumentsValue); } - JSC::profiledCall(globalObject, ProfilingReason::API, JSValue(callbackCell), JSC::getCallData(callbackCell), timeout, ArgList(args)); - break; - } + JSC::profiledCall(globalObject, ProfilingReason::API, callbackValue, callData, timerObject, args); } + bool hadException = false; + if (UNLIKELY(scope.exception())) { auto* exception = scope.exception(); scope.clearException(); Bun__reportUnhandledError(globalObject, JSValue::encode(exception)); + hadException = true; } if (asyncContextData) { asyncContextData->putInternalField(vm, 0, restoreAsyncContext); } + + return hadException; } -extern "C" void Bun__JSTimeout__call(JSC::EncodedJSValue encodedTimeoutValue, JSC::JSGlobalObject* globalObject) +// Returns true if an exception was thrown. +extern "C" bool Bun__JSTimeout__call(JSGlobalObject* globalObject, EncodedJSValue timerObject, EncodedJSValue callbackValue, EncodedJSValue argumentsValue) { auto& vm = globalObject->vm(); if (UNLIKELY(vm.hasPendingTerminationException())) { - return; + return true; } - JSValue timeoutValue = JSValue::decode(encodedTimeoutValue); - if (auto* timeout = jsDynamicCast(timeoutValue)) { - return callInternal(timeout, globalObject); - } else if (auto* immediate = jsDynamicCast(timeoutValue)) { - return callInternal(immediate, globalObject); - } - - ASSERT_NOT_REACHED_WITH_MESSAGE("Object passed to Bun__JSTimeout__call is not a JSTimeout or a JSImmediate"); + return call(globalObject, JSValue::decode(timerObject), JSValue::decode(callbackValue), JSValue::decode(argumentsValue)); } } diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 9af6a67850..093e7b0f70 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -675,16 +675,9 @@ ZIG_DECL JSC::EncodedJSValue Bun__Timer__clearImmediate(JSC::JSGlobalObject* arg ZIG_DECL JSC::EncodedJSValue Bun__Timer__clearInterval(JSC::JSGlobalObject* arg0, JSC::EncodedJSValue JSValue1); ZIG_DECL JSC::EncodedJSValue Bun__Timer__clearTimeout(JSC::JSGlobalObject* arg0, JSC::EncodedJSValue JSValue1); ZIG_DECL int32_t Bun__Timer__getNextID(); -ZIG_DECL JSC::EncodedJSValue Bun__Timer__setInterval(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue countdown, JSC::EncodedJSValue arguments); -namespace Bun { -enum class CountdownOverflowBehavior : uint8_t { - // If the countdown overflows the range of int32_t, use a countdown of 1ms instead. Behavior of `setTimeout` and friends. - OneMs, - // If the countdown overflows the range of int32_t, clamp to the nearest value within the range. Behavior of `Bun.sleep`. - Clamp, -}; -} // namespace Bun -ZIG_DECL JSC::EncodedJSValue Bun__Timer__setTimeout(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue countdown, JSC::EncodedJSValue arguments, Bun::CountdownOverflowBehavior behavior); +ZIG_DECL JSC::EncodedJSValue Bun__Timer__setInterval(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue arguments, JSC::EncodedJSValue countdown); +ZIG_DECL JSC::EncodedJSValue Bun__Timer__setTimeout(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue arguments, JSC::EncodedJSValue countdown); +ZIG_DECL JSC::EncodedJSValue Bun__Timer__sleep(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue promise, JSC::EncodedJSValue countdown); ZIG_DECL JSC::EncodedJSValue Bun__Timer__setImmediate(JSC::JSGlobalObject* globalThis, JSC::EncodedJSValue callback, JSC::EncodedJSValue arguments); #endif diff --git a/src/bun.js/bindings/node/NodeTimers.cpp b/src/bun.js/bindings/node/NodeTimers.cpp index a2b62431f2..7df2fecd11 100644 --- a/src/bun.js/bindings/node/NodeTimers.cpp +++ b/src/bun.js/bindings/node/NodeTimers.cpp @@ -13,7 +13,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, auto& vm = JSC::getVM(globalObject); JSC::JSValue job = callFrame->argument(0); JSC::JSValue num = callFrame->argument(1); - JSC::JSValue arguments = {}; + JSC::JSValue arguments = jsUndefined(); size_t argumentCount = callFrame->argumentCount(); auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); switch (argumentCount) { @@ -59,7 +59,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, } #endif - return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments), Bun::CountdownOverflowBehavior::OneMs); + return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(arguments), JSValue::encode(num)); } JSC_DEFINE_HOST_FUNCTION(functionSetInterval, @@ -68,7 +68,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetInterval, auto& vm = JSC::getVM(globalObject); JSC::JSValue job = callFrame->argument(0); JSC::JSValue num = callFrame->argument(1); - JSC::JSValue arguments = {}; + JSC::JSValue arguments = jsUndefined(); size_t argumentCount = callFrame->argumentCount(); auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); @@ -77,10 +77,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetInterval, Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "setInterval requires 1 argument (a function)"_s); return {}; } - case 1: { - num = jsNumber(0); - break; - } + case 1: case 2: { break; } @@ -118,7 +115,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetInterval, } #endif - return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments)); + return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(arguments), JSValue::encode(num)); } // https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate @@ -141,7 +138,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSetImmediate, return {}; } - JSC::JSValue arguments = {}; + JSC::JSValue arguments = jsUndefined(); switch (argCount) { case 0: case 1: { diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index b8c87c27b7..4ee693d7b9 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -902,6 +902,19 @@ pub const EventLoop = struct { this.entered_event_loop_count -= 1; } + pub fn exitMaybeDrainMicrotasks(this: *EventLoop, allow_drain_microtask: bool) void { + const count = this.entered_event_loop_count; + log("exit() = {d}", .{count - 1}); + + defer this.debug.exit(); + + if (allow_drain_microtask and count == 1 and !this.virtual_machine.is_inside_deferred_task_queue) { + this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc); + } + + this.entered_event_loop_count -= 1; + } + pub inline fn getVmImpl(this: *EventLoop) *VirtualMachine { return this.virtual_machine; } @@ -939,6 +952,13 @@ pub const EventLoop = struct { this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc); } + // should be called after exit() + pub fn maybeDrainMicrotasks(this: *EventLoop) void { + if (this.entered_event_loop_count == 0 and !this.virtual_machine.is_inside_deferred_task_queue) { + this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc); + } + } + /// When you call a JavaScript function from outside the event loop task /// queue /// @@ -1356,14 +1376,6 @@ pub const EventLoop = struct { var any: *RuntimeTranspilerStore = task.get(RuntimeTranspilerStore).?; any.drain(); }, - @field(Task.Tag, @typeName(TimeoutObject)) => { - var any: *TimeoutObject = task.get(TimeoutObject).?; - any.runImmediateTask(virtual_machine); - }, - @field(Task.Tag, @typeName(ImmediateObject)) => { - var any: *ImmediateObject = task.get(ImmediateObject).?; - any.runImmediateTask(virtual_machine); - }, @field(Task.Tag, @typeName(ServerAllConnectionsClosedTask)) => { var any: *ServerAllConnectionsClosedTask = task.get(ServerAllConnectionsClosedTask).?; any.runFromJSThread(virtual_machine); @@ -1404,17 +1416,19 @@ pub const EventLoop = struct { } pub fn tickImmediateTasks(this: *EventLoop, virtual_machine: *VirtualMachine) void { - var global = this.global; - const global_vm = global.vm(); - var to_run_now = this.immediate_tasks; this.immediate_tasks = this.next_immediate_tasks; this.next_immediate_tasks = .{}; + var exception_thrown = false; for (to_run_now.items) |task| { - task.runImmediateTask(virtual_machine); - this.drainMicrotasksWithGlobal(global, global_vm); + exception_thrown = task.runImmediateTask(virtual_machine); + } + + // make sure microtasks are drained if the last task had an exception + if (exception_thrown) { + this.maybeDrainMicrotasks(); } if (this.next_immediate_tasks.capacity > 0) { diff --git a/src/bun.js/jsc/host_fn.zig b/src/bun.js/jsc/host_fn.zig index bf78643df1..2ef4f032e4 100644 --- a/src/bun.js/jsc/host_fn.zig +++ b/src/bun.js/jsc/host_fn.zig @@ -35,18 +35,14 @@ pub fn toJSHostFn(comptime functionToWrap: JSHostFnZig) JSHostFn { pub fn toJSHostFnWithContext(comptime ContextType: type, comptime Function: JSHostFnZigWithContext(ContextType)) JSHostFunctionTypeWithContext(ContextType) { return struct { pub fn function(ctx: *ContextType, globalThis: *JSGlobalObject, callframe: *CallFrame) callconv(jsc.conv) JSValue { - if (Environment.allow_assert and Environment.is_canary) { - const value = Function(ctx, globalThis, callframe) catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => globalThis.throwOutOfMemoryValue(), - }; - debugExceptionAssertion(globalThis, value, Function); - return value; - } - return @call(.always_inline, Function, .{ ctx, globalThis, callframe }) catch |err| switch (err) { + const value = Function(ctx, globalThis, callframe) catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => globalThis.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(globalThis, value, Function); + } + return value; } }.function; } @@ -74,19 +70,26 @@ fn debugExceptionAssertion(globalThis: *JSGlobalObject, value: JSValue, comptime bun.assert((value == .zero) == globalThis.hasException()); } +pub fn toJSHostSetterValue(globalThis: *JSGlobalObject, value: error{ OutOfMemory, JSError }!void) bool { + value catch |err| switch (err) { + error.JSError => return false, + error.OutOfMemory => { + _ = globalThis.throwOutOfMemoryValue(); + return false; + }, + }; + return true; +} + pub fn toJSHostValue(globalThis: *JSGlobalObject, value: error{ OutOfMemory, JSError }!JSValue) JSValue { - if (Environment.allow_assert and Environment.is_canary) { - const normal = value catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => globalThis.throwOutOfMemoryValue(), - }; - bun.assert((normal == .zero) == globalThis.hasException()); - return normal; - } - return value catch |err| switch (err) { + const normal = value catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => globalThis.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(globalThis, normal, toJSHostValue); + } + return normal; } const ParsedHostFunctionErrorSet = struct { @@ -116,18 +119,14 @@ pub fn wrap1(comptime func: anytype) @"return": { const p = @typeInfo(@TypeOf(func)).@"fn".params; return struct { pub fn wrapped(arg0: p[0].type.?) callconv(.c) JSValue { - if (Environment.allow_assert and Environment.isDebug) { - const value = func(arg0) catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => arg0.throwOutOfMemoryValue(), - }; - debugExceptionAssertion(arg0, value, func); - return value; - } - return @call(.always_inline, func, .{arg0}) catch |err| switch (err) { + const value = func(arg0) catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => arg0.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(arg0, value, func); + } + return value; } }.wrapped; } @@ -139,18 +138,14 @@ pub fn wrap2(comptime func: anytype) @"return": { const p = @typeInfo(@TypeOf(func)).@"fn".params; return struct { pub fn wrapped(arg0: p[0].type.?, arg1: p[1].type.?) callconv(.c) JSValue { - if (Environment.allow_assert and Environment.isDebug) { - const value = func(arg0, arg1) catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => arg0.throwOutOfMemoryValue(), - }; - debugExceptionAssertion(arg0, value, func); - return value; - } - return @call(.always_inline, func, .{ arg0, arg1 }) catch |err| switch (err) { + const value = func(arg0, arg1) catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => arg0.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(arg0, value, func); + } + return value; } }.wrapped; } @@ -162,18 +157,14 @@ pub fn wrap3(comptime func: anytype) @"return": { const p = @typeInfo(@TypeOf(func)).@"fn".params; return struct { pub fn wrapped(arg0: p[0].type.?, arg1: p[1].type.?, arg2: p[2].type.?) callconv(.c) JSValue { - if (Environment.allow_assert and Environment.isDebug) { - const value = func(arg0, arg1, arg2) catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => arg0.throwOutOfMemoryValue(), - }; - debugExceptionAssertion(arg0, value, func); - return value; - } - return @call(.always_inline, func, .{ arg0, arg1, arg2 }) catch |err| switch (err) { + const value = func(arg0, arg1, arg2) catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => arg0.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(arg0, value, func); + } + return value; } }.wrapped; } @@ -185,18 +176,14 @@ pub fn wrap4(comptime func: anytype) @"return": { const p = @typeInfo(@TypeOf(func)).@"fn".params; return struct { pub fn wrapped(arg0: p[0].type.?, arg1: p[1].type.?, arg2: p[2].type.?, arg3: p[3].type.?) callconv(.c) JSValue { - if (Environment.allow_assert and Environment.isDebug) { - const value = func(arg0, arg1, arg2, arg3) catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => arg0.throwOutOfMemoryValue(), - }; - debugExceptionAssertion(arg0, value, func); - return value; - } - return @call(.always_inline, func, .{ arg0, arg1, arg2, arg3 }) catch |err| switch (err) { + const value = func(arg0, arg1, arg2, arg3) catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => arg0.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(arg0, value, func); + } + return value; } }.wrapped; } @@ -208,18 +195,14 @@ pub fn wrap5(comptime func: anytype) @"return": { const p = @typeInfo(@TypeOf(func)).@"fn".params; return struct { pub fn wrapped(arg0: p[0].type.?, arg1: p[1].type.?, arg2: p[2].type.?, arg3: p[3].type.?, arg4: p[4].type.?) callconv(.c) JSValue { - if (Environment.allow_assert and Environment.isDebug) { - const value = func(arg0, arg1, arg2, arg3, arg4) catch |err| switch (err) { - error.JSError => .zero, - error.OutOfMemory => arg0.throwOutOfMemoryValue(), - }; - debugExceptionAssertion(arg0, value, func); - return value; - } - return @call(.always_inline, func, .{ arg0, arg1, arg2, arg3, arg4 }) catch |err| switch (err) { + const value = func(arg0, arg1, arg2, arg3, arg4) catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => arg0.throwOutOfMemoryValue(), }; + if (Environment.allow_assert and Environment.is_canary) { + debugExceptionAssertion(arg0, value, func); + } + return value; } }.wrapped; } diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index 6fbdc16716..5a6a1b5226 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -180,13 +180,18 @@ export default [ setter: "set_idleTimeout", this: true, }, + _repeat: { + getter: "get_repeat", + setter: "set_repeat", + this: true, + }, ["@@dispose"]: { fn: "dispose", length: 0, invalidThisBehavior: InvalidThisBehavior.NoOp, }, }, - values: ["arguments", "callback", "idleTimeout"], + values: ["arguments", "callback", "idleTimeout", "repeat"], }), define({ name: "Immediate", diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index 1811a1a13f..9cab715fa7 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -247,11 +247,10 @@ pub fn CompressionStream(comptime T: type) type { this.stream.close(); } - pub fn setOnError(_: *T, this_value: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bool { + pub fn setOnError(_: *T, this_value: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { if (value.isFunction()) { T.js.errorCallbackSetCached(this_value, globalObject, value); } - return true; } pub fn getOnError(_: *T, this_value: JSC.JSValue, _: *JSC.JSGlobalObject) JSC.JSValue { diff --git a/src/bun.js/test/expect.zig b/src/bun.js/test/expect.zig index c60eb84ab4..452ec50811 100644 --- a/src/bun.js/test/expect.zig +++ b/src/bun.js/test/expect.zig @@ -169,22 +169,22 @@ pub const Expect = struct { return thisValue; } - pub fn getResolves(this: *Expect, thisValue: JSValue, globalThis: *JSGlobalObject) JSValue { + pub fn getResolves(this: *Expect, thisValue: JSValue, globalThis: *JSGlobalObject) bun.JSError!JSValue { this.flags.promise = switch (this.flags.promise) { .resolves, .none => .resolves, .rejects => { - return globalThis.throw("Cannot chain .resolves() after .rejects()", .{}) catch .zero; + return globalThis.throw("Cannot chain .resolves() after .rejects()", .{}); }, }; return thisValue; } - pub fn getRejects(this: *Expect, thisValue: JSValue, globalThis: *JSGlobalObject) JSValue { + pub fn getRejects(this: *Expect, thisValue: JSValue, globalThis: *JSGlobalObject) bun.JSError!JSValue { this.flags.promise = switch (this.flags.promise) { .none, .rejects => .rejects, .resolves => { - return globalThis.throw("Cannot chain .rejects() after .resolves()", .{}) catch .zero; + return globalThis.throw("Cannot chain .rejects() after .resolves()", .{}); }, }; @@ -4637,15 +4637,15 @@ pub const Expect = struct { pub const toHaveLastReturnedWith = notImplementedJSCFn; pub const toHaveNthReturnedWith = notImplementedJSCFn; - pub fn getStaticNot(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) JSValue { + pub fn getStaticNot(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) bun.JSError!JSValue { return ExpectStatic.create(globalThis, .{ .not = true }); } - pub fn getStaticResolvesTo(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) JSValue { + pub fn getStaticResolvesTo(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) bun.JSError!JSValue { return ExpectStatic.create(globalThis, .{ .promise = .resolves }); } - pub fn getStaticRejectsTo(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) JSValue { + pub fn getStaticRejectsTo(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) bun.JSError!JSValue { return ExpectStatic.create(globalThis, .{ .promise = .rejects }); } @@ -5026,10 +5026,8 @@ pub const ExpectStatic = struct { VirtualMachine.get().allocator.destroy(this); } - pub fn create(globalThis: *JSGlobalObject, flags: Expect.Flags) JSValue { - var expect = globalThis.bunVM().allocator.create(ExpectStatic) catch { - return globalThis.throwOutOfMemoryValue(); - }; + pub fn create(globalThis: *JSGlobalObject, flags: Expect.Flags) bun.JSError!JSValue { + var expect = try globalThis.bunVM().allocator.create(ExpectStatic); expect.flags = flags; const value = expect.toJS(globalThis); @@ -5037,27 +5035,27 @@ pub const ExpectStatic = struct { return value; } - pub fn getNot(this: *ExpectStatic, _: JSValue, globalThis: *JSGlobalObject) JSValue { + pub fn getNot(this: *ExpectStatic, _: JSValue, globalThis: *JSGlobalObject) bun.JSError!JSValue { var flags = this.flags; flags.not = !this.flags.not; return create(globalThis, flags); } - pub fn getResolvesTo(this: *ExpectStatic, _: JSValue, globalThis: *JSGlobalObject) JSValue { + pub fn getResolvesTo(this: *ExpectStatic, _: JSValue, globalThis: *JSGlobalObject) bun.JSError!JSValue { var flags = this.flags; - if (flags.promise != .none) return asyncChainingError(globalThis, flags, "resolvesTo") catch .zero; + if (flags.promise != .none) return asyncChainingError(globalThis, flags, "resolvesTo"); flags.promise = .resolves; return create(globalThis, flags); } - pub fn getRejectsTo(this: *ExpectStatic, _: JSValue, globalThis: *JSGlobalObject) JSValue { + pub fn getRejectsTo(this: *ExpectStatic, _: JSValue, globalThis: *JSGlobalObject) bun.JSError!JSValue { var flags = this.flags; - if (flags.promise != .none) return asyncChainingError(globalThis, flags, "rejectsTo") catch .zero; + if (flags.promise != .none) return asyncChainingError(globalThis, flags, "rejectsTo"); flags.promise = .rejects; return create(globalThis, flags); } - fn asyncChainingError(globalThis: *JSGlobalObject, flags: Expect.Flags, name: string) bun.JSError!JSValue { + fn asyncChainingError(globalThis: *JSGlobalObject, flags: Expect.Flags, name: string) bun.JSError { @branchHint(.cold); const str = switch (flags.promise) { .resolves => "resolvesTo", diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 2b4ac5c95f..62ffc57bfb 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -159,7 +159,7 @@ pub const WebWorker = struct { } var resolved_entry_point: bun.resolver.Result = parent.transpiler.resolveEntryPoint(str) catch { - const out = logger.toJS(parent.global, bun.default_allocator, "Error resolving Worker entry point").toBunString(parent.global) catch { + const out = (logger.toJS(parent.global, bun.default_allocator, "Error resolving Worker entry point") catch bun.outOfMemory()).toBunString(parent.global) catch { error_message.* = bun.String.static("unexpected exception"); return null; }; @@ -336,7 +336,7 @@ pub const WebWorker = struct { JSC.markBinding(@src()); var vm = this.vm orelse return; if (vm.log.msgs.items.len == 0) return; - const err = vm.log.toJS(vm.global, bun.default_allocator, "Error in worker"); + const err = vm.log.toJS(vm.global, bun.default_allocator, "Error in worker") catch bun.outOfMemory(); const str = err.toBunString(vm.global) catch @panic("unexpected exception"); defer str.deref(); WebWorker__dispatchError(vm.global, this.cpp_worker, str, err); diff --git a/src/bun.js/webcore/Blob.zig b/src/bun.js/webcore/Blob.zig index b83f8d48d2..6f89001da3 100644 --- a/src/bun.js/webcore/Blob.zig +++ b/src/bun.js/webcore/Blob.zig @@ -2919,35 +2919,23 @@ pub fn setName( jsThis: JSC.JSValue, globalThis: *JSC.JSGlobalObject, value: JSValue, - - // TODO: support JSError for getters/setters -) bool { +) JSError!void { // by default we don't have a name so lets allow it to be set undefined if (value.isEmptyOrUndefinedOrNull()) { this.name.deref(); this.name = bun.String.dead; js.nameSetCached(jsThis, globalThis, value); - return true; + return; } if (value.isString()) { const old_name = this.name; - this.name = bun.String.fromJS(value, globalThis) catch |err| { - switch (err) { - error.JSError => {}, - error.OutOfMemory => { - globalThis.throwOutOfMemory() catch {}; - }, - } - this.name = bun.String.empty; - return false; - }; + errdefer this.name = bun.String.empty; + this.name = try bun.String.fromJS(value, globalThis); // We don't need to increment the reference count since tryFromJS already did it. js.nameSetCached(jsThis, globalThis, value); old_name.deref(); - return true; } - return false; } pub fn getFileName( diff --git a/src/bun.js/webcore/ReadableStream.zig b/src/bun.js/webcore/ReadableStream.zig index 17c339743d..1161224fb0 100644 --- a/src/bun.js/webcore/ReadableStream.zig +++ b/src/bun.js/webcore/ReadableStream.zig @@ -676,41 +676,37 @@ pub fn NewSource( return .undefined; } - pub fn setOnCloseFromJS(this: *ReadableStreamSourceType, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bool { + pub fn setOnCloseFromJS(this: *ReadableStreamSourceType, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!void { JSC.markBinding(@src()); this.close_handler = JSReadableStreamSource.onClose; this.globalThis = globalObject; if (value.isUndefined()) { this.close_jsvalue.deinit(); - return true; + return; } if (!value.isCallable()) { - globalObject.throwInvalidArgumentType("ReadableStreamSource", "onclose", "function") catch {}; - return false; + return globalObject.throwInvalidArgumentType("ReadableStreamSource", "onclose", "function"); } const cb = value.withAsyncContextIfNeeded(globalObject); this.close_jsvalue.set(globalObject, cb); - return true; } - pub fn setOnDrainFromJS(this: *ReadableStreamSourceType, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bool { + pub fn setOnDrainFromJS(this: *ReadableStreamSourceType, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!void { JSC.markBinding(@src()); this.globalThis = globalObject; if (value.isUndefined()) { js.onDrainCallbackSetCached(this.this_jsvalue, globalObject, .undefined); - return true; + return; } if (!value.isCallable()) { - globalObject.throwInvalidArgumentType("ReadableStreamSource", "onDrain", "function") catch {}; - return false; + return globalObject.throwInvalidArgumentType("ReadableStreamSource", "onDrain", "function"); } const cb = value.withAsyncContextIfNeeded(globalObject); js.onDrainCallbackSetCached(this.this_jsvalue, globalObject, cb); - return true; } pub fn getOnCloseFromJS(this: *ReadableStreamSourceType, globalObject: *JSC.JSGlobalObject) JSC.JSValue { diff --git a/src/bun.js/webcore/Request.zig b/src/bun.js/webcore/Request.zig index 066f0becf0..6be9abcf89 100644 --- a/src/bun.js/webcore/Request.zig +++ b/src/bun.js/webcore/Request.zig @@ -366,12 +366,8 @@ pub fn getReferrerPolicy( ) JSC.JSValue { return ZigString.init("").toJS(globalThis); } -pub fn getUrl(this: *Request, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - this.ensureURL() catch { - globalObject.throw("Failed to join URL", .{}) catch {}; // TODO: propagate - return .zero; - }; - +pub fn getUrl(this: *Request, globalObject: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { + try this.ensureURL(); return this.url.toJS(globalObject); } @@ -403,7 +399,7 @@ pub fn getProtocol(this: *const Request) []const u8 { return "http://"; } -pub fn ensureURL(this: *Request) !void { +pub fn ensureURL(this: *Request) bun.OOM!void { if (!this.url.isEmpty()) return; if (this.request_context.getRequest()) |req| { @@ -463,11 +459,11 @@ pub fn ensureURL(this: *Request) !void { }; } else { // slow path - const temp_url = std.fmt.allocPrint(bun.default_allocator, "{s}{any}{s}", .{ + const temp_url = try std.fmt.allocPrint(bun.default_allocator, "{s}{any}{s}", .{ this.getProtocol(), fmt, req_url, - }) catch bun.outOfMemory(); + }); defer bun.default_allocator.free(temp_url); this.url = bun.String.createUTF8(temp_url); } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index eba547f486..1c1561133d 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2085,7 +2085,9 @@ pub const BundleV2 = struct { root_obj.put( globalThis, JSC.ZigString.static("logs"), - this.log.toJSArray(globalThis, bun.default_allocator), + this.log.toJSArray(globalThis, bun.default_allocator) catch |err| { + return promise.reject(globalThis, err); + }, ); promise.resolve(globalThis, root_obj); }, @@ -2150,7 +2152,9 @@ pub const BundleV2 = struct { root_obj.put( globalThis, JSC.ZigString.static("logs"), - this.log.toJSArray(globalThis, bun.default_allocator), + this.log.toJSArray(globalThis, bun.default_allocator) catch |err| { + return promise.reject(globalThis, err); + }, ); promise.resolve(globalThis, root_obj); }, diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index fa764a40fc..5c74f9a981 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -82,9 +82,9 @@ function ZigDOMJITArgType(type) { return { ["bool"]: "bool", ["int"]: "i32", - ["JSUint8Array"]: "*JSC.JSUint8Array", - ["JSString"]: "*JSC.JSString", - ["JSValue"]: "JSC.JSValue", + ["JSUint8Array"]: "*jsc.JSUint8Array", + ["JSString"]: "*jsc.JSString", + ["JSValue"]: "jsc.JSValue", }[type]; } @@ -93,9 +93,9 @@ function ZigDOMJITArgTypeDefinition(type, index) { } function ZigDOMJITFunctionType(thisName, { args, returns }) { - return `fn (*${thisName}, *JSC.JSGlobalObject, ${args + return `fn (*${thisName}, *jsc.JSGlobalObject, ${args .map(ZigDOMJITArgType) - .join(", ")}) callconv(JSC.conv) ${ZigDOMJITArgType("JSValue")}`; + .join(", ")}) callconv(jsc.conv) ${ZigDOMJITArgType("JSValue")}`; } function DOMJITReturnType(type) { @@ -813,17 +813,17 @@ function renderCallbacksZig(typeName, callbacks: Record) { var out = "\n" + `pub const Callbacks = struct { - instance: JSC.JSValue,` + + instance: jsc.JSValue,` + "\n"; for (const name in callbacks) { const get = symbolName(typeName, "_callback_get_" + name); const set = symbolName(typeName, "_callback_set_" + name); out += ` - extern fn ${get}(JSC.JSValue) callconv(JSC.conv) JSC.JSValue; - extern fn ${set}(JSC.JSValue, JSC.JSValue) callconv(JSC.conv) void; - pub const ${pascalCase(name)}Callback = JSC.Codegen.CallbackWrapper(${get}, ${set}); - pub fn ${camelCase(name)}(cb: @This(), thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, args: []const JSC.JSValue) ?JSC.JSValue { + extern fn ${get}(jsc.JSValue) callconv(jsc.conv) jsc.JSValue; + extern fn ${set}(jsc.JSValue, jsc.JSValue) callconv(jsc.conv) void; + pub const ${pascalCase(name)}Callback = jsc.Codegen.CallbackWrapper(${get}, ${set}); + pub fn ${camelCase(name)}(cb: @This(), thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject, args: []const jsc.JSValue) ?jsc.JSValue { return ${pascalCase(name)}Callback.call(.{.instance = cb.instance}, thisValue, globalObject, args); } `; @@ -832,13 +832,13 @@ function renderCallbacksZig(typeName, callbacks: Record) { out = out.trim(); out += ` - extern fn ${symbolName(typeName, "_setAllCallbacks")}(JSC.JSValue, ${Object.keys(callbacks) - .map((a, i) => `callback${i}: JSC.JSValue`) - .join(", ")}) callconv(JSC.conv) void; + extern fn ${symbolName(typeName, "_setAllCallbacks")}(jsc.JSValue, ${Object.keys(callbacks) + .map((a, i) => `callback${i}: jsc.JSValue`) + .join(", ")}) callconv(jsc.conv) void; pub inline fn set(this: @This(), values: struct { ${Object.keys(callbacks) - .map((name, i) => `${camelCase(name)}: JSC.JSValue = .zero,`) + .map((name, i) => `${camelCase(name)}: jsc.JSValue = .zero,`) .join("\n")} }) void { ${symbolName(typeName, "_setAllCallbacks")}(this.instance, ${Object.keys(callbacks) @@ -851,7 +851,7 @@ function renderCallbacksZig(typeName, callbacks: Record) { out += ` - pub fn callbacks(_: *const ${typeName}, instance: JSC.JSValue) Callbacks { + pub fn callbacks(_: *const ${typeName}, instance: jsc.JSValue) Callbacks { return .{.instance = instance }; } @@ -1828,21 +1828,21 @@ function generateZig( .filter(([name, { cache, internal }]) => (cache && typeof cache !== "string") || internal) .map( ([name]) => - `extern fn ${protoSymbolName(typeName, name)}SetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(JSC.conv) void; + `extern fn ${protoSymbolName(typeName, name)}SetCachedValue(jsc.JSValue, *jsc.JSGlobalObject, jsc.JSValue) callconv(jsc.conv) void; - extern fn ${protoSymbolName(typeName, name)}GetCachedValue(JSC.JSValue) callconv(JSC.conv) JSC.JSValue; + extern fn ${protoSymbolName(typeName, name)}GetCachedValue(jsc.JSValue) callconv(jsc.conv) jsc.JSValue; /// \`${typeName}.${name}\` setter /// This value will be visited by the garbage collector. - pub fn ${name}SetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { - JSC.markBinding(@src()); + pub fn ${name}SetCached(thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject, value: jsc.JSValue) void { + jsc.markBinding(@src()); ${protoSymbolName(typeName, name)}SetCachedValue(thisValue, globalObject, value); } /// \`${typeName}.${name}\` getter /// This value will be visited by the garbage collector. - pub fn ${name}GetCached(thisValue: JSC.JSValue) ?JSC.JSValue { - JSC.markBinding(@src()); + pub fn ${name}GetCached(thisValue: jsc.JSValue) ?jsc.JSValue { + jsc.markBinding(@src()); const result = ${protoSymbolName(typeName, name)}GetCachedValue(thisValue); if (result == .zero) return null; @@ -1868,7 +1868,7 @@ const JavaScriptCoreBindings = struct { if (memoryCost) { exports.set("memoryCost", symbolName(typeName, "memoryCost")); output += ` - pub fn ${symbolName(typeName, "memoryCost")}(thisValue: *${typeName}) callconv(JSC.conv) usize { + pub fn ${symbolName(typeName, "memoryCost")}(thisValue: *${typeName}) callconv(jsc.conv) usize { return @call(bun.callmod_inline, ${typeName}.memoryCost, .{thisValue}); } `; @@ -1877,7 +1877,7 @@ const JavaScriptCoreBindings = struct { if (estimatedSize) { exports.set("estimatedSize", symbolName(typeName, "estimatedSize")); output += ` - pub fn ${symbolName(typeName, "estimatedSize")}(thisValue: *${typeName}) callconv(JSC.conv) usize { + pub fn ${symbolName(typeName, "estimatedSize")}(thisValue: *${typeName}) callconv(jsc.conv) usize { return @call(bun.callmod_inline, ${typeName}.estimatedSize, .{thisValue}); } `; @@ -1890,7 +1890,7 @@ const JavaScriptCoreBindings = struct { if (hasPendingActivity) { exports.set("hasPendingActivity", symbolName(typeName, "hasPendingActivity")); output += ` - pub fn ${symbolName(typeName, "hasPendingActivity")}(thisValue: *${typeName}) callconv(JSC.conv) bool { + pub fn ${symbolName(typeName, "hasPendingActivity")}(thisValue: *${typeName}) callconv(jsc.conv) bool { return @call(bun.callmod_inline, ${typeName}.hasPendingActivity, .{thisValue}); } `; @@ -1899,7 +1899,7 @@ const JavaScriptCoreBindings = struct { if (finalize) { exports.set("finalize", classSymbolName(typeName, "finalize")); output += ` - pub fn ${classSymbolName(typeName, "finalize")}(thisValue: *${typeName}) callconv(JSC.conv) void { + pub fn ${classSymbolName(typeName, "finalize")}(thisValue: *${typeName}) callconv(jsc.conv) void { if (comptime Environment.enable_logs) log_zig_finalize("${typeName}", thisValue); @call(.always_inline, ${typeName}.finalize, .{thisValue}); } @@ -1909,7 +1909,7 @@ const JavaScriptCoreBindings = struct { if (construct && !noConstructor) { exports.set("construct", classSymbolName(typeName, "construct")); output += ` - pub fn ${classSymbolName(typeName, "construct")}(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(JSC.conv) ?*anyopaque { + pub fn ${classSymbolName(typeName, "construct")}(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) ?*anyopaque { if (comptime Environment.enable_logs) log_zig_constructor("${typeName}", callFrame); return @as(*${typeName}, ${typeName}.constructor(globalObject, callFrame) catch |err| switch (err) { error.JSError => return null, @@ -1925,9 +1925,9 @@ const JavaScriptCoreBindings = struct { if (call) { exports.set("call", classSymbolName(typeName, "call")); output += ` - pub fn ${classSymbolName(typeName, "call")}(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue { + pub fn ${classSymbolName(typeName, "call")}(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_call("${typeName}", callFrame); - return @call(.always_inline, JSC.toJSHostFn(${typeName}.call), .{globalObject, callFrame}); + return @call(.always_inline, jsc.toJSHostFn(${typeName}.call), .{globalObject, callFrame}); } `; } @@ -1935,7 +1935,7 @@ const JavaScriptCoreBindings = struct { if (getInternalProperties) { exports.set("getInternalProperties", classSymbolName(typeName, "getInternalProperties")); output += ` - pub fn ${classSymbolName(typeName, "getInternalProperties")}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, thisValue: JSC.JSValue) callconv(JSC.conv) JSC.JSValue { + pub fn ${classSymbolName(typeName, "getInternalProperties")}(thisValue: *${typeName}, globalObject: *jsc.JSGlobalObject, thisValue: jsc.JSValue) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_get_internal_properties("${typeName}"); return @call(.always_inline, ${typeName}.getInternalProperties, .{thisValue, globalObject, thisValue}); } @@ -1949,18 +1949,35 @@ const JavaScriptCoreBindings = struct { const names = exportNames(name); if (names.getter) { output += ` - pub fn ${names.getter}(this: *${typeName}, ${thisValue ? "thisValue: JSC.JSValue," : ""} globalObject: *JSC.JSGlobalObject) callconv(JSC.conv) JSC.JSValue { + pub fn ${names.getter}(this: *${typeName}, ${thisValue ? "thisValue: jsc.JSValue," : ""} globalObject: *jsc.JSGlobalObject) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_getter("${typeName}", "${name}"); - return @call(.always_inline, ${typeName}.${getter}, .{this, ${thisValue ? "thisValue," : ""} globalObject}); + return switch (@typeInfo(@typeInfo(@TypeOf(${typeName}.${getter})).@"fn".return_type.?)) { + .error_union => { + return @call(.always_inline, jsc.toJSHostValue, .{globalObject, @call(.always_inline, ${typeName}.${getter}, .{this, ${thisValue ? "thisValue," : ""} globalObject})}); + }, + else => @call(.always_inline, ${typeName}.${getter}, .{this, ${thisValue ? "thisValue," : ""} globalObject}), + }; } `; } if (names.setter) { output += ` - pub fn ${names.setter}(this: *${typeName}, ${thisValue ? "thisValue: JSC.JSValue," : ""} globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) callconv(JSC.conv) bool { + pub fn ${names.setter}(this: *${typeName}, ${thisValue ? "thisValue: jsc.JSValue," : ""} globalObject: *jsc.JSGlobalObject, value: jsc.JSValue) callconv(jsc.conv) bool { if (comptime Environment.enable_logs) log_zig_setter("${typeName}", "${name}", value); - return @call(.always_inline, ${typeName}.${setter}, .{this, ${thisValue ? "thisValue," : ""} globalObject, value}); + switch (@typeInfo(@typeInfo(@TypeOf(${typeName}.${setter})).@"fn".return_type.?)) { + .error_union => |error_union| { + if (error_union.payload != void) { + @compileError("Setter return type must be JSError!void or void"); + } + return @call(.always_inline, jsc.host_fn.toJSHostSetterValue, .{globalObject, @call(.always_inline, ${typeName}.${setter}, .{this, ${thisValue ? "thisValue," : ""} globalObject, value})}); + }, + .void => { + @call(.always_inline, ${typeName}.${setter}, .{this, ${thisValue ? "thisValue," : ""} globalObject, value}); + return true; + }, + else => @compileError("Setter return type must be JSError!void or void"), + } } `; } @@ -1969,18 +1986,18 @@ const JavaScriptCoreBindings = struct { if (names.DOMJIT) { const { args, returns } = DOMJIT; output += ` - pub fn ${names.DOMJIT}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, ${args + pub fn ${names.DOMJIT}(thisValue: *${typeName}, globalObject: *jsc.JSGlobalObject, ${args .map(ZigDOMJITArgTypeDefinition) - .join(", ")}) callconv(JSC.conv) JSC.JSValue { + .join(", ")}) callconv(jsc.conv) jsc.JSValue { return @call(bun.callmod_inline, ${typeName}.${DOMJITName(fn)}, .{thisValue, globalObject, ${args.map((_, i) => `arg${i}`).join(", ")}}); } `; } output += ` - pub fn ${names.fn}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame${proto[name].passThis ? ", js_this_value: JSC.JSValue" : ""}) callconv(JSC.conv) JSC.JSValue { + pub fn ${names.fn}(thisValue: *${typeName}, globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame${proto[name].passThis ? ", js_this_value: jsc.JSValue" : ""}) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_method("${typeName}", "${name}", callFrame); - return @call(.always_inline, JSC.toJSHostValue, .{globalObject, @call(.always_inline, ${typeName}.${fn}, .{thisValue, globalObject, callFrame${proto[name].passThis ? ", js_this_value" : ""}})}); + return @call(.always_inline, jsc.toJSHostValue, .{globalObject, @call(.always_inline, ${typeName}.${fn}, .{thisValue, globalObject, callFrame${proto[name].passThis ? ", js_this_value" : ""}})}); } `; } @@ -1994,16 +2011,23 @@ const JavaScriptCoreBindings = struct { const names = exportNames(name); if (names.getter) { output += ` - pub fn ${names.getter}(globalObject: *JSC.JSGlobalObject, ${thisValue ? "thisValue: JSC.JSValue," : ""} propertyName: JSC.JSValue) callconv(JSC.conv) JSC.JSValue { + pub fn ${names.getter}(globalObject: *jsc.JSGlobalObject, ${thisValue ? "thisValue: jsc.JSValue," : ""} propertyName: jsc.JSValue) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_class_getter("${typeName}", "${name}"); - return @call(.always_inline, ${typeName}.${getter}, .{globalObject, ${thisValue ? "thisValue," : ""} propertyName}); + return switch (@typeInfo(@typeInfo(@TypeOf(${typeName}.${getter})).@"fn".return_type.?)) { + .error_union => { + return @call(.always_inline, jsc.toJSHostValue, .{globalObject, @call(.always_inline, ${typeName}.${getter}, .{globalObject, ${thisValue ? "thisValue," : ""} propertyName})}); + }, + else => { + return @call(.always_inline, ${typeName}.${getter}, .{globalObject, ${thisValue ? "thisValue," : ""} propertyName}); + }, + }; } `; } if (names.setter) { output += ` - pub fn ${names.setter}(globalObject: *JSC.JSGlobalObject, thisValue: JSC.JSValue, target: JSC.JSValue) callconv(JSC.conv) bool { + pub fn ${names.setter}(globalObject: *jsc.JSGlobalObject, thisValue: jsc.JSValue, target: jsc.JSValue) callconv(jsc.conv) bool { if (comptime Environment.enable_logs) log_zig_class_setter("${typeName}", "${name}", target); return @call(.always_inline, ${typeName}.${setter || accessor.setter}, .{thisValue, globalObject, target}); } @@ -2015,9 +2039,9 @@ const JavaScriptCoreBindings = struct { const { args, returns } = DOMJIT; output += ` - pub fn ${names.DOMJIT}(globalObject: *JSC.JSGlobalObject, thisValue: JSC.JSValue, ${args + pub fn ${names.DOMJIT}(globalObject: *jsc.JSGlobalObject, thisValue: jsc.JSValue, ${args .map(ZigDOMJITArgTypeDefinition) - .join(", ")}) callconv(JSC.conv) JSC.JSValue { + .join(", ")}) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_class_domjit("${typeName}", "${name}"); return @call(.always_inline, ${typeName}.${DOMJITName(fn)}, .{thisValue, globalObject, ${args.map((_, i) => `arg${i}`).join(", ")}}); } @@ -2025,9 +2049,9 @@ const JavaScriptCoreBindings = struct { } output += ` - pub fn ${names.fn}(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(JSC.conv) JSC.JSValue { + pub fn ${names.fn}(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_class_method("${typeName}", "${name}", callFrame); - return @call(.always_inline, JSC.toJSHostFn(${typeName}.${fn}), .{globalObject, callFrame}); + return @call(.always_inline, jsc.toJSHostFn(${typeName}.${fn}), .{globalObject, callFrame}); } `; } @@ -2037,7 +2061,7 @@ const JavaScriptCoreBindings = struct { if (structuredClone) { exports.set("structuredClone", symbolName(typeName, "onStructuredCloneSerialize")); output += ` - pub fn ${symbolName(typeName, "onStructuredCloneSerialize")}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, ctx: *anyopaque, writeBytes: WriteBytesFn) callconv(JSC.conv) void { + pub fn ${symbolName(typeName, "onStructuredCloneSerialize")}(thisValue: *${typeName}, globalObject: *jsc.JSGlobalObject, ctx: *anyopaque, writeBytes: WriteBytesFn) callconv(jsc.conv) void { if (comptime Environment.enable_logs) log_zig_structured_clone_serialize("${typeName}"); @call(.always_inline, ${typeName}.onStructuredCloneSerialize, .{thisValue, globalObject, ctx, writeBytes}); } @@ -2046,7 +2070,7 @@ const JavaScriptCoreBindings = struct { if (typeof structuredClone === "object" && structuredClone.transferable) { exports.set("structuredClone_transferable", symbolName(typeName, "onStructuredCloneTransfer")); output += ` - pub fn ${exports.get("structuredClone_transferable")}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, ctx: *anyopaque, write: WriteBytesFn) callconv(JSC.conv) void { + pub fn ${exports.get("structuredClone_transferable")}(thisValue: *${typeName}, globalObject: *jsc.JSGlobalObject, ctx: *anyopaque, write: WriteBytesFn) callconv(jsc.conv) void { if (comptime Environment.enable_logs) log_zig_structured_clone_transfer("${typeName}"); @call(.always_inline, ${typeName}.onStructuredCloneTransfer, .{thisValue, globalObject, ctx, write}); } @@ -2056,14 +2080,14 @@ const JavaScriptCoreBindings = struct { exports.set("structuredCloneDeserialize", symbolName(typeName, "onStructuredCloneDeserialize")); output += ` - pub fn ${symbolName(typeName, "onStructuredCloneDeserialize")}(globalObject: *JSC.JSGlobalObject, ptr: [*]u8, end: [*]u8) callconv(JSC.conv) JSC.JSValue { + pub fn ${symbolName(typeName, "onStructuredCloneDeserialize")}(globalObject: *jsc.JSGlobalObject, ptr: [*]u8, end: [*]u8) callconv(jsc.conv) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_structured_clone_deserialize("${typeName}"); - return @call(.always_inline, JSC.toJSHostValue, .{ globalObject, @call(.always_inline, ${typeName}.onStructuredCloneDeserialize, .{globalObject, ptr, end}) }); + return @call(.always_inline, jsc.toJSHostValue, .{ globalObject, @call(.always_inline, ${typeName}.onStructuredCloneDeserialize, .{globalObject, ptr, end}) }); } `; } else { output += ` - pub fn ${symbolName(typeName, "onStructuredCloneSerialize")}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, ctx: *anyopaque, writeBytes: WriteBytesFn) callconv(JSC.conv) void { + pub fn ${symbolName(typeName, "onStructuredCloneSerialize")}(thisValue: *${typeName}, globalObject: *jsc.JSGlobalObject, ctx: *anyopaque, writeBytes: WriteBytesFn) callconv(jsc.conv) void { _ = thisValue; _ = globalObject; _ = ctx; @@ -2103,7 +2127,7 @@ pub const ${className(typeName)} = struct { /// Return the pointer to the wrapped object. /// If the object does not match the type, return null. - pub fn fromJS(value: JSC.JSValue) ?*${typeName} { + pub fn fromJS(value: jsc.JSValue) ?*${typeName} { if (comptime Environment.enable_logs) log_zig_from_js("${typeName}"); return ${symbolName(typeName, "fromJS")}(value); } @@ -2112,7 +2136,7 @@ pub const ${className(typeName)} = struct { /// If the object does not match the type, return null. /// If the object is a subclass of the type or has mutated the structure, return null. /// Note: this may return null for direct instances of the type if the user adds properties to the object. - pub fn fromJSDirect(value: JSC.JSValue) ?*${typeName} { + pub fn fromJSDirect(value: jsc.JSValue) ?*${typeName} { if (comptime Environment.enable_logs) log_zig_from_js_direct("${typeName}"); return ${symbolName(typeName, "fromJSDirect")}(value); } @@ -2124,7 +2148,7 @@ pub const ${className(typeName)} = struct { ? ` /// Get the ${typeName} constructor value. /// This loads lazily from the global object. - pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { + pub fn getConstructor(globalObject: *jsc.JSGlobalObject) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_get_constructor("${typeName}"); return ${symbolName(typeName, "getConstructor")}(globalObject); } @@ -2136,7 +2160,7 @@ pub const ${className(typeName)} = struct { !overridesToJS ? ` /// Create a new instance of ${typeName} - pub fn toJS(this: *${typeName}, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + pub fn toJS(this: *${typeName}, globalObject: *jsc.JSGlobalObject) jsc.JSValue { if (comptime Environment.enable_logs) log_zig_to_js("${typeName}"); if (comptime Environment.allow_assert) { const value__ = ${symbolName(typeName, "create")}(globalObject, this); @@ -2150,26 +2174,26 @@ pub const ${className(typeName)} = struct { } /// Modify the internal ptr to point to a new instance of ${typeName}. - pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*${typeName}) bool { - JSC.markBinding(@src()); + pub fn dangerouslySetPtr(value: jsc.JSValue, ptr: ?*${typeName}) bool { + jsc.markBinding(@src()); return ${symbolName(typeName, "dangerouslySetPtr")}(value, ptr); } /// Detach the ptr from the thisValue - pub fn detachPtr(_: *${typeName}, value: JSC.JSValue) void { - JSC.markBinding(@src()); + pub fn detachPtr(_: *${typeName}, value: jsc.JSValue) void { + jsc.markBinding(@src()); bun.assert(${symbolName(typeName, "dangerouslySetPtr")}(value, null)); } - extern fn ${symbolName(typeName, "fromJS")}(JSC.JSValue) callconv(JSC.conv) ?*${typeName}; - extern fn ${symbolName(typeName, "fromJSDirect")}(JSC.JSValue) callconv(JSC.conv) ?*${typeName}; - extern fn ${symbolName(typeName, "getConstructor")}(*JSC.JSGlobalObject) callconv(JSC.conv) JSC.JSValue; - extern fn ${symbolName(typeName, "create")}(globalObject: *JSC.JSGlobalObject, ptr: ?*${typeName}) callconv(JSC.conv) JSC.JSValue; + extern fn ${symbolName(typeName, "fromJS")}(jsc.JSValue) callconv(jsc.conv) ?*${typeName}; + extern fn ${symbolName(typeName, "fromJSDirect")}(jsc.JSValue) callconv(jsc.conv) ?*${typeName}; + extern fn ${symbolName(typeName, "getConstructor")}(*jsc.JSGlobalObject) callconv(jsc.conv) jsc.JSValue; + extern fn ${symbolName(typeName, "create")}(globalObject: *jsc.JSGlobalObject, ptr: ?*${typeName}) callconv(jsc.conv) jsc.JSValue; /// Create a new instance of ${typeName} without validating it works. pub const toJSUnchecked = ${symbolName(typeName, "create")}; - extern fn ${typeName}__dangerouslySetPtr(JSC.JSValue, ?*${typeName}) callconv(JSC.conv) bool; + extern fn ${typeName}__dangerouslySetPtr(jsc.JSValue, ?*${typeName}) callconv(jsc.conv) bool; ${renderMethods()} @@ -2370,7 +2394,7 @@ const ZIG_GENERATED_CLASSES_HEADER = ` /// 4. For the Zig code to successfully compile: /// - Add it to generated_classes_list.zig /// - \`\`\` -/// pub const js = JSC.Codegen.JSMyClassName; +/// pub const js = jsc.Codegen.JSMyClassName; /// pub const toJS = js.toJS; /// pub const fromJS = js.fromJS; /// pub const fromJSDirect = js.fromJSDirect; @@ -2378,8 +2402,8 @@ const ZIG_GENERATED_CLASSES_HEADER = ` /// 5. bun run build /// const bun = @import("bun"); -const JSC = bun.JSC; -const Classes = JSC.GeneratedClassesList; +const jsc = bun.jsc; +const Classes = jsc.GeneratedClassesList; const Environment = bun.Environment; const std = @import("std"); const zig = bun.Output.scoped(.zig, true); @@ -2391,10 +2415,10 @@ const wrapConstructor = bun.gen_classes_lib.wrapConstructor; const wrapGetterCallback = bun.gen_classes_lib.wrapGetterCallback; const wrapGetterWithValueCallback = bun.gen_classes_lib.wrapGetterWithValueCallback; -pub const StaticGetterType = fn(*JSC.JSGlobalObject, JSC.JSValue, JSC.JSValue) callconv(JSC.conv) JSC.JSValue; -pub const StaticSetterType = fn(*JSC.JSGlobalObject, JSC.JSValue, JSC.JSValue, JSC.JSValue) callconv(JSC.conv) bool; -pub const StaticCallbackType = JSC.JSHostFn; -pub const WriteBytesFn = *const fn(*anyopaque, ptr: [*]const u8, len: u32) callconv(JSC.conv) void; +pub const StaticGetterType = fn(*jsc.JSGlobalObject, jsc.JSValue, jsc.JSValue) callconv(jsc.conv) jsc.JSValue; +pub const StaticSetterType = fn(*jsc.JSGlobalObject, jsc.JSValue, jsc.JSValue, jsc.JSValue) callconv(jsc.conv) bool; +pub const StaticCallbackType = jsc.JSHostFn; +pub const WriteBytesFn = *const fn(*anyopaque, ptr: [*]const u8, len: u32) callconv(jsc.conv) void; `; @@ -2517,7 +2541,7 @@ comptime { // -- Avoid instantiating these log functions too many times -fn log_zig_method_call(typename: []const u8, method_name: []const u8, callframe: *JSC.CallFrame) callconv(bun.callconv_inline) void { +fn log_zig_method_call(typename: []const u8, method_name: []const u8, callframe: *jsc.CallFrame) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("{s}.{s}({d} args)", .{typename, method_name, callframe.arguments().len}); } @@ -2529,7 +2553,7 @@ fn log_zig_getter(typename: []const u8, property_name: []const u8) callconv(bun. } } -fn log_zig_setter(typename: []const u8, property_name: []const u8, value: JSC.JSValue) callconv(bun.callconv_inline) void { +fn log_zig_setter(typename: []const u8, property_name: []const u8, value: jsc.JSValue) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("set {s}.{s} = {}", .{typename, property_name, value}); } @@ -2541,19 +2565,19 @@ fn log_zig_finalize(typename: []const u8, ptr: *const anyopaque) callconv(bun.ca } } -fn log_zig_function_call(typename: []const u8, callframe: *JSC.CallFrame) callconv(bun.callconv_inline) void { +fn log_zig_function_call(typename: []const u8, callframe: *jsc.CallFrame) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("{s}({d} args)", .{typename, callframe.arguments().len}); } } -fn log_zig_constructor(typename: []const u8, callframe: *JSC.CallFrame) callconv(bun.callconv_inline) void { +fn log_zig_constructor(typename: []const u8, callframe: *jsc.CallFrame) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("new {s}({d} args)", .{typename, callframe.arguments().len}); } } -fn log_zig_call(typename: []const u8, callframe: *JSC.CallFrame) callconv(bun.callconv_inline) void { +fn log_zig_call(typename: []const u8, callframe: *jsc.CallFrame) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("{s}({d} args)", .{typename, callframe.arguments().len}); } @@ -2565,7 +2589,7 @@ fn log_zig_get_internal_properties(typename: []const u8) callconv(bun.callconv_i } } -fn log_zig_method(typename: []const u8, method_name: []const u8, callframe: *JSC.CallFrame) callconv(bun.callconv_inline) void { +fn log_zig_method(typename: []const u8, method_name: []const u8, callframe: *jsc.CallFrame) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("{s}.{s}({d} args)", .{typename, method_name, callframe.arguments().len}); } @@ -2614,7 +2638,7 @@ fn log_zig_to_js(typename: []const u8) callconv(bun.callconv_inline) void { } } -fn log_zig_class_method(typename: []const u8, method_name: []const u8, callframe: *JSC.CallFrame) callconv(bun.callconv_inline) void { +fn log_zig_class_method(typename: []const u8, method_name: []const u8, callframe: *jsc.CallFrame) callconv(bun.callconv_inline) void { if (comptime Environment.enable_logs) { zig("{s}.{s}({d} args)", .{typename, method_name, callframe.arguments().len}); } @@ -2733,7 +2757,7 @@ function getPropertySignatureWithComment( commentLines.push( ` Look for a function like this: * \`\`\`zig - * fn ${propDef.fn}(this: *${classDef.name}, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { ... } + * fn ${propDef.fn}(this: *${classDef.name}, globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue { ... } * \`\`\``, ); } else if ("builtin" in propDef) { @@ -2745,7 +2769,7 @@ function getPropertySignatureWithComment( commentLines.push( ` Look for a getter like this: * \`\`\`zig - * fn ${propDef.accessor.getter}(this: *${classDef.name}, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { ... } + * fn ${propDef.accessor.getter}(this: *${classDef.name}, globalThis: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue { ... } * \`\`\``, ); commentLines.push( @@ -2763,7 +2787,7 @@ function getPropertySignatureWithComment( commentLines.push( ` Look for a getter like this: * \`\`\`zig - * fn ${propDef.getter}(this: *${classDef.name}, globalThis: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { ... } + * fn ${propDef.getter}(this: *${classDef.name}, globalThis: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue { ... } * \`\`\``, ); if (propDef.writable) { @@ -2775,7 +2799,7 @@ function getPropertySignatureWithComment( commentLines.push( ` Look for a setter like this: * \`\`\`zig - * fn ${propDef.setter}(this: *${classDef.name}, globalThis: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!void { ... } + * fn ${propDef.setter}(this: *${classDef.name}, globalThis: *jsc.JSGlobalObject, value: jsc.JSValue) bun.JSError!void { ... } * \`\`\``, ); } else { diff --git a/src/install/dependency.zig b/src/install/dependency.zig index 114d4c6e80..5315714409 100644 --- a/src/install/dependency.zig +++ b/src/install/dependency.zig @@ -1286,14 +1286,14 @@ pub fn fromJS(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JS const dep: Version = Dependency.parse(allocator, SlicedString.init(buf, alias).value(), null, buf, &sliced, &log, null) orelse { if (log.msgs.items.len > 0) { - return globalThis.throwValue(log.toJS(globalThis, bun.default_allocator, "Failed to parse dependency")); + return globalThis.throwValue(try log.toJS(globalThis, bun.default_allocator, "Failed to parse dependency")); } return .undefined; }; if (log.msgs.items.len > 0) { - return globalThis.throwValue(log.toJS(globalThis, bun.default_allocator, "Failed to parse dependency")); + return globalThis.throwValue(try log.toJS(globalThis, bun.default_allocator, "Failed to parse dependency")); } log.deinit(); diff --git a/src/install/install.zig b/src/install/install.zig index d363f78559..5511ed1c4f 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -10384,14 +10384,14 @@ pub const PackageManager = struct { allocator, ) orelse return .zero; if (input_str.len > 0) - all_positionals.append(input_str.slice()) catch bun.outOfMemory(); + try all_positionals.append(input_str.slice()); } else if (input.isArray()) { var iter = input.arrayIterator(globalThis); while (iter.next()) |item| { const slice = item.toSliceCloneWithAllocator(globalThis, allocator) orelse return .zero; if (globalThis.hasException()) return .zero; if (slice.len == 0) continue; - all_positionals.append(slice.slice()) catch bun.outOfMemory(); + try all_positionals.append(slice.slice()); } if (globalThis.hasException()) return .zero; } else { @@ -10405,12 +10405,12 @@ pub const PackageManager = struct { var array = Array{}; const update_requests = parseWithError(allocator, null, &log, all_positionals.items, &array, .add, false) catch { - return globalThis.throwValue(log.toJS(globalThis, bun.default_allocator, "Failed to parse dependencies")); + return globalThis.throwValue(try log.toJS(globalThis, bun.default_allocator, "Failed to parse dependencies")); }; if (update_requests.len == 0) return .undefined; if (log.msgs.items.len > 0) { - return globalThis.throwValue(log.toJS(globalThis, bun.default_allocator, "Failed to parse dependencies")); + return globalThis.throwValue(try log.toJS(globalThis, bun.default_allocator, "Failed to parse dependencies")); } if (update_requests[0].failed) { diff --git a/src/logger.zig b/src/logger.zig index fe16e4b53b..e975fd9a69 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -446,7 +446,7 @@ pub const Msg = struct { }; } - pub fn toJS(this: Msg, globalObject: *bun.JSC.JSGlobalObject, allocator: std.mem.Allocator) JSC.JSValue { + pub fn toJS(this: Msg, globalObject: *bun.JSC.JSGlobalObject, allocator: std.mem.Allocator) bun.OOM!JSC.JSValue { return switch (this.metadata) { .build => bun.api.BuildMessage.create(globalObject, allocator, this), .resolve => bun.api.ResolveMessage.create(globalObject, allocator, this, ""), @@ -748,7 +748,7 @@ pub const Log = struct { } } - pub fn toJS(this: Log, global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, message: string) JSC.JSValue { + pub fn toJS(this: Log, global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, message: string) bun.OOM!JSC.JSValue { const msgs: []const Msg = this.msgs.items; var errors_stack: [256]JSC.JSValue = undefined; @@ -765,8 +765,8 @@ pub const Log = struct { else => { for (msgs[0..count], 0..) |msg, i| { errors_stack[i] = switch (msg.metadata) { - .build => bun.api.BuildMessage.create(global, allocator, msg), - .resolve => bun.api.ResolveMessage.create(global, allocator, msg, ""), + .build => try bun.api.BuildMessage.create(global, allocator, msg), + .resolve => try bun.api.ResolveMessage.create(global, allocator, msg, ""), }; } const out = JSC.ZigString.init(message); @@ -777,16 +777,16 @@ pub const Log = struct { } /// unlike toJS, this always produces an AggregateError object - pub fn toJSAggregateError(this: Log, global: *JSC.JSGlobalObject, message: bun.String) JSC.JSValue { - return global.createAggregateErrorWithArray(message, this.toJSArray(global, bun.default_allocator)); + pub fn toJSAggregateError(this: Log, global: *JSC.JSGlobalObject, message: bun.String) bun.OOM!JSC.JSValue { + return global.createAggregateErrorWithArray(message, try this.toJSArray(global, bun.default_allocator)); } - pub fn toJSArray(this: Log, global: *JSC.JSGlobalObject, allocator: std.mem.Allocator) JSC.JSValue { + pub fn toJSArray(this: Log, global: *JSC.JSGlobalObject, allocator: std.mem.Allocator) bun.OOM!JSC.JSValue { const msgs: []const Msg = this.msgs.items; const arr = JSC.JSValue.createEmptyArray(global, msgs.len); for (msgs, 0..) |msg, i| { - arr.putIndex(global, @as(u32, @intCast(i)), msg.toJS(global, allocator)); + arr.putIndex(global, @as(u32, @intCast(i)), try msg.toJS(global, allocator)); } return arr; diff --git a/src/router.zig b/src/router.zig index 82aa4d2df8..ceb5a880fd 100644 --- a/src/router.zig +++ b/src/router.zig @@ -70,11 +70,11 @@ pub fn deinit(this: *Router) void { } } -pub fn getEntryPoints(this: *const Router) ![]const string { +pub fn getEntryPoints(this: *const Router) []const string { return this.routes.list.items(.filepath); } -pub fn getPublicPaths(this: *const Router) ![]const string { +pub fn getPublicPaths(this: *const Router) []const string { return this.routes.list.items(.public_path); } @@ -86,7 +86,7 @@ pub fn routeIndexByHash(this: *const Router, hash: u32) ?usize { return std.mem.indexOfScalar(u32, this.routes.list.items(.hash), hash); } -pub fn getNames(this: *const Router) ![]const string { +pub fn getNames(this: *const Router) []const string { return this.routes.list.items(.name); } @@ -1080,7 +1080,7 @@ pub const Test = struct { &resolver, FileSystem.instance.top_level_dir, ); - const entry_points = try router.getEntryPoints(); + const entry_points = router.getEntryPoints(); try expectEqual(std.meta.fieldNames(@TypeOf(data)).len, entry_points.len); return router; diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index 2c2eeebd30..8f8427a2d4 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1413,9 +1413,8 @@ pub const PostgresSQLConnection = struct { return .undefined; } - pub fn setOnConnect(_: *PostgresSQLConnection, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bool { + pub fn setOnConnect(_: *PostgresSQLConnection, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { js.onconnectSetCached(thisValue, globalObject, value); - return true; } pub fn getOnClose(_: *PostgresSQLConnection, thisValue: JSC.JSValue, _: *JSC.JSGlobalObject) JSC.JSValue { @@ -1426,9 +1425,8 @@ pub const PostgresSQLConnection = struct { return .undefined; } - pub fn setOnClose(_: *PostgresSQLConnection, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bool { + pub fn setOnClose(_: *PostgresSQLConnection, thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { js.oncloseSetCached(thisValue, globalObject, value); - return true; } pub fn setupTLS(this: *PostgresSQLConnection) void { diff --git a/src/url.zig b/src/url.zig index 3ee221ebe6..b8de5ccf99 100644 --- a/src/url.zig +++ b/src/url.zig @@ -618,7 +618,7 @@ pub const QueryStringMap = struct { pub fn initWithScanner( allocator: std.mem.Allocator, _scanner: CombinedScanner, - ) !?QueryStringMap { + ) bun.OOM!?QueryStringMap { var list = Param.List{}; var scanner = _scanner; @@ -727,7 +727,7 @@ pub const QueryStringMap = struct { pub fn init( allocator: std.mem.Allocator, query_string: string, - ) !?QueryStringMap { + ) bun.OOM!?QueryStringMap { var list = Param.List{}; var scanner = Scanner.init(query_string); diff --git a/src/valkey/js_valkey.zig b/src/valkey/js_valkey.zig index e9dcc69bcc..dbb14bed70 100644 --- a/src/valkey/js_valkey.zig +++ b/src/valkey/js_valkey.zig @@ -232,9 +232,8 @@ pub const JSValkeyClient = struct { return .undefined; } - pub fn setOnConnect(_: *JSValkeyClient, thisValue: JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) bool { + pub fn setOnConnect(_: *JSValkeyClient, thisValue: JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) void { js.onconnectSetCached(thisValue, globalObject, value); - return true; } pub fn getOnClose(_: *JSValkeyClient, thisValue: JSValue, _: *JSC.JSGlobalObject) JSValue { @@ -244,9 +243,8 @@ pub const JSValkeyClient = struct { return .undefined; } - pub fn setOnClose(_: *JSValkeyClient, thisValue: JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) bool { + pub fn setOnClose(_: *JSValkeyClient, thisValue: JSValue, globalObject: *JSC.JSGlobalObject, value: JSValue) void { js.oncloseSetCached(thisValue, globalObject, value); - return true; } /// Safely add a timer with proper reference counting and event loop keepalive diff --git a/test/js/node/test/parallel/test-timers-immediate-queue-throw.js b/test/js/node/test/parallel/test-timers-immediate-queue-throw.js new file mode 100644 index 0000000000..aa2691fd88 --- /dev/null +++ b/test/js/node/test/parallel/test-timers-immediate-queue-throw.js @@ -0,0 +1,60 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const domain = require('domain'); + +// setImmediate should run clear its queued cbs once per event loop turn +// but immediates queued while processing the current queue should happen +// on the next turn of the event loop. + +// In addition, if any setImmediate throws, the rest of the queue should +// be processed after all error handling is resolved, but that queue +// should not include any setImmediate calls scheduled after the +// processing of the queue started. + +let threw = false; +let stage = -1; + +const QUEUE = 10; + +const errObj = { + name: 'Error', + message: 'setImmediate Err' +}; + +process.once('uncaughtException', common.mustCall((err, errorOrigin) => { + assert.strictEqual(errorOrigin, 'uncaughtException'); + assert.strictEqual(stage, 0); + common.expectsError(errObj)(err); +})); + +// The domain part of this test is commented out because it's not planned to be +// supported in Bun. The rest of this test is untouched and still tests the behavior +// of throwing errors in setImmediate callbacks exactly like Node.js. + +// const d1 = domain.create(); +// d1.once('error', common.expectsError(errObj)); +// d1.once('error', () => assert.strictEqual(stage, 0)); + +const run = common.mustCall((callStage) => { + assert(callStage >= stage); + stage = callStage; + if (threw) + return; + + setImmediate(run, 2); +}, QUEUE * 3); + +for (let i = 0; i < QUEUE; i++) + setImmediate(run, 0); +setImmediate(() => { + threw = true; + process.nextTick(common.mustCall(() => assert.strictEqual(stage, 1))); + throw new Error('setImmediate Err'); +}); +// d1.run(() => setImmediate(() => { +// throw new Error('setImmediate Err'); +// })); +for (let i = 0; i < QUEUE; i++) + setImmediate(run, 1); diff --git a/test/js/node/test/parallel/test-timers-immediate-unref.js b/test/js/node/test/parallel/test-timers-immediate-unref.js new file mode 100644 index 0000000000..fa9fd9aa55 --- /dev/null +++ b/test/js/node/test/parallel/test-timers-immediate-unref.js @@ -0,0 +1,41 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const immediate = setImmediate(() => {}); +assert.strictEqual(immediate.hasRef(), true); +immediate.unref(); +assert.strictEqual(immediate.hasRef(), false); +clearImmediate(immediate); + +// This immediate should execute as it was unrefed and refed again. +// It also confirms that unref/ref are chainable. +setImmediate(common.mustCall(firstStep)).ref().unref().unref().ref(); + +function firstStep() { + // Unrefed setImmediate executes if it was unrefed but something else keeps + // the loop open + setImmediate(common.mustCall()).unref(); + setTimeout(common.mustCall(() => { setImmediate(secondStep); }), 0); +} + +function secondStep() { + // clearImmediate works just fine with unref'd immediates + const immA = setImmediate(() => { + clearImmediate(immA); + clearImmediate(immB); + // This should not keep the event loop open indefinitely + // or do anything else weird + immA.ref(); + immB.ref(); + }).unref(); + const immB = setImmediate(common.mustNotCall()).unref(); + setImmediate(common.mustCall(finalStep)); +} + +function finalStep() { + // This immediate should not execute as it was unrefed + // and nothing else is keeping the event loop alive + setImmediate(common.mustNotCall()).unref(); +} diff --git a/test/js/node/test/parallel/test-timers-timeout-to-interval.js b/test/js/node/test/parallel/test-timers-timeout-to-interval.js new file mode 100644 index 0000000000..6b83e2e09e --- /dev/null +++ b/test/js/node/test/parallel/test-timers-timeout-to-interval.js @@ -0,0 +1,12 @@ +'use strict'; +const common = require('../common'); + +// This isn't officially supported but nonetheless is something that is +// currently possible and as such it shouldn't cause the process to crash + +const t = setTimeout(common.mustCall(() => { + if (t._repeat) { + clearInterval(t); + } + t._repeat = 1; +}, 2), 1); diff --git a/test/js/node/test/sequential/test-timers-block-eventloop.js b/test/js/node/test/sequential/test-timers-block-eventloop.js new file mode 100644 index 0000000000..7fcaef38ec --- /dev/null +++ b/test/js/node/test/sequential/test-timers-block-eventloop.js @@ -0,0 +1,20 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const sleep = typeof Bun === 'object' ? Bun.sleepSync : require('internal/util').sleep; + +let called = false; +const t1 = setInterval(() => { + assert(!called); + called = true; + setImmediate(common.mustCall(() => { + clearInterval(t1); + clearInterval(t2); + })); +}, 10); + +const t2 = setInterval(() => { + sleep(20); +}, 10); diff --git a/test/js/node/test/sequential/test-timers-set-interval-excludes-callback-duration.js b/test/js/node/test/sequential/test-timers-set-interval-excludes-callback-duration.js new file mode 100644 index 0000000000..b980abbddb --- /dev/null +++ b/test/js/node/test/sequential/test-timers-set-interval-excludes-callback-duration.js @@ -0,0 +1,25 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const sleep = typeof Bun === 'object' ? Bun.sleepSync : require('internal/util').sleep; + +let cntr = 0; +let first; +const t = setInterval(() => { + cntr++; + if (cntr === 1) { + sleep(100); + // Ensure that the event loop passes before the second interval + setImmediate(() => assert.strictEqual(cntr, 1)); + first = Date.now(); + } else if (cntr === 2) { + assert(Date.now() - first < 100); + clearInterval(t); + } +}, 100); +const t2 = setInterval(() => { + if (cntr === 2) { + clearInterval(t2); + } +}, 100); diff --git a/test/js/node/timers/node-timers.test.ts b/test/js/node/timers/node-timers.test.ts index 574500ed06..9953605442 100644 --- a/test/js/node/timers/node-timers.test.ts +++ b/test/js/node/timers/node-timers.test.ts @@ -250,3 +250,7 @@ describe.each(["with", "without"])("setImmediate %s timers running", mode => { 5000, ); }); + +it("should defer microtasks when an exception is thrown in an immediate", async () => { + expect(["run", path.join(import.meta.dir, "timers-immediate-exception-fixture.js")]).toRun(); +}); diff --git a/test/js/node/timers/timers-immediate-exception-fixture.js b/test/js/node/timers/timers-immediate-exception-fixture.js new file mode 100644 index 0000000000..bf3bc8bbec --- /dev/null +++ b/test/js/node/timers/timers-immediate-exception-fixture.js @@ -0,0 +1,106 @@ +import { mustCall } from "../test/common/index.mjs"; + +process.on( + "uncaughtException", + mustCall(err => { + if (err.message !== "oops") { + throw err; + } + }, 3), +); + +function checkNextTick(expected) { + process.nextTick( + mustCall(() => { + if (counter !== expected) { + throw new Error("oops: " + expected + " != " + counter); + } + }), + ); +} + +var counter = 0; +setImmediate( + mustCall(() => { + counter++; + checkNextTick(1); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(2); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(4); + throw new Error("oops"); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(4); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(6); + throw new Error("oops"); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(6); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(7); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(8); + setImmediate( + mustCall(() => { + counter++; + checkNextTick(11); + }), + ); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(9); + setImmediate( + mustCall(() => { + counter++; + checkNextTick(12); + throw new Error("oops"); + }), + ); + }), +); + +setImmediate( + mustCall(() => { + counter++; + checkNextTick(10); + }), +); diff --git a/test/js/web/timers/setTimeout.test.js b/test/js/web/timers/setTimeout.test.js index fe8797bd56..5ff7058037 100644 --- a/test/js/web/timers/setTimeout.test.js +++ b/test/js/web/timers/setTimeout.test.js @@ -364,5 +364,5 @@ it("Returning a Promise in setTimeout (unref'd) doesnt keep the event loop alive }); it("setTimeout canceling with unref, close, _idleTimeout, and _onTimeout", () => { - expect([path.join(import.meta.dir, "timers-fixture-unref.js"), "setInterval"]).toRun(); + expect([path.join(import.meta.dir, "timers-fixture-unref.js"), "setTimeout"]).toRun(); });