diff --git a/src/ast.zig b/src/ast.zig index d51e38b335..9cafcfa7fe 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -610,6 +610,7 @@ pub const ToJSError = error{ MacroError, OutOfMemory, JSError, + JSTerminated, }; /// Say you need to allocate a bunch of tiny arrays diff --git a/src/bake/DevServer.zig b/src/bake/DevServer.zig index 9e7c1a7725..30ebcdd046 100644 --- a/src/bake/DevServer.zig +++ b/src/bake/DevServer.zig @@ -2950,8 +2950,8 @@ pub fn finalizeBundle( defer current_bundle.promise.deinitIdempotently(); current_bundle.promise.setRouteBundleState(dev, .loaded); dev.vm.eventLoop().enter(); - current_bundle.promise.strong.resolve(dev.vm.global, .true); - dev.vm.eventLoop().exit(); + defer dev.vm.eventLoop().exit(); + try current_bundle.promise.strong.resolve(dev.vm.global, .true); } while (current_bundle.requests.popFirst()) |node| { @@ -3139,6 +3139,7 @@ fn onRequest(dev: *DevServer, req: *Request, resp: anytype) void { &ctx, ) catch |err| switch (err) { error.JSError => dev.vm.global.reportActiveExceptionAsUnhandled(err), + error.JSTerminated => dev.vm.global.reportActiveExceptionAsUnhandled(err), error.OutOfMemory => bun.outOfMemory(), }; return; @@ -3196,6 +3197,7 @@ pub fn respondForHTMLBundle(dev: *DevServer, html: *HTMLBundle.HTMLBundleRoute, &ctx, ) catch |err| switch (err) { error.JSError => dev.vm.global.reportActiveExceptionAsUnhandled(err), + error.JSTerminated => dev.vm.global.reportActiveExceptionAsUnhandled(err), else => |other| return other, }; } @@ -3363,8 +3365,8 @@ fn sendSerializedFailures( false, ); dev.vm.eventLoop().enter(); - r.promise.reject(r.global, response.toJS(r.global)); defer dev.vm.eventLoop().exit(); + try r.promise.reject(r.global, response.toJS(r.global)); }, } } @@ -4473,7 +4475,7 @@ const PromiseEnsureRouteBundledCtx = struct { fn onLoaded(this: *PromiseEnsureRouteBundledCtx) bun.JSError!void { _ = this.ensurePromise(); - this.p.?.resolve(this.global, .true); + try this.p.?.resolve(this.global, .true); this.dev.vm.drainMicrotasks(); } @@ -4490,7 +4492,7 @@ const PromiseEnsureRouteBundledCtx = struct { fn onPluginError(this: *PromiseEnsureRouteBundledCtx) bun.JSError!void { _ = this.ensurePromise(); - this.p.?.reject(this.global, bun.String.static("Plugin error").toJS(this.global)); + try this.p.?.reject(this.global, bun.String.static("Plugin error").toJS(this.global)); this.dev.vm.drainMicrotasks(); } diff --git a/src/bun.js.zig b/src/bun.js.zig index e6e171d800..1a4fafbbd4 100644 --- a/src/bun.js.zig +++ b/src/bun.js.zig @@ -420,7 +420,7 @@ pub const Run = struct { if (result.asAnyPromise()) |promise| { switch (promise.status(vm.jsc_vm)) { .pending => { - result._then2(vm.global, .js_undefined, Bun__onResolveEntryPointResult, Bun__onRejectEntryPointResult); + result.then2(vm.global, .js_undefined, Bun__onResolveEntryPointResult, Bun__onRejectEntryPointResult) catch {}; // TODO: properly propagate exception upwards vm.tick(); vm.eventLoop().autoTickActive(); diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index f017857c97..42705bbad3 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -67,10 +67,7 @@ pub fn messageWithTypeAndLevel( vals: [*]const JSValue, len: usize, ) callconv(jsc.conv) void { - messageWithTypeAndLevel_(ctype, message_type, level, global, vals, len) catch |err| switch (err) { - error.JSError => {}, - error.OutOfMemory => global.throwOutOfMemory() catch {}, // TODO: properly propagate exception upwards - }; + messageWithTypeAndLevel_(ctype, message_type, level, global, vals, len) catch |err| bun.jsc.host_fn.voidFromJSError(err, global); } fn messageWithTypeAndLevel_( //console_: *ConsoleObject, diff --git a/src/bun.js/VirtualMachine.zig b/src/bun.js/VirtualMachine.zig index 37c64a40c7..6a6737941b 100644 --- a/src/bun.js/VirtualMachine.zig +++ b/src/bun.js/VirtualMachine.zig @@ -573,14 +573,14 @@ pub fn unhandledRejection(this: *jsc.VirtualMachine, globalObject: *JSGlobalObje }, .none => { defer this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => {}, // we are returning anyway + error.JSTerminated => {}, // we are returning anyway }; if (Bun__handleUnhandledRejection(globalObject, reason, promise) > 0) return; return; // ignore the unhandled rejection }, .warn => { defer this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => {}, // we are returning anyway + error.JSTerminated => {}, // we are returning anyway }; _ = Bun__handleUnhandledRejection(globalObject, reason, promise); jsc.fromJSHostCallGeneric(globalObject, @src(), Bun__promises__emitUnhandledRejectionWarning, .{ globalObject, reason, promise }) catch |err| { @@ -590,7 +590,7 @@ pub fn unhandledRejection(this: *jsc.VirtualMachine, globalObject: *JSGlobalObje }, .warn_with_error_code => { defer this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => {}, // we are returning anyway + error.JSTerminated => {}, // we are returning anyway }; if (Bun__handleUnhandledRejection(globalObject, reason, promise) > 0) return; jsc.fromJSHostCallGeneric(globalObject, @src(), Bun__promises__emitUnhandledRejectionWarning, .{ globalObject, reason, promise }) catch |err| { @@ -601,7 +601,7 @@ pub fn unhandledRejection(this: *jsc.VirtualMachine, globalObject: *JSGlobalObje }, .strict => { defer this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => {}, // we are returning anyway + error.JSTerminated => {}, // we are returning anyway }; const wrapped_reason = wrapUnhandledRejectionErrorForUncaughtException(globalObject, reason); _ = this.uncaughtException(globalObject, wrapped_reason, true); @@ -614,20 +614,20 @@ pub fn unhandledRejection(this: *jsc.VirtualMachine, globalObject: *JSGlobalObje .throw => { if (Bun__handleUnhandledRejection(globalObject, reason, promise) > 0) { this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => {}, // we are returning anyway + error.JSTerminated => {}, // we are returning anyway }; return; } const wrapped_reason = wrapUnhandledRejectionErrorForUncaughtException(globalObject, reason); if (this.uncaughtException(globalObject, wrapped_reason, true)) { this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => {}, // we are returning anyway + error.JSTerminated => {}, // we are returning anyway }; return; } // continue to default handler this.eventLoop().drainMicrotasks() catch |e| switch (e) { - error.JSExecutionTerminated => return, + error.JSTerminated => return, }; }, } @@ -1860,8 +1860,7 @@ pub export fn Bun__drainMicrotasksFromJS(globalObject: *JSGlobalObject, callfram } pub fn drainMicrotasks(this: *VirtualMachine) void { - // TODO: properly propagate exception upwards - this.eventLoop().drainMicrotasks() catch {}; + this.eventLoop().drainMicrotasks() catch {}; // TODO: properly propagate exception upwards } pub fn processFetchLog(globalThis: *JSGlobalObject, specifier: bun.String, referrer: bun.String, log: *logger.Log, ret: *ErrorableResolvedSource, err: anyerror) void { diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 04bfbda834..60a13a1e83 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -1904,7 +1904,7 @@ pub const JSZstd = struct { } } - pub fn runFromJS(this: *ZstdJob) void { + pub fn runFromJS(this: *ZstdJob) bun.JSTerminated!void { defer this.deinit(); if (this.vm.isShuttingDown()) { @@ -1915,14 +1915,14 @@ pub const JSZstd = struct { const promise = this.promise.swap(); if (this.error_message) |err_msg| { - promise.reject(globalThis, globalThis.ERR(.ZSTD, "{s}", .{err_msg}).toJS()); + try promise.reject(globalThis, globalThis.ERR(.ZSTD, "{s}", .{err_msg}).toJS()); return; } const output_slice = this.output; const buffer_value = jsc.JSValue.createBuffer(globalThis, output_slice); this.output = &[_]u8{}; - promise.resolve(globalThis, buffer_value); + try promise.resolve(globalThis, buffer_value); } pub fn deinit(this: *ZstdJob) void { diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index db8982524a..2dfe0a5727 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -1103,6 +1103,7 @@ pub const JSBundler = struct { bun.outOfMemory(); }, error.JSError => {}, + error.JSTerminated => {}, } @panic("Unexpected: source_code is not a string"); @@ -1333,7 +1334,7 @@ pub const JSBundler = struct { exception, ) catch |err| switch (err) { error.OutOfMemory => bun.outOfMemory(), - error.JSError => { + error.JSError, error.JSTerminated => { plugin.globalObject().reportActiveExceptionAsUnhandled(err); return; }, @@ -1351,7 +1352,7 @@ pub const JSBundler = struct { exception, ) catch |err| switch (err) { error.OutOfMemory => bun.outOfMemory(), - error.JSError => { + error.JSError, error.JSTerminated => { plugin.globalObject().reportActiveExceptionAsUnhandled(err); return; }, diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig index 395daf0cda..b0fab5ab2f 100644 --- a/src/bun.js/api/JSTranspiler.zig +++ b/src/bun.js/api/JSTranspiler.zig @@ -540,11 +540,11 @@ pub const TransformTask = struct { } } - pub fn then(this: *TransformTask, promise: *jsc.JSPromise) void { + pub fn then(this: *TransformTask, promise: *jsc.JSPromise) bun.JSTerminated!void { defer this.deinit(); if (this.log.hasAny() or this.err != null) { - const error_value: bun.OOM!JSValue = brk: { + const error_value: bun.JSError!JSValue = brk: { if (this.err) |err| { if (!this.log.hasAny()) { break :brk bun.api.BuildMessage.create( @@ -557,18 +557,18 @@ pub const TransformTask = struct { } } - break :brk this.log.toJS(this.global, bun.default_allocator, "Transform failed") catch return; // TODO: properly propagate exception upwards + break :brk this.log.toJS(this.global, bun.default_allocator, "Transform failed"); }; - promise.reject(this.global, error_value); + try promise.reject(this.global, error_value); return; } - finish(this, promise); + try finish(this, promise); } - fn finish(this: *TransformTask, promise: *jsc.JSPromise) void { - promise.resolve(this.global, this.output_code.transferToJS(this.global)); + fn finish(this: *TransformTask, promise: *jsc.JSPromise) bun.JSTerminated!void { + return promise.resolve(this.global, this.output_code.transferToJS(this.global)); } pub fn deinit(this: *TransformTask) void { diff --git a/src/bun.js/api/YAMLObject.zig b/src/bun.js/api/YAMLObject.zig index 40867f6ba8..327ccea014 100644 --- a/src/bun.js/api/YAMLObject.zig +++ b/src/bun.js/api/YAMLObject.zig @@ -44,12 +44,12 @@ pub fn stringify(global: *JSGlobalObject, callFrame: *jsc.CallFrame) JSError!JSV defer stringifier.deinit(); stringifier.findAnchorsAndAliases(global, value, .root) catch |err| return switch (err) { - error.OutOfMemory, error.JSError => |js_err| js_err, + error.OutOfMemory, error.JSError, error.JSTerminated => |js_err| js_err, error.StackOverflow => global.throwStackOverflow(), }; stringifier.stringify(global, value) catch |err| return switch (err) { - error.OutOfMemory, error.JSError => |js_err| js_err, + error.OutOfMemory, error.JSError, error.JSTerminated => |js_err| js_err, error.StackOverflow => global.throwStackOverflow(), }; @@ -978,7 +978,7 @@ const ParserCtx = struct { ctx.result = ctx.global.throwOutOfMemoryValue(); return; }, - error.JSError => { + error.JSError, error.JSTerminated => { ctx.result = .zero; return; }, diff --git a/src/bun.js/api/bun/dns.zig b/src/bun.js/api/bun/dns.zig index a9fca57010..b7ea63ebda 100644 --- a/src/bun.js/api/bun/dns.zig +++ b/src/bun.js/api/bun/dns.zig @@ -95,7 +95,7 @@ const LibInfo = struct { ); if (errno != 0) { - request.head.promise.rejectTask(globalThis, globalThis.createErrorInstance("getaddrinfo_async_start error: {s}", .{@tagName(bun.sys.getErrno(errno))})); + request.head.promise.rejectTask(globalThis, globalThis.createErrorInstance("getaddrinfo_async_start error: {s}", .{@tagName(bun.sys.getErrno(errno))})) catch {}; // TODO: properly propagate exception upwards if (request.cache.pending_cache) this.pending_host_cache_native.used.set(request.cache.pos_in_pending); this.vm.allocator.destroy(request); @@ -516,7 +516,7 @@ pub const CAresNameInfo = struct { var promise = this.promise; const globalThis = this.globalThis; this.promise = .{}; - promise.resolveTask(globalThis, result); + promise.resolveTask(globalThis, result) catch {}; // TODO: properly propagate exception upwards this.deinit(); } @@ -932,7 +932,7 @@ pub const CAresReverse = struct { var promise = this.promise; const globalThis = this.globalThis; this.promise = .{}; - promise.resolveTask(globalThis, result); + promise.resolveTask(globalThis, result) catch {}; // TODO: properly propagate exception upwards if (this.resolver) |resolver| { resolver.requestCompleted(); } @@ -1013,7 +1013,7 @@ pub fn CAresLookup(comptime cares_type: type, comptime type_name: []const u8) ty var promise = this.promise; const globalThis = this.globalThis; this.promise = .{}; - promise.resolveTask(globalThis, result); + promise.resolveTask(globalThis, result) catch {}; // TODO: properly propagate exception upwards if (this.resolver) |resolver| { resolver.requestCompleted(); } @@ -1108,7 +1108,7 @@ pub const DNSLookup = struct { var promise = this.promise; this.promise = .{}; const globalThis = this.globalThis; - promise.resolveTask(globalThis, result); + promise.resolveTask(globalThis, result) catch {}; // TODO: properly propagate exception upwards if (this.resolver) |resolver| { resolver.requestCompleted(); } @@ -2746,6 +2746,7 @@ pub const Resolver = struct { error.InvalidFlags => globalThis.throwInvalidArgumentValue("flags", try optionsObject.getTruthy(globalThis, "flags") orelse .js_undefined), error.JSError => |exception| exception, error.OutOfMemory => |oom| oom, + error.JSTerminated => |e| e, // more information with these errors error.InvalidOptions, diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 3da90d798b..369ffb5f15 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -328,7 +328,7 @@ pub fn NewSocket(comptime ssl: bool) type { // reject the promise on connect() error const err_value = err.toErrorInstance(globalObject); - promise.asPromise().?.reject(globalObject, err_value); + promise.asPromise().?.reject(globalObject, err_value) catch {}; // TODO: properly propagate exception upwards } return; @@ -342,14 +342,14 @@ pub fn NewSocket(comptime ssl: bool) type { const result = callback.call(globalObject, this_value, &[_]JSValue{ this_value, err_value }) catch |e| globalObject.takeException(e); if (result.toError()) |err_val| { - if (handlers.rejectPromise(err_val)) return; + if (handlers.rejectPromise(err_val) catch true) return; // TODO: properly propagate exception upwards _ = handlers.callErrorHandler(this_value, &.{ this_value, err_val }); } else if (handlers.promise.trySwap()) |val| { // They've defined a `connectError` callback // The error is effectively handled, but we should still reject the promise. var promise = val.asPromise().?; const err_ = err.toErrorInstance(globalObject); - promise.rejectAsHandled(globalObject, err_); + promise.rejectAsHandled(globalObject, err_) catch {}; // TODO: properly propagate exception upwards } } @@ -457,7 +457,7 @@ pub fn NewSocket(comptime ssl: bool) type { const this_value = this.getThisValue(globalObject); this.markActive(); - handlers.resolvePromise(this_value); + handlers.resolvePromise(this_value) catch {}; // TODO: properly propagate exception upwards if (comptime ssl) { // only calls open callback if handshake callback is provided @@ -482,7 +482,7 @@ pub fn NewSocket(comptime ssl: bool) type { log("Already closed", .{}); } - if (handlers.rejectPromise(err)) return; + if (handlers.rejectPromise(err) catch true) return; // TODO: properly propagate exception upwards _ = handlers.callErrorHandler(this_value, &.{ this_value, err }); } } diff --git a/src/bun.js/api/bun/socket/Handlers.zig b/src/bun.js/api/bun/socket/Handlers.zig index b874d9f4f2..d902d677dd 100644 --- a/src/bun.js/api/bun/socket/Handlers.zig +++ b/src/bun.js/api/bun/socket/Handlers.zig @@ -43,7 +43,7 @@ pub fn enter(this: *Handlers) Scope { // corker: Corker = .{}, -pub fn resolvePromise(this: *Handlers, value: JSValue) void { +pub fn resolvePromise(this: *Handlers, value: JSValue) bun.JSTerminated!void { const vm = this.vm; if (vm.isShuttingDown()) { return; @@ -51,10 +51,10 @@ pub fn resolvePromise(this: *Handlers, value: JSValue) void { const promise = this.promise.trySwap() orelse return; const anyPromise = promise.asAnyPromise() orelse return; - anyPromise.resolve(this.globalObject, value); + try anyPromise.resolve(this.globalObject, value); } -pub fn rejectPromise(this: *Handlers, value: JSValue) bool { +pub fn rejectPromise(this: *Handlers, value: JSValue) bun.JSTerminated!bool { const vm = this.vm; if (vm.isShuttingDown()) { return true; @@ -62,7 +62,7 @@ pub fn rejectPromise(this: *Handlers, value: JSValue) bool { const promise = this.promise.trySwap() orelse return false; const anyPromise = promise.asAnyPromise() orelse return false; - anyPromise.reject(this.globalObject, value); + try anyPromise.reject(this.globalObject, value); return true; } diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index e4d754b243..a7693fa604 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -697,9 +697,9 @@ pub fn onProcessExit(this: *Subprocess, process: *Process, status: bun.spawn.Sta } switch (status) { - .exited => |exited| promise.asAnyPromise().?.resolve(globalThis, JSValue.jsNumber(exited.code)), - .err => |err| promise.asAnyPromise().?.reject(globalThis, err.toJS(globalThis)), - .signaled => promise.asAnyPromise().?.resolve(globalThis, JSValue.jsNumber(128 +% @intFromEnum(status.signaled))), + .exited => |exited| promise.asAnyPromise().?.resolve(globalThis, JSValue.jsNumber(exited.code)) catch {}, // TODO: properly propagate exception upwards + .err => |err| promise.asAnyPromise().?.reject(globalThis, err.toJS(globalThis)) catch {}, // TODO: properly propagate exception upwards + .signaled => promise.asAnyPromise().?.resolve(globalThis, JSValue.jsNumber(128 +% @intFromEnum(status.signaled))) catch {}, // TODO: properly propagate exception upwards else => { // crash in debug mode if (comptime Environment.allow_assert) diff --git a/src/bun.js/api/crypto/PBKDF2.zig b/src/bun.js/api/crypto/PBKDF2.zig index 3fed8e6042..c9f7127732 100644 --- a/src/bun.js/api/crypto/PBKDF2.zig +++ b/src/bun.js/api/crypto/PBKDF2.zig @@ -62,7 +62,7 @@ pub const Job = struct { } } - pub fn runFromJS(this: *Job) void { + pub fn runFromJS(this: *Job) bun.JSTerminated!void { defer this.deinit(); if (this.vm.isShuttingDown()) { return; @@ -71,7 +71,7 @@ pub const Job = struct { const globalThis = this.vm.global; const promise = this.promise.swap(); if (this.err) |err| { - promise.reject(globalThis, createCryptoError(globalThis, err)); + try promise.reject(globalThis, createCryptoError(globalThis, err)); return; } @@ -79,7 +79,7 @@ pub const Job = struct { assert(output_slice.len == @as(usize, @intCast(this.pbkdf2.length))); const buffer_value = jsc.JSValue.createBuffer(globalThis, output_slice); this.output = &[_]u8{}; - promise.resolve(globalThis, buffer_value); + try promise.resolve(globalThis, buffer_value); } pub fn deinit(this: *Job) void { diff --git a/src/bun.js/api/crypto/PasswordObject.zig b/src/bun.js/api/crypto/PasswordObject.zig index 86db3d0b6c..59e07638e1 100644 --- a/src/bun.js/api/crypto/PasswordObject.zig +++ b/src/bun.js/api/crypto/PasswordObject.zig @@ -365,7 +365,7 @@ pub const JSPasswordObject = struct { } }; - pub fn runFromJS(this: *Result) void { + pub fn runFromJS(this: *Result) bun.JSTerminated!void { var promise = this.promise; defer promise.deinit(); this.promise = .{}; @@ -375,12 +375,12 @@ pub const JSPasswordObject = struct { .err => { const error_instance = this.value.toErrorInstance(global); bun.destroy(this); - promise.reject(global, error_instance); + try promise.reject(global, error_instance); }, .hash => |value| { const js_string = jsc.ZigString.init(value).toJS(global); bun.destroy(this); - promise.resolve(global, js_string); + try promise.resolve(global, js_string); }, } } @@ -577,7 +577,7 @@ pub const JSPasswordObject = struct { } }; - pub fn runFromJS(this: *Result) void { + pub fn runFromJS(this: *Result) bun.JSTerminated!void { var promise = this.promise; defer promise.deinit(); this.promise = .{}; @@ -587,11 +587,11 @@ pub const JSPasswordObject = struct { .err => { const error_instance = this.value.toErrorInstance(global); bun.destroy(this); - promise.reject(global, error_instance); + try promise.reject(global, error_instance); }, .pass => |pass| { bun.destroy(this); - promise.resolve(global, jsc.JSValue.jsBoolean(pass)); + try promise.resolve(global, jsc.JSValue.jsBoolean(pass)); }, } } diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index 470cd8daf1..8325117440 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -739,6 +739,7 @@ pub const FFI = struct { }, error.JSError => |e| return e, error.OutOfMemory => |e| return e, + error.JSTerminated => |e| return e, } }; defer { diff --git a/src/bun.js/api/glob.zig b/src/bun.js/api/glob.zig index 0e3f37375c..c393448566 100644 --- a/src/bun.js/api/glob.zig +++ b/src/bun.js/api/glob.zig @@ -172,17 +172,17 @@ pub const WalkTask = struct { } } - pub fn then(this: *WalkTask, promise: *jsc.JSPromise) void { + pub fn then(this: *WalkTask, promise: *jsc.JSPromise) bun.JSTerminated!void { defer this.deinit(); if (this.err) |err| { const errJs = err.toJS(this.global); - promise.reject(this.global, errJs); + try promise.reject(this.global, errJs); return; } const jsStrings = globWalkResultToJS(this.walker, this.global) catch return promise.reject(this.global, error.JSError); - promise.resolve(this.global, jsStrings); + try promise.resolve(this.global, jsStrings); } fn deinit(this: *WalkTask) void { diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index 9291a9c78c..5cc0c0d9f1 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -545,7 +545,7 @@ pub const HTMLRewriter = struct { sinkBodyValue.Locked.task = null; } if (is_async) { - sinkBodyValue.toErrorInstance(err.dupe(sink.global), sink.global); + sinkBodyValue.toErrorInstance(err.dupe(sink.global), sink.global) catch {}; // TODO: properly propagate exception upwards } else { var ret_err = createLOLHTMLError(sink.global); ret_err.ensureStillAlive(); @@ -575,7 +575,7 @@ pub const HTMLRewriter = struct { sink.rewriter.?.write(bytes) catch { if (is_async) { - response.getBodyValue().toErrorInstance(.{ .Message = createLOLHTMLStringError() }, global); + response.getBodyValue().toErrorInstance(.{ .Message = createLOLHTMLStringError() }, global) catch {}; // TODO: properly propagate exception upwards return null; } else { return createLOLHTMLError(global); @@ -586,7 +586,7 @@ pub const HTMLRewriter = struct { if (!is_async) response.finalize(); sink.response = undefined; if (is_async) { - response.getBodyValue().toErrorInstance(.{ .Message = createLOLHTMLStringError() }, global); + response.getBodyValue().toErrorInstance(.{ .Message = createLOLHTMLStringError() }, global) catch {}; // TODO: properly propagate exception upwards return null; } else { return createLOLHTMLError(global); @@ -619,7 +619,7 @@ pub const HTMLRewriter = struct { bodyValue, this.global, null, - ); + ) catch {}; // TODO: properly propagate exception upwards } pub fn write(this: *BufferOutputSink, bytes: []const u8) void { diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 544646faec..7db6d46af2 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -400,7 +400,7 @@ const ServePlugins = struct { this.ref(); const promise_value = promise.asValue(); this.state.pending.promise.strong.set(global, promise_value); - promise_value.then(global, this, onResolveImpl, onRejectImpl); + try promise_value.then(global, this, onResolveImpl, onRejectImpl); return; }, .fulfilled => { @@ -593,6 +593,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d return p.getOrStartLoad(server.globalThis, callback) catch |err| switch (err) { error.JSError => std.debug.panic("unhandled exception from ServePlugins.getStartOrLoad", .{}), error.OutOfMemory => bun.outOfMemory(), + error.JSTerminated => std.debug.panic("unhandled exception from ServePlugins.getStartOrLoad", .{}), }; } // no plugins @@ -1943,7 +1944,7 @@ pub fn NewServer(protocol_enum: enum { http, https }, development_kind: enum { d node_response.promise = strong_promise; strong_promise = .empty; - result._then2(globalThis, strong_self, NodeHTTPResponse.Bun__NodeHTTPRequest__onResolve, NodeHTTPResponse.Bun__NodeHTTPRequest__onReject); + result.then2(globalThis, strong_self, NodeHTTPResponse.Bun__NodeHTTPRequest__onResolve, NodeHTTPResponse.Bun__NodeHTTPRequest__onReject) catch {}; // TODO: properly propagate exception upwards is_async = true; } @@ -2923,7 +2924,7 @@ pub const ServerAllConnectionsClosedTask = struct { vm.eventLoop().enqueueTask(jsc.Task.init(ptr)); } - pub fn runFromJSThread(this: *ServerAllConnectionsClosedTask, vm: *jsc.VirtualMachine) void { + pub fn runFromJSThread(this: *ServerAllConnectionsClosedTask, vm: *jsc.VirtualMachine) bun.JSTerminated!void { httplog("ServerAllConnectionsClosedTask runFromJSThread", .{}); const globalObject = this.globalObject; @@ -2936,7 +2937,7 @@ pub const ServerAllConnectionsClosedTask = struct { bun.destroy(this); if (!vm.isShuttingDown()) { - promise.resolve(globalObject, .js_undefined); + try promise.resolve(globalObject, .js_undefined); } } }; @@ -3245,6 +3246,7 @@ pub export fn Server__setIdleTimeout(server: jsc.JSValue, seconds: jsc.JSValue, error.OutOfMemory => { _ = globalThis.throwOutOfMemoryValue(); }, + error.JSTerminated => {}, }; } diff --git a/src/bun.js/api/server/RequestContext.zig b/src/bun.js/api/server/RequestContext.zig index d130e4eaeb..eb5d3158cc 100644 --- a/src/bun.js/api/server/RequestContext.zig +++ b/src/bun.js/api/server/RequestContext.zig @@ -672,7 +672,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, if (this.isDeadRequest()) { this.finalizeWithoutDeinit(); } else { - if (this.endRequestStreaming()) { + if (this.endRequestStreaming() catch true) { // TODO: properly propagate exception upwards any_js_calls = true; } @@ -743,7 +743,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, // User ignored the body and the connection was aborted or ended // Case 3: // Stream was not consumed and the connection was aborted or ended - _ = this.endRequestStreaming(); + _ = this.endRequestStreaming() catch {}; // TODO: properly propagate exception upwards if (this.byte_stream) |stream| { ctxLog("finalizeWithoutDeinit: stream != null", .{}); @@ -1224,7 +1224,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, this, onResolveStream, onRejectStream, - ); + ) catch {}; // TODO: properly propagate exception upwards // the response_stream should be GC'd }, @@ -1338,11 +1338,11 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, fn endRequestStreamingAndDrain(this: *RequestContext) void { assert(this.server != null); - if (this.endRequestStreaming()) { + if (this.endRequestStreaming() catch true) { // TODO: properly propagate exception upwards this.server.?.vm.drainMicrotasks(); } } - fn endRequestStreaming(this: *RequestContext) bool { + fn endRequestStreaming(this: *RequestContext) bun.JSTerminated!bool { assert(this.server != null); this.request_body_buf.clearAndFree(bun.default_allocator); @@ -1353,7 +1353,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object // but we received nothing or the connection was aborted if (body.value == .Locked) { - body.value.toErrorInstance(.{ .AbortReason = .ConnectionClosed }, this.server.?.globalThis); + try body.value.toErrorInstance(.{ .AbortReason = .ConnectionClosed }, this.server.?.globalThis); return true; } } @@ -1479,8 +1479,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, const path = blob.store.?.data.s3.path(); const env = globalThis.bunVM().transpiler.env; - S3.stat(credentials, path, @ptrCast(&onS3SizeResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); - + S3.stat(credentials, path, @ptrCast(&onS3SizeResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null) catch {}; // TODO: properly propagate exception upwards return; } this.renderMetadata(); @@ -1586,7 +1585,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, switch (promise.unwrap(vm.global.vm(), .mark_handled)) { .pending => { ctx.ref(); - response_value.then(this.globalThis, ctx, RequestContext.onResolve, RequestContext.onReject); + response_value.then(this.globalThis, ctx, RequestContext.onResolve, RequestContext.onReject) catch {}; // TODO: properly propagate exception upwards return; }, .fulfilled => |fulfilled_value| { @@ -2155,7 +2154,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, ctx, RequestContext.onResolve, RequestContext.onReject, - ); + ) catch {}; // TODO: properly propagate exception upwards }, .fulfilled => |fulfilled_value| { // if you return a Response object or a Promise @@ -2377,7 +2376,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, .temporary = bun.ByteList.fromBorrowedSliceDangerous(chunk), }, bun.default_allocator, - ); + ) catch {}; // TODO: properly propagate exception upwards } else { var strong = this.request_body_readable_stream_ref; this.request_body_readable_stream_ref = .{}; @@ -2393,7 +2392,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, .temporary_and_done = bun.ByteList.fromBorrowedSliceDangerous(chunk), }, bun.default_allocator, - ); + ) catch {}; // TODO: properly propagate exception upwards } return; @@ -2421,7 +2420,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, // } else { bytes.ensureTotalCapacityPrecise(this.allocator, total) catch |err| { this.request_body_buf.clearAndFree(this.allocator); - body.value.toError(err, globalThis); + body.value.toError(err, globalThis) catch {}; // TODO: properly propagate exception upwards break :getter; }; @@ -2443,7 +2442,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, loop.enter(); defer loop.exit(); - old.resolve(&body.value, globalThis, null); + old.resolve(&body.value, globalThis, null) catch {}; // TODO: properly propagate exception upwards } return; } @@ -2496,7 +2495,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, var old = body.value; old.Locked.onReceiveValue = null; var new_body: WebCore.Body.Value = .{ .Null = {} }; - old.resolve(&new_body, server.globalThis, null); + old.resolve(&new_body, server.globalThis, null) catch {}; // TODO: properly propagate exception upwards body.value = new_body; } } diff --git a/src/bun.js/api/server/SSLConfig.zig b/src/bun.js/api/server/SSLConfig.zig index 3d36e04b5c..2e4063451f 100644 --- a/src/bun.js/api/server/SSLConfig.zig +++ b/src/bun.js/api/server/SSLConfig.zig @@ -309,6 +309,7 @@ fn handleFileForField( return handleFile(global, file) catch |err| switch (err) { error.JSError => return error.JSError, error.OutOfMemory => return error.OutOfMemory, + error.JSTerminated => return error.JSTerminated, error.EmptyFile => return global.throwInvalidArguments( std.fmt.comptimePrint("TLSOptions.{s} is an empty file", .{field}), .{}, diff --git a/src/bun.js/bindings/AnyPromise.zig b/src/bun.js/bindings/AnyPromise.zig index ef53b82bd6..601de348c0 100644 --- a/src/bun.js/bindings/AnyPromise.zig +++ b/src/bun.js/bindings/AnyPromise.zig @@ -28,19 +28,19 @@ pub const AnyPromise = union(enum) { } } - pub fn resolve(this: AnyPromise, globalThis: *JSGlobalObject, value: JSValue) void { + pub fn resolve(this: AnyPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!void { switch (this) { - inline else => |promise| promise.resolve(globalThis, value), + inline else => |promise| try promise.resolve(globalThis, value), } } - pub fn reject(this: AnyPromise, globalThis: *JSGlobalObject, value: JSValue) void { + pub fn reject(this: AnyPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!void { switch (this) { - inline else => |promise| promise.reject(globalThis, value), + inline else => |promise| try promise.reject(globalThis, value), } } - pub fn rejectAsHandled(this: AnyPromise, globalThis: *JSGlobalObject, value: JSValue) void { + pub fn rejectAsHandled(this: AnyPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!void { switch (this) { inline else => |promise| promise.rejectAsHandled(globalThis, value), } @@ -60,7 +60,7 @@ pub const AnyPromise = union(enum) { globalObject: *JSGlobalObject, comptime Function: anytype, args: std.meta.ArgsTuple(@TypeOf(Function)), - ) void { + ) bun.JSTerminated!void { const Args = std.meta.ArgsTuple(@TypeOf(Function)); const Fn = Function; const Wrapper = struct { @@ -76,7 +76,7 @@ pub const AnyPromise = union(enum) { defer scope.deinit(); var ctx = Wrapper{ .args = args }; JSC__AnyPromise__wrap(globalObject, this.asValue(), &ctx, @ptrCast(&Wrapper.call)); - bun.debugAssert(!scope.hasException()); // TODO: properly propagate exception upwards + try scope.assertNoExceptionExceptTermination(); } }; diff --git a/src/bun.js/bindings/CatchScope.zig b/src/bun.js/bindings/CatchScope.zig index d20f42cc28..a685574b13 100644 --- a/src/bun.js/bindings/CatchScope.zig +++ b/src/bun.js/bindings/CatchScope.zig @@ -102,12 +102,12 @@ pub const CatchScope = struct { } /// If no exception, returns. - /// If termination exception, returns JSExecutionTerminated (so you can `try`) + /// If termination exception, returns JSTerminated (so you can `try`) /// If non-termination exception, assertion failure. - pub fn assertNoExceptionExceptTermination(self: *CatchScope) bun.JSExecutionTerminated!void { + pub fn assertNoExceptionExceptTermination(self: *CatchScope) bun.JSTerminated!void { if (self.exception()) |e| { if (jsc.JSValue.fromCell(e).isTerminationException()) - return error.JSExecutionTerminated + return error.JSTerminated else if (comptime Environment.ci_assert) self.assertionFailure(e); // Unconditionally panicking here is worse for our users. @@ -166,9 +166,9 @@ pub const ExceptionValidationScope = struct { } /// If no exception, returns. - /// If termination exception, returns JSExecutionTerminated (so you can `try`) + /// If termination exception, returns JSTerminated (so you can `try`) /// If non-termination exception, assertion failure. - pub fn assertNoExceptionExceptTermination(self: *ExceptionValidationScope) bun.JSExecutionTerminated!void { + pub fn assertNoExceptionExceptTermination(self: *ExceptionValidationScope) bun.JSTerminated!void { if (Environment.ci_assert) { return self.scope.assertNoExceptionExceptTermination(); } diff --git a/src/bun.js/bindings/JSGlobalObject.zig b/src/bun.js/bindings/JSGlobalObject.zig index d1168de32d..478558a99a 100644 --- a/src/bun.js/bindings/JSGlobalObject.zig +++ b/src/bun.js/bindings/JSGlobalObject.zig @@ -506,6 +506,7 @@ pub const JSGlobalObject = opaque { switch (proof) { error.JSError => {}, error.OutOfMemory => this.throwOutOfMemory() catch {}, + error.JSTerminated => {}, } return this.tryTakeException() orelse { @@ -517,6 +518,7 @@ pub const JSGlobalObject = opaque { switch (proof) { error.JSError => {}, error.OutOfMemory => this.throwOutOfMemory() catch {}, + error.JSTerminated => {}, } return (this.tryTakeException() orelse { diff --git a/src/bun.js/bindings/JSInternalPromise.zig b/src/bun.js/bindings/JSInternalPromise.zig index 87742b5055..37e6f57c57 100644 --- a/src/bun.js/bindings/JSInternalPromise.zig +++ b/src/bun.js/bindings/JSInternalPromise.zig @@ -1,11 +1,9 @@ pub const JSInternalPromise = opaque { extern fn JSC__JSInternalPromise__create(arg0: *JSGlobalObject) *JSInternalPromise; extern fn JSC__JSInternalPromise__isHandled(arg0: *const JSInternalPromise, arg1: *VM) bool; - extern fn JSC__JSInternalPromise__reject(arg0: *JSInternalPromise, arg1: *JSGlobalObject, JSValue2: JSValue) void; extern fn JSC__JSInternalPromise__rejectAsHandled(arg0: *JSInternalPromise, arg1: *JSGlobalObject, JSValue2: JSValue) void; extern fn JSC__JSInternalPromise__rejectAsHandledException(arg0: *JSInternalPromise, arg1: *JSGlobalObject, arg2: *jsc.Exception) void; extern fn JSC__JSInternalPromise__rejectedPromise(arg0: *JSGlobalObject, JSValue1: JSValue) *JSInternalPromise; - extern fn JSC__JSInternalPromise__resolve(arg0: *JSInternalPromise, arg1: *JSGlobalObject, JSValue2: JSValue) void; extern fn JSC__JSInternalPromise__resolvedPromise(arg0: *JSGlobalObject, JSValue1: JSValue) *JSInternalPromise; extern fn JSC__JSInternalPromise__result(arg0: *const JSInternalPromise, arg1: *VM) JSValue; extern fn JSC__JSInternalPromise__setHandled(arg0: *JSInternalPromise, arg1: *VM) void; @@ -42,11 +40,11 @@ pub const JSInternalPromise = opaque { return JSC__JSInternalPromise__rejectedPromise(globalThis, value); } - pub fn resolve(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: JSValue) void { - JSC__JSInternalPromise__resolve(this, globalThis, value); + pub fn resolve(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!void { + bun.cpp.JSC__JSInternalPromise__resolve(this, globalThis, value) catch return error.JSTerminated; } - pub fn reject(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: JSValue) void { - JSC__JSInternalPromise__reject(this, globalThis, value); + pub fn reject(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!void { + bun.cpp.JSC__JSInternalPromise__reject(this, globalThis, value) catch return error.JSTerminated; } pub fn rejectAsHandled(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: JSValue) void { JSC__JSInternalPromise__rejectAsHandled(this, globalThis, value); diff --git a/src/bun.js/bindings/JSPromise.zig b/src/bun.js/bindings/JSPromise.zig index ed57743f7a..d57a75052b 100644 --- a/src/bun.js/bindings/JSPromise.zig +++ b/src/bun.js/bindings/JSPromise.zig @@ -103,31 +103,30 @@ pub const JSPromise = opaque { (this.strong.get() orelse return).asPromise().?.resolve(globalThis, val); } - pub fn reject(this: *Strong, globalThis: *jsc.JSGlobalObject, val: JSError!jsc.JSValue) void { - this.swap().reject(globalThis, val catch globalThis.tryTakeException().?); + pub fn reject(this: *Strong, globalThis: *jsc.JSGlobalObject, val: JSError!jsc.JSValue) bun.JSTerminated!void { + try this.swap().reject(globalThis, val catch globalThis.tryTakeException().?); } /// Like `reject`, except it drains microtasks at the end of the current event loop iteration. - pub fn rejectTask(this: *Strong, globalThis: *jsc.JSGlobalObject, val: jsc.JSValue) void { + pub fn rejectTask(this: *Strong, globalThis: *jsc.JSGlobalObject, val: jsc.JSValue) bun.JSTerminated!void { const loop = jsc.VirtualMachine.get().eventLoop(); loop.enter(); defer loop.exit(); - - this.reject(globalThis, val); + try this.reject(globalThis, val); } pub const rejectOnNextTick = @compileError("Either use an event loop task, or you're draining microtasks when you shouldn't be."); - pub fn resolve(this: *Strong, globalThis: *jsc.JSGlobalObject, val: jsc.JSValue) void { - this.swap().resolve(globalThis, val); + pub fn resolve(this: *Strong, globalThis: *jsc.JSGlobalObject, val: jsc.JSValue) bun.JSTerminated!void { + try this.swap().resolve(globalThis, val); } /// Like `resolve`, except it drains microtasks at the end of the current event loop iteration. - pub fn resolveTask(this: *Strong, globalThis: *jsc.JSGlobalObject, val: jsc.JSValue) void { + pub fn resolveTask(this: *Strong, globalThis: *jsc.JSGlobalObject, val: jsc.JSValue) bun.JSTerminated!void { const loop = jsc.VirtualMachine.get().eventLoop(); loop.enter(); defer loop.exit(); - this.resolve(globalThis, val); + try this.resolve(globalThis, val); } pub fn init(globalThis: *jsc.JSGlobalObject) Strong { @@ -180,7 +179,7 @@ pub const JSPromise = opaque { globalObject: *JSGlobalObject, comptime Function: anytype, args: std.meta.ArgsTuple(@TypeOf(Function)), - ) JSValue { + ) bun.JSTerminated!JSValue { const Args = std.meta.ArgsTuple(@TypeOf(Function)); const Fn = Function; const Wrapper = struct { @@ -196,7 +195,7 @@ pub const JSPromise = opaque { defer scope.deinit(); var ctx = Wrapper{ .args = args }; const promise = JSC__JSPromise__wrap(globalObject, &ctx, @ptrCast(&Wrapper.call)); - bun.debugAssert(!scope.hasException()); // TODO: properly propagate exception upwards + try scope.assertNoExceptionExceptTermination(); return promise; } @@ -265,7 +264,7 @@ pub const JSPromise = opaque { /// Fulfill an existing promise with the value /// The value can be another Promise /// If you want to create a new Promise that is already resolved, see JSPromise.resolvedPromiseValue - pub fn resolve(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) void { + pub fn resolve(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!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)); @@ -274,10 +273,10 @@ pub const JSPromise = opaque { } } - bun.cpp.JSC__JSPromise__resolve(this, globalThis, value) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards + bun.cpp.JSC__JSPromise__resolve(this, globalThis, value) catch return error.JSTerminated; } - pub fn reject(this: *JSPromise, globalThis: *JSGlobalObject, value: JSError!JSValue) void { + pub fn reject(this: *JSPromise, globalThis: *JSGlobalObject, value: JSError!JSValue) bun.JSTerminated!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)); @@ -288,11 +287,11 @@ pub const JSPromise = opaque { const err = value catch |err| globalThis.takeException(err); - bun.cpp.JSC__JSPromise__reject(this, globalThis, err) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards + bun.cpp.JSC__JSPromise__reject(this, globalThis, err) catch return error.JSTerminated; } - pub fn rejectAsHandled(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) void { - bun.cpp.JSC__JSPromise__rejectAsHandled(this, globalThis, value) catch return bun.debugAssert(false); // TODO: properly propagate exception upwards + pub fn rejectAsHandled(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) bun.JSTerminated!void { + bun.cpp.JSC__JSPromise__rejectAsHandled(this, globalThis, value) catch return error.JSTerminated; } /// Create a new pending promise. diff --git a/src/bun.js/bindings/JSValue.zig b/src/bun.js/bindings/JSValue.zig index 1b36c3c4bd..a5cf810dfb 100644 --- a/src/bun.js/bindings/JSValue.zig +++ b/src/bun.js/bindings/JSValue.zig @@ -1413,20 +1413,20 @@ pub const JSValue = enum(i64) { return JSC__JSValue___then(this, global, ctx, toJSHostFunction(resolve), toJSHostFunction(reject)); } - pub fn _then2(this: JSValue, global: *JSGlobalObject, ctx: JSValue, resolve: *const jsc.JSHostFn, reject: *const jsc.JSHostFn) void { + pub fn then2(this: JSValue, global: *JSGlobalObject, ctx: JSValue, resolve: *const jsc.JSHostFn, reject: *const jsc.JSHostFn) bun.JSTerminated!void { var scope: CatchScope = undefined; scope.init(global, @src()); defer scope.deinit(); JSC__JSValue___then(this, global, ctx, resolve, reject); - bun.debugAssert(!scope.hasException()); // TODO: properly propagate exception upwards + try scope.assertNoExceptionExceptTermination(); } - pub fn then(this: JSValue, global: *JSGlobalObject, ctx: ?*anyopaque, resolve: jsc.JSHostFnZig, reject: jsc.JSHostFnZig) void { + pub fn then(this: JSValue, global: *JSGlobalObject, ctx: ?*anyopaque, resolve: jsc.JSHostFnZig, reject: jsc.JSHostFnZig) bun.JSTerminated!void { var scope: CatchScope = undefined; scope.init(global, @src()); defer scope.deinit(); this._then(global, JSValue.fromPtrAddress(@intFromPtr(ctx)), resolve, reject); - bun.debugAssert(!scope.hasException()); // TODO: properly propagate exception upwards + try scope.assertNoExceptionExceptTermination(); } pub fn getDescription(this: JSValue, global: *JSGlobalObject) ZigString { diff --git a/src/bun.js/bindings/VM.zig b/src/bun.js/bindings/VM.zig index 784a51e7fc..641987e610 100644 --- a/src/bun.js/bindings/VM.zig +++ b/src/bun.js/bindings/VM.zig @@ -147,6 +147,14 @@ pub const VM = opaque { return JSC__VM__isEntered(vm); } + pub fn isTerminationException(vm: *VM, exception: *bun.jsc.Exception) bool { + return bun.cpp.JSC__VM__isTerminationException(vm, exception); + } + + pub fn hasTerminationRequest(vm: *VM) bool { + return bun.cpp.JSC__VM__hasTerminationRequest(vm); + } + extern fn JSC__VM__throwError(*VM, *JSGlobalObject, JSValue) void; pub fn throwError(vm: *VM, global_object: *JSGlobalObject, value: JSValue) error{JSError} { var scope: bun.jsc.ExceptionValidationScope = undefined; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index ddb7745397..378e747beb 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2934,7 +2934,7 @@ extern "C" void JSGlobalObject__clearTerminationException(JSC::JSGlobalObject* g extern "C" void Bun__queueTask(JSC::JSGlobalObject*, WebCore::EventLoopTask* task); extern "C" void Bun__queueTaskConcurrently(JSC::JSGlobalObject*, WebCore::EventLoopTask* task); -extern "C" void Bun__performTask(Zig::GlobalObject* globalObject, WebCore::EventLoopTask* task) +extern "C" [[ZIG_EXPORT(check_slow)]] void Bun__performTask(Zig::GlobalObject* globalObject, WebCore::EventLoopTask* task) { task->performTask(*globalObject->scriptExecutionContext()); } diff --git a/src/bun.js/bindings/ZigString.zig b/src/bun.js/bindings/ZigString.zig index da23004d69..b1fe333040 100644 --- a/src/bun.js/bindings/ZigString.zig +++ b/src/bun.js/bindings/ZigString.zig @@ -365,7 +365,7 @@ pub const ZigString = extern struct { return .{ .allocator = .init(allocator), .ptr = duped.ptr, .len = this.len }; } - pub fn cloneIfNeeded(this: Slice, allocator: std.mem.Allocator) !Slice { + pub fn cloneIfNeeded(this: Slice, allocator: std.mem.Allocator) bun.OOM!Slice { if (this.isAllocated()) { return this; } diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index e0a81bd8b8..5d88518ca6 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3400,8 +3400,7 @@ JSC::EncodedJSValue JSC__JSPromise__wrap(JSC::JSGlobalObject* globalObject, void RELEASE_AND_RETURN(scope, JSValue::encode(JSC::JSPromise::resolvedPromise(globalObject, result))); } -[[ZIG_EXPORT(check_slow)]] void JSC__JSPromise__reject(JSC::JSPromise* arg0, JSC::JSGlobalObject* globalObject, - JSC::EncodedJSValue JSValue2) +[[ZIG_EXPORT(check_slow)]] void JSC__JSPromise__reject(JSC::JSPromise* arg0, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue JSValue2) { JSValue value = JSC::JSValue::decode(JSValue2); ASSERT_WITH_MESSAGE(!value.isEmpty(), "Promise.reject cannot be called with a empty JSValue"); @@ -3418,8 +3417,8 @@ JSC::EncodedJSValue JSC__JSPromise__wrap(JSC::JSGlobalObject* globalObject, void arg0->reject(globalObject, exception); } -[[ZIG_EXPORT(check_slow)]] void JSC__JSPromise__rejectAsHandled(JSC::JSPromise* arg0, JSC::JSGlobalObject* arg1, - JSC::EncodedJSValue JSValue2) + +[[ZIG_EXPORT(check_slow)]] void JSC__JSPromise__rejectAsHandled(JSC::JSPromise* arg0, JSC::JSGlobalObject* arg1, JSC::EncodedJSValue JSValue2) { ASSERT_WITH_MESSAGE(arg0->inherits(), "Argument is not a promise"); ASSERT_WITH_MESSAGE(arg0->status(arg0->vm()) == JSC::JSPromise::Status::Pending, "Promise is already resolved or rejected"); @@ -3432,8 +3431,7 @@ JSC::JSPromise* JSC__JSPromise__rejectedPromise(JSC::JSGlobalObject* arg0, JSC:: return JSC::JSPromise::rejectedPromise(arg0, JSC::JSValue::decode(JSValue1)); } -[[ZIG_EXPORT(check_slow)]] void JSC__JSPromise__resolve(JSC::JSPromise* arg0, JSC::JSGlobalObject* arg1, - JSC::EncodedJSValue JSValue2) +[[ZIG_EXPORT(check_slow)]] void JSC__JSPromise__resolve(JSC::JSPromise* arg0, JSC::JSGlobalObject* arg1, JSC::EncodedJSValue JSValue2) { JSValue target = JSValue::decode(JSValue2); @@ -3447,8 +3445,7 @@ JSC::JSPromise* JSC__JSPromise__rejectedPromise(JSC::JSGlobalObject* arg0, JSC:: } // This implementation closely mimics the one in JSC::JSPromise::resolve -void JSC__JSPromise__resolveOnNextTick(JSC::JSPromise* promise, JSC::JSGlobalObject* lexicalGlobalObject, - JSC::EncodedJSValue encoedValue) +void JSC__JSPromise__resolveOnNextTick(JSC::JSPromise* promise, JSC::JSGlobalObject* lexicalGlobalObject, JSC::EncodedJSValue encoedValue) { return JSC__JSPromise__resolve(promise, lexicalGlobalObject, encoedValue); } @@ -3575,8 +3572,8 @@ JSC::JSInternalPromise* JSC__JSInternalPromise__create(JSC::JSGlobalObject* glob return JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); } -void JSC__JSInternalPromise__reject(JSC::JSInternalPromise* arg0, JSC::JSGlobalObject* globalObject, - JSC::EncodedJSValue JSValue2) +[[ZIG_EXPORT(check_slow)]] +void JSC__JSInternalPromise__reject(JSC::JSInternalPromise* arg0, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue JSValue2) { JSValue value = JSC::JSValue::decode(JSValue2); auto& vm = JSC::getVM(globalObject); @@ -3608,8 +3605,8 @@ JSC::JSInternalPromise* JSC__JSInternalPromise__rejectedPromise(JSC::JSGlobalObj JSC::JSInternalPromise::rejectedPromise(arg0, JSC::JSValue::decode(JSValue1))); } -void JSC__JSInternalPromise__resolve(JSC::JSInternalPromise* arg0, JSC::JSGlobalObject* arg1, - JSC::EncodedJSValue JSValue2) +[[ZIG_EXPORT(check_slow)]] +void JSC__JSInternalPromise__resolve(JSC::JSInternalPromise* arg0, JSC::JSGlobalObject* arg1, JSC::EncodedJSValue JSValue2) { arg0->resolve(arg1, JSC::JSValue::decode(JSValue2)); } @@ -4685,6 +4682,18 @@ bool JSC__VM__isEntered(JSC::VM* arg0) return (*arg0).isEntered(); } +[[ZIG_EXPORT(nothrow)]] +bool JSC__VM__isTerminationException(JSC::VM* vm, JSC::Exception* exception) +{ + return vm->isTerminationException(exception); +} + +[[ZIG_EXPORT(nothrow)]] +bool JSC__VM__hasTerminationRequest(JSC::VM* vm) +{ + return vm->hasTerminationRequest(); +} + void JSC__VM__setExecutionForbidden(JSC::VM* arg0, bool arg1) { (*arg0).setExecutionForbidden(); diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index b84f9c7d41..dd54426e9a 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -89,7 +89,7 @@ pub fn exit(this: *EventLoop) void { this.entered_event_loop_count -= 1; } -pub fn exitMaybeDrainMicrotasks(this: *EventLoop, allow_drain_microtask: bool) bun.JSExecutionTerminated!void { +pub fn exitMaybeDrainMicrotasks(this: *EventLoop, allow_drain_microtask: bool) bun.JSTerminated!void { const count = this.entered_event_loop_count; log("exit() = {d}", .{count - 1}); @@ -121,18 +121,16 @@ pub fn tickWhilePaused(this: *EventLoop, done: *bool) void { const DrainMicrotasksResult = enum(u8) { success = 0, - JSExecutionTerminated = 1, + JSTerminated = 1, }; extern fn JSC__JSGlobalObject__drainMicrotasks(*jsc.JSGlobalObject) DrainMicrotasksResult; -pub fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *jsc.JSGlobalObject, jsc_vm: *jsc.VM) bun.JSExecutionTerminated!void { +pub fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *jsc.JSGlobalObject, jsc_vm: *jsc.VM) bun.JSTerminated!void { jsc.markBinding(@src()); jsc_vm.releaseWeakRefs(); switch (JSC__JSGlobalObject__drainMicrotasks(globalObject)) { .success => {}, - .JSExecutionTerminated => { - return error.JSExecutionTerminated; - }, + .JSTerminated => return error.JSTerminated, } this.virtual_machine.is_inside_deferred_task_queue = true; @@ -144,7 +142,7 @@ pub fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *jsc.JSGlobalOb } } -pub fn drainMicrotasks(this: *EventLoop) bun.JSExecutionTerminated!void { +pub fn drainMicrotasks(this: *EventLoop) bun.JSTerminated!void { try this.drainMicrotasksWithGlobal(this.global, this.virtual_machine.jsc_vm); } @@ -215,7 +213,9 @@ pub fn runCallbackWithResult(this: *EventLoop, callback: jsc.JSValue, globalObje } fn tickWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u32 { - return this.tickQueueWithCount(virtual_machine); + var counter: u32 = 0; + this.tickQueueWithCount(virtual_machine, &counter) catch {}; + return counter; } pub fn tickImmediateTasks(this: *EventLoop, virtual_machine: *VirtualMachine) void { diff --git a/src/bun.js/event_loop/AnyTask.zig b/src/bun.js/event_loop/AnyTask.zig index 2c72b6701d..566144c1b0 100644 --- a/src/bun.js/event_loop/AnyTask.zig +++ b/src/bun.js/event_loop/AnyTask.zig @@ -4,17 +4,17 @@ const AnyTask = @This(); ctx: ?*anyopaque, -callback: *const (fn (*anyopaque) void), +callback: *const (fn (*anyopaque) bun.JSError!void), pub fn task(this: *AnyTask) Task { return Task.init(this); } -pub fn run(this: *AnyTask) void { +pub fn run(this: *AnyTask) bun.JSError!void { @setRuntimeSafety(false); const callback = this.callback; const ctx = this.ctx; - callback(ctx.?); + try callback(ctx.?); } pub fn New(comptime Type: type, comptime Callback: anytype) type { @@ -26,8 +26,8 @@ pub fn New(comptime Type: type, comptime Callback: anytype) type { }; } - pub fn wrap(this: ?*anyopaque) void { - @call(bun.callmod_inline, Callback, .{@as(*Type, @ptrCast(@alignCast(this.?)))}); + pub fn wrap(this: ?*anyopaque) bun.JSError!void { + return @call(bun.callmod_inline, Callback, .{@as(*Type, @ptrCast(@alignCast(this.?)))}); } }; } diff --git a/src/bun.js/event_loop/ConcurrentPromiseTask.zig b/src/bun.js/event_loop/ConcurrentPromiseTask.zig index 6a96fb2b48..1a041bab10 100644 --- a/src/bun.js/event_loop/ConcurrentPromiseTask.zig +++ b/src/bun.js/event_loop/ConcurrentPromiseTask.zig @@ -40,13 +40,13 @@ pub fn ConcurrentPromiseTask(comptime Context: type) type { this.onFinish(); } - pub fn runFromJS(this: *This) void { + pub fn runFromJS(this: *This) bun.JSTerminated!void { const promise = this.promise.swap(); this.ref.unref(this.event_loop.virtual_machine); var ctx = this.ctx; - ctx.then(promise); + return ctx.then(promise); } pub fn schedule(this: *This) void { diff --git a/src/bun.js/event_loop/CppTask.zig b/src/bun.js/event_loop/CppTask.zig index f0c1433f1f..f1ad33d6e9 100644 --- a/src/bun.js/event_loop/CppTask.zig +++ b/src/bun.js/event_loop/CppTask.zig @@ -1,12 +1,8 @@ /// A task created from C++ code, usually via ScriptExecutionContext. pub const CppTask = opaque { - extern fn Bun__performTask(globalObject: *jsc.JSGlobalObject, task: *CppTask) void; - pub fn run(this: *CppTask, global: *jsc.JSGlobalObject) void { + pub fn run(this: *CppTask, global: *jsc.JSGlobalObject) bun.JSError!void { jsc.markBinding(@src()); - // TODO: properly propagate exception upwards - bun.jsc.fromJSHostCallGeneric(global, @src(), Bun__performTask, .{ global, this }) catch |err| { - _ = global.reportUncaughtException(global.takeException(err).asException(global.vm()).?); - }; + return bun.cpp.Bun__performTask(global, this); } }; diff --git a/src/bun.js/event_loop/JSCScheduler.zig b/src/bun.js/event_loop/JSCScheduler.zig index c79258034f..dbce749657 100644 --- a/src/bun.js/event_loop/JSCScheduler.zig +++ b/src/bun.js/event_loop/JSCScheduler.zig @@ -2,13 +2,13 @@ const JSCScheduler = @This(); pub const JSCDeferredWorkTask = opaque { extern fn Bun__runDeferredWork(task: *JSCScheduler.JSCDeferredWorkTask) void; - pub fn run(task: *JSCScheduler.JSCDeferredWorkTask) void { + pub fn run(task: *JSCScheduler.JSCDeferredWorkTask) bun.JSTerminated!void { const globalThis = bun.jsc.VirtualMachine.get().global; var scope: bun.jsc.ExceptionValidationScope = undefined; scope.init(globalThis, @src()); defer scope.deinit(); Bun__runDeferredWork(task); - scope.assertNoExceptionExceptTermination() catch return; // TODO: properly propagate exception upwards + try scope.assertNoExceptionExceptTermination(); } }; diff --git a/src/bun.js/event_loop/ManagedTask.zig b/src/bun.js/event_loop/ManagedTask.zig index 49eba9f72b..f768a5fc69 100644 --- a/src/bun.js/event_loop/ManagedTask.zig +++ b/src/bun.js/event_loop/ManagedTask.zig @@ -4,18 +4,18 @@ const ManagedTask = @This(); ctx: ?*anyopaque, -callback: *const (fn (*anyopaque) void), +callback: *const (fn (*anyopaque) bun.JSTerminated!void), pub fn task(this: *ManagedTask) Task { return Task.init(this); } -pub fn run(this: *ManagedTask) void { +pub fn run(this: *ManagedTask) bun.JSTerminated!void { @setRuntimeSafety(false); + defer bun.default_allocator.destroy(this); const callback = this.callback; const ctx = this.ctx; - callback(ctx.?); - bun.default_allocator.destroy(this); + try callback(ctx.?); } pub fn cancel(this: *ManagedTask) void { @@ -35,8 +35,8 @@ pub fn New(comptime Type: type, comptime Callback: anytype) type { return managed.task(); } - pub fn wrap(this: ?*anyopaque) void { - @call(bun.callmod_inline, Callback, .{@as(*Type, @ptrCast(@alignCast(this.?)))}); + pub fn wrap(this: ?*anyopaque) bun.JSTerminated!void { + return @call(bun.callmod_inline, Callback, .{@as(*Type, @ptrCast(@alignCast(this.?)))}); } }; } diff --git a/src/bun.js/event_loop/Task.zig b/src/bun.js/event_loop/Task.zig index e4a4ed793b..58cc3a6129 100644 --- a/src/bun.js/event_loop/Task.zig +++ b/src/bun.js/event_loop/Task.zig @@ -94,10 +94,9 @@ pub const Task = TaggedPointerUnion(.{ Writev, }); -pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u32 { +pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine, counter: *u32) bun.JSTerminated!void { var global = this.global; const global_vm = global.vm(); - var counter: u32 = 0; if (comptime Environment.isDebug) { if (this.debug.js_call_count_outside_tick_queue > this.debug.drain_microtasks_count_outside_tick_queue) { @@ -132,7 +131,7 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 while (this.tasks.readItem()) |task| { log("run {s}", .{@tagName(task.tag())}); - defer counter += 1; + defer counter.* += 1; switch (task.tag()) { @field(Task.Tag, @typeName(ShellAsync)) => { var shell_ls_task: *ShellAsync = task.get(ShellAsync).?; @@ -197,11 +196,11 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 }, @field(Task.Tag, @typeName(FetchTasklet)) => { var fetch_task: *Fetch.FetchTasklet = task.get(Fetch.FetchTasklet).?; - fetch_task.onProgressUpdate(); + try fetch_task.onProgressUpdate(); }, @field(Task.Tag, @typeName(S3HttpSimpleTask)) => { var s3_task: *S3HttpSimpleTask = task.get(S3HttpSimpleTask).?; - s3_task.onResponse(); + try s3_task.onResponse(); }, @field(Task.Tag, @typeName(S3HttpDownloadStreamingTask)) => { var s3_task: *S3HttpDownloadStreamingTask = task.get(S3HttpDownloadStreamingTask).?; @@ -209,18 +208,18 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 }, @field(Task.Tag, @typeName(AsyncGlobWalkTask)) => { var globWalkTask: *AsyncGlobWalkTask = task.get(AsyncGlobWalkTask).?; - globWalkTask.*.runFromJS(); - globWalkTask.deinit(); + defer globWalkTask.deinit(); + try globWalkTask.runFromJS(); }, @field(Task.Tag, @typeName(AsyncTransformTask)) => { var transform_task: *AsyncTransformTask = task.get(AsyncTransformTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); + defer transform_task.deinit(); + try transform_task.runFromJS(); }, @field(Task.Tag, @typeName(CopyFilePromiseTask)) => { var transform_task: *CopyFilePromiseTask = task.get(CopyFilePromiseTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); + defer transform_task.deinit(); + try transform_task.runFromJS(); }, @field(Task.Tag, @typeName(bun.api.napi.napi_async_work)) => { const transform_task: *bun.api.napi.napi_async_work = task.get(bun.api.napi.napi_async_work).?; @@ -232,25 +231,27 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 }, @field(Task.Tag, @typeName(ReadFileTask)) => { var transform_task: *ReadFileTask = task.get(ReadFileTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); + defer transform_task.deinit(); + try transform_task.runFromJS(); }, @field(Task.Tag, @typeName(JSCDeferredWorkTask)) => { var jsc_task: *JSCDeferredWorkTask = task.get(JSCDeferredWorkTask).?; jsc.markBinding(@src()); - jsc_task.run(); + try jsc_task.run(); }, @field(Task.Tag, @typeName(WriteFileTask)) => { var transform_task: *WriteFileTask = task.get(WriteFileTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); + defer transform_task.deinit(); + try transform_task.runFromJS(); }, @field(Task.Tag, @typeName(HotReloadTask)) => { const transform_task: *HotReloadTask = task.get(HotReloadTask).?; + defer transform_task.deinit(); transform_task.run(); - transform_task.deinit(); // special case: we return - return 0; + // hot reload runs immediately so it should not drain microtasks + counter.* = 0; + return; }, @field(Task.Tag, @typeName(bun.bake.DevServer.HotReloadEvent)) => { const hmr_task: *bun.bake.DevServer.HotReloadEvent = task.get(bun.bake.DevServer.HotReloadEvent).?; @@ -258,194 +259,193 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 }, @field(Task.Tag, @typeName(FSWatchTask)) => { var transform_task: *FSWatchTask = task.get(FSWatchTask).?; - transform_task.*.run(); - transform_task.deinit(); + defer transform_task.deinit(); + transform_task.run(); }, @field(Task.Tag, @typeName(AnyTask)) => { var any: *AnyTask = task.get(AnyTask).?; - any.run(); + any.run() catch |err| try reportErrorOrTerminate(global, err); }, @field(Task.Tag, @typeName(ManagedTask)) => { var any: *ManagedTask = task.get(ManagedTask).?; - any.run(); + any.run() catch |err| try reportErrorOrTerminate(global, err); }, @field(Task.Tag, @typeName(CppTask)) => { var any: *CppTask = task.get(CppTask).?; - any.run(global); + any.run(global) catch |err| try reportErrorOrTerminate(global, err); }, @field(Task.Tag, @typeName(PollPendingModulesTask)) => { virtual_machine.modules.onPoll(); }, @field(Task.Tag, @typeName(GetAddrInfoRequestTask)) => { if (Environment.os == .windows) @panic("This should not be reachable on Windows"); - var any: *GetAddrInfoRequestTask = task.get(GetAddrInfoRequestTask).?; - any.runFromJS(); - any.deinit(); + defer any.deinit(); + try any.runFromJS(); }, @field(Task.Tag, @typeName(Stat)) => { var any: *Stat = task.get(Stat).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Lstat)) => { var any: *Lstat = task.get(Lstat).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Fstat)) => { var any: *Fstat = task.get(Fstat).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Open)) => { var any: *Open = task.get(Open).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(ReadFile)) => { var any: *ReadFile = task.get(ReadFile).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(WriteFile)) => { var any: *WriteFile = task.get(WriteFile).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(CopyFile)) => { var any: *CopyFile = task.get(CopyFile).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Read)) => { var any: *Read = task.get(Read).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Write)) => { var any: *Write = task.get(Write).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Truncate)) => { var any: *Truncate = task.get(Truncate).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Writev)) => { var any: *Writev = task.get(Writev).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Readv)) => { var any: *Readv = task.get(Readv).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Rename)) => { var any: *Rename = task.get(Rename).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(FTruncate)) => { var any: *FTruncate = task.get(FTruncate).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Readdir)) => { var any: *Readdir = task.get(Readdir).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(ReaddirRecursive)) => { var any: *ReaddirRecursive = task.get(ReaddirRecursive).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Close)) => { var any: *Close = task.get(Close).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Rm)) => { var any: *Rm = task.get(Rm).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Rmdir)) => { var any: *Rmdir = task.get(Rmdir).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Chown)) => { var any: *Chown = task.get(Chown).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(FChown)) => { var any: *FChown = task.get(FChown).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Utimes)) => { var any: *Utimes = task.get(Utimes).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Lutimes)) => { var any: *Lutimes = task.get(Lutimes).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Chmod)) => { var any: *Chmod = task.get(Chmod).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Fchmod)) => { var any: *Fchmod = task.get(Fchmod).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Link)) => { var any: *Link = task.get(Link).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Symlink)) => { var any: *Symlink = task.get(Symlink).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Readlink)) => { var any: *Readlink = task.get(Readlink).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Realpath)) => { var any: *Realpath = task.get(Realpath).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(RealpathNonNative)) => { var any: *RealpathNonNative = task.get(RealpathNonNative).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Mkdir)) => { var any: *Mkdir = task.get(Mkdir).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Fsync)) => { var any: *Fsync = task.get(Fsync).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Fdatasync)) => { var any: *Fdatasync = task.get(Fdatasync).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Access)) => { var any: *Access = task.get(Access).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(AppendFile)) => { var any: *AppendFile = task.get(AppendFile).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Mkdtemp)) => { var any: *Mkdtemp = task.get(Mkdtemp).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Exists)) => { var any: *Exists = task.get(Exists).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Futimes)) => { var any: *Futimes = task.get(Futimes).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Lchmod)) => { var any: *Lchmod = task.get(Lchmod).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Lchown)) => { var any: *Lchown = task.get(Lchown).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(Unlink)) => { var any: *Unlink = task.get(Unlink).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(NativeZlib)) => { var any: *NativeZlib = task.get(NativeZlib).?; @@ -470,7 +470,7 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 }, @field(Task.Tag, @typeName(ServerAllConnectionsClosedTask)) => { var any: *ServerAllConnectionsClosedTask = task.get(ServerAllConnectionsClosedTask).?; - any.runFromJSThread(virtual_machine); + try any.runFromJSThread(virtual_machine); }, @field(Task.Tag, @typeName(bun.bundle_v2.DeferredBatchTask)) => { var any: *bun.bundle_v2.DeferredBatchTask = task.get(bun.bundle_v2.DeferredBatchTask).?; @@ -480,11 +480,12 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 PosixSignalTask.runFromJSThread(@intCast(task.asUintptr()), global); }, @field(Task.Tag, @typeName(NapiFinalizerTask)) => { - task.get(NapiFinalizerTask).?.runOnJSThread(); + var any: *NapiFinalizerTask = task.get(NapiFinalizerTask).?; + any.runOnJSThread(); }, @field(Task.Tag, @typeName(StatFS)) => { var any: *StatFS = task.get(StatFS).?; - any.runFromJSThread(); + try any.runFromJSThread(); }, @field(Task.Tag, @typeName(FlushPendingFileSinkTask)) => { var any: *FlushPendingFileSinkTask = task.get(FlushPendingFileSinkTask).?; @@ -504,11 +505,20 @@ pub fn tickQueueWithCount(this: *EventLoop, virtual_machine: *VirtualMachine) u3 }, } - this.drainMicrotasksWithGlobal(global, global_vm) catch return counter; + try this.drainMicrotasksWithGlobal(global, global_vm); } this.tasks.head = if (this.tasks.count == 0) 0 else this.tasks.head; - return counter; +} + +pub fn reportErrorOrTerminate(global: *jsc.JSGlobalObject, proof: bun.JSError) bun.JSTerminated!void { + @branchHint(.cold); + if (proof == error.JSTerminated) return error.JSTerminated; + const vm = global.vm(); + const ex = global.takeException(proof).asException(vm).?; + const is_termination_exception = vm.isTerminationException(ex); + if (is_termination_exception) return error.JSTerminated; + _ = global.reportUncaughtException(ex); } // const PromiseTask = JSInternalPromise.Completion.PromiseTask; diff --git a/src/bun.js/event_loop/WorkTask.zig b/src/bun.js/event_loop/WorkTask.zig index bcdbbfc57b..1e792c7e9a 100644 --- a/src/bun.js/event_loop/WorkTask.zig +++ b/src/bun.js/event_loop/WorkTask.zig @@ -52,7 +52,7 @@ pub fn WorkTask(comptime Context: type) type { Context.run(this.ctx, this); } - pub fn runFromJS(this: *This) void { + pub fn runFromJS(this: *This) bun.JSTerminated!void { var ctx = this.ctx; const tracker = this.async_task_tracker; const vm = this.event_loop.virtual_machine; @@ -60,8 +60,8 @@ pub fn WorkTask(comptime Context: type) type { this.ref.unref(vm); tracker.willDispatch(globalThis); - ctx.then(globalThis); - tracker.didDispatch(globalThis); + defer tracker.didDispatch(globalThis); + return ctx.then(globalThis); } pub fn schedule(this: *This) void { diff --git a/src/bun.js/ipc.zig b/src/bun.js/ipc.zig index 660dbe0136..3d961afa7a 100644 --- a/src/bun.js/ipc.zig +++ b/src/bun.js/ipc.zig @@ -206,6 +206,10 @@ const json = struct { globalThis.clearException(); return IPCDecodeError.InvalidFormat; }, + error.JSTerminated => { + globalThis.clearException(); + return IPCDecodeError.InvalidFormat; + }, error.OutOfMemory => return bun.outOfMemory(), }; @@ -1138,7 +1142,7 @@ fn onData2(send_queue: *SendQueue, all_data: []const u8) void { log("hit NotEnoughBytes", .{}); return; }, - error.InvalidFormat, error.JSError => { + error.InvalidFormat, error.JSError, error.JSTerminated => { send_queue.closeSocket(.failure, .user); return; }, @@ -1171,7 +1175,7 @@ fn onData2(send_queue: *SendQueue, all_data: []const u8) void { log("hit NotEnoughBytes2", .{}); return; }, - error.InvalidFormat, error.JSError => { + error.InvalidFormat, error.JSError, error.JSTerminated => { send_queue.closeSocket(.failure, .user); return; }, @@ -1332,7 +1336,7 @@ pub const IPCHandlers = struct { log("hit NotEnoughBytes3", .{}); return; }, - error.InvalidFormat, error.JSError => { + error.InvalidFormat, error.JSError, error.JSTerminated => { send_queue.closeSocket(.failure, .user); return; }, diff --git a/src/bun.js/jsc/host_fn.zig b/src/bun.js/jsc/host_fn.zig index a92e4c11b4..0712b05d43 100644 --- a/src/bun.js/jsc/host_fn.zig +++ b/src/bun.js/jsc/host_fn.zig @@ -33,6 +33,7 @@ pub fn toJSHostFnResult(globalThis: *JSGlobalObject, result: bun.JSError!JSValue const value = result catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => globalThis.throwOutOfMemoryValue(), + error.JSTerminated => .zero, }; debugExceptionAssertion(globalThis, value, "_unknown_".*); return value; @@ -40,6 +41,7 @@ pub fn toJSHostFnResult(globalThis: *JSGlobalObject, result: bun.JSError!JSValue return result catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => globalThis.throwOutOfMemoryValue(), + error.JSTerminated => .zero, }; } @@ -66,13 +68,14 @@ fn debugExceptionAssertion(globalThis: *JSGlobalObject, value: JSValue, comptime bun.assert((value == .zero) == globalThis.hasException()); } -pub fn toJSHostSetterValue(globalThis: *JSGlobalObject, value: error{ OutOfMemory, JSError }!void) bool { +pub fn toJSHostSetterValue(globalThis: *JSGlobalObject, value: error{ OutOfMemory, JSError, JSTerminated }!void) bool { value catch |err| switch (err) { error.JSError => return false, error.OutOfMemory => { _ = globalThis.throwOutOfMemoryValue(); return false; }, + error.JSTerminated => return false, }; return true; } @@ -90,10 +93,11 @@ pub fn toJSHostCall( scope.init(globalThis, src); defer scope.deinit(); - const returned: error{ OutOfMemory, JSError }!JSValue = @call(.auto, function, args); + const returned: error{ OutOfMemory, JSError, JSTerminated }!JSValue = @call(.auto, function, args); const normal = returned catch |err| switch (err) { error.JSError => .zero, error.OutOfMemory => globalThis.throwOutOfMemoryValue(), + error.JSTerminated => .zero, }; scope.assertExceptionPresenceMatches(normal == .zero); return normal; @@ -110,7 +114,7 @@ pub fn fromJSHostCall( src: std.builtin.SourceLocation, comptime function: anytype, args: std.meta.ArgsTuple(@TypeOf(function)), -) bun.JSError!JSValue { +) error{JSError}!JSValue { var scope: jsc.ExceptionValidationScope = undefined; scope.init(globalThis, src); defer scope.deinit(); @@ -127,7 +131,7 @@ pub fn fromJSHostCallGeneric( src: std.builtin.SourceLocation, comptime function: anytype, args: std.meta.ArgsTuple(@TypeOf(function)), -) bun.JSError!@typeInfo(@TypeOf(function)).@"fn".return_type.? { +) error{JSError}!@typeInfo(@TypeOf(function)).@"fn".return_type.? { var scope: jsc.CatchScope = undefined; scope.init(globalThis, src); defer scope.deinit(); @@ -171,6 +175,7 @@ pub fn voidFromJSError(err: bun.JSError, globalThis: *jsc.JSGlobalObject) void { switch (err) { error.JSError => {}, error.OutOfMemory => globalThis.throwOutOfMemory() catch {}, + error.JSTerminated => {}, } // TODO: catch exception, declare throw scope, re-throw // c++ needs to be able to see that zig functions can throw for BUN_JSC_validateExceptionChecks diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 174563b5c7..89b7d123a9 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -287,7 +287,7 @@ pub const Async = struct { this.globalObject.bunVM().eventLoop().enqueueTask(jsc.Task.init(this)); } - pub fn runFromJSThread(this: *Task) void { + pub fn runFromJSThread(this: *Task) bun.JSTerminated!void { defer this.deinit(); const globalObject = this.globalObject; @@ -308,10 +308,10 @@ pub const Async = struct { switch (success) { false => { - promise.reject(globalObject, result); + try promise.reject(globalObject, result); }, true => { - promise.resolve(globalObject, result); + try promise.resolve(globalObject, result); }, } } @@ -387,7 +387,7 @@ pub const Async = struct { this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(jsc.ConcurrentTask.createFrom(this)); } - pub fn runFromJSThread(this: *Task) void { + pub fn runFromJSThread(this: *Task) bun.JSTerminated!void { defer this.deinit(); const globalObject = this.globalObject; @@ -409,17 +409,17 @@ pub const Async = struct { if (have_abort_signal) check_abort: { const signal = this.args.signal orelse break :check_abort; if (signal.reasonIfAborted(globalObject)) |reason| { - promise.reject(globalObject, reason.toJS(globalObject)); + try promise.reject(globalObject, reason.toJS(globalObject)); return; } } switch (success) { false => { - promise.reject(globalObject, result); + try promise.reject(globalObject, result); }, true => { - promise.resolve(globalObject, result); + try promise.resolve(globalObject, result); }, } } @@ -651,10 +651,10 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type { } pub fn runFromJSThreadMini(this: *ThisAsyncCpTask, _: *anyopaque) void { - this.runFromJSThread(); + this.runFromJSThread() catch {}; // TODO: properly propagate exception upwards } - fn runFromJSThread(this: *ThisAsyncCpTask) void { + fn runFromJSThread(this: *ThisAsyncCpTask) bun.JSTerminated!void { if (comptime is_shell) { this.shelltask.cpOnFinish(this.result); this.deinit(); @@ -681,10 +681,10 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type { this.deinit(); switch (success) { false => { - promise.reject(globalObject, result); + try promise.reject(globalObject, result); }, true => { - promise.resolve(globalObject, result); + try promise.resolve(globalObject, result); }, } } @@ -1212,7 +1212,7 @@ pub const AsyncReaddirRecursiveTask = struct { this.result_list_count.store(0, .monotonic); } - pub fn runFromJSThread(this: *AsyncReaddirRecursiveTask) void { + pub fn runFromJSThread(this: *AsyncReaddirRecursiveTask) bun.JSTerminated!void { const globalObject = this.globalObject; const success = this.pending_err == null; var promise_value = this.promise.value(); @@ -1234,10 +1234,10 @@ pub const AsyncReaddirRecursiveTask = struct { this.deinit(); switch (success) { false => { - promise.reject(globalObject, result); + try promise.reject(globalObject, result); }, true => { - promise.resolve(globalObject, result); + try promise.resolve(globalObject, result); }, } } diff --git a/src/bun.js/node/node_fs_stat_watcher.zig b/src/bun.js/node/node_fs_stat_watcher.zig index 8ff99bde77..b75436801d 100644 --- a/src/bun.js/node/node_fs_stat_watcher.zig +++ b/src/bun.js/node/node_fs_stat_watcher.zig @@ -382,7 +382,7 @@ pub const StatWatcher = struct { return; } - const jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint) catch return; // TODO: properly propagate exception upwards + const jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint) catch |err| return this.globalThis.reportActiveExceptionAsUnhandled(err); this.last_jsvalue = .create(jsvalue, this.globalThis); this.scheduler.data.append(this); @@ -393,7 +393,7 @@ pub const StatWatcher = struct { return; } - const jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint) catch return; // TODO: properly propagate exception upwards + const jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint) catch |err| return this.globalThis.reportActiveExceptionAsUnhandled(err); this.last_jsvalue = .create(jsvalue, this.globalThis); _ = js.listenerGetCached(this.js_this).?.call( diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index f56a93c175..75f84afbb5 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -141,8 +141,8 @@ pub fn CompressionStream(comptime T: type) type { this_value.ensureStillAlive(); - if (!(checkError(this, global, this_value) catch return global.reportActiveExceptionAsUnhandled(error.JSError))) { - return; // TODO: properly propagate exception upwards + if (!(checkError(this, global, this_value))) { + return; } this.stream.updateWriteResult(&this.write_result.?[1], &this.write_result.?[0]); @@ -203,7 +203,7 @@ pub fn CompressionStream(comptime T: type) type { const this_value = callframe.this(); this.stream.doWork(); - if (try checkError(this, globalThis, this_value)) { + if (checkError(this, globalThis, this_value)) { this.stream.updateWriteResult(&this.write_result.?[1], &this.write_result.?[0]); this.write_in_progress = false; } @@ -212,10 +212,10 @@ pub fn CompressionStream(comptime T: type) type { return .js_undefined; } - pub fn reset(this: *T, globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue { + pub fn reset(this: *T, globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) jsc.JSValue { const err = this.stream.reset(); if (err.isError()) { - try emitError(this, globalThis, callframe.this(), err); + emitError(this, globalThis, callframe.this(), err); } return .js_undefined; } @@ -249,14 +249,14 @@ pub fn CompressionStream(comptime T: type) type { } /// returns true if no error was detected/emitted - fn checkError(this: *T, globalThis: *jsc.JSGlobalObject, this_value: jsc.JSValue) !bool { + fn checkError(this: *T, globalThis: *jsc.JSGlobalObject, this_value: jsc.JSValue) bool { const err = this.stream.getErrorInfo(); if (!err.isError()) return true; - try emitError(this, globalThis, this_value, err); + emitError(this, globalThis, this_value, err); return false; } - pub fn emitError(this: *T, globalThis: *jsc.JSGlobalObject, this_value: jsc.JSValue, err_: Error) !void { + pub fn emitError(this: *T, globalThis: *jsc.JSGlobalObject, this_value: jsc.JSValue, err_: Error) void { var msg_str = bun.handleOom(bun.String.createFormat("{s}", .{std.mem.sliceTo(err_.msg, 0) orelse ""})); const msg_value = msg_str.transferToJS(globalThis); const err_value: jsc.JSValue = .jsNumber(err_.err); diff --git a/src/bun.js/node/zlib/NativeBrotli.zig b/src/bun.js/node/zlib/NativeBrotli.zig index 3675caa92b..9bd1e60231 100644 --- a/src/bun.js/node/zlib/NativeBrotli.zig +++ b/src/bun.js/node/zlib/NativeBrotli.zig @@ -79,7 +79,7 @@ pub fn init(this: *@This(), globalThis: *jsc.JSGlobalObject, callframe: *jsc.Cal var err = this.stream.init(); if (err.isError()) { - try impl.emitError(this, globalThis, this_value, err); + impl.emitError(this, globalThis, this_value, err); return .false; } diff --git a/src/bun.js/node/zlib/NativeZlib.zig b/src/bun.js/node/zlib/NativeZlib.zig index 98dea8ffda..c907b0646c 100644 --- a/src/bun.js/node/zlib/NativeZlib.zig +++ b/src/bun.js/node/zlib/NativeZlib.zig @@ -100,7 +100,7 @@ pub fn params(this: *@This(), globalThis: *jsc.JSGlobalObject, callframe: *jsc.C const err = this.stream.setParams(level, strategy); if (err.isError()) { - try impl.emitError(this, globalThis, callframe.this(), err); + impl.emitError(this, globalThis, callframe.this(), err); } return .js_undefined; } diff --git a/src/bun.js/node/zlib/NativeZstd.zig b/src/bun.js/node/zlib/NativeZstd.zig index 5254ca51fc..1acbde76b7 100644 --- a/src/bun.js/node/zlib/NativeZstd.zig +++ b/src/bun.js/node/zlib/NativeZstd.zig @@ -84,7 +84,7 @@ pub fn init(this: *@This(), globalThis: *jsc.JSGlobalObject, callframe: *jsc.Cal var err = this.stream.init(pledged_src_size); if (err.isError()) { - try impl.emitError(this, globalThis, this_value, err); + impl.emitError(this, globalThis, this_value, err); return .false; } diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 8f53659842..7040aa7f4f 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -492,6 +492,10 @@ pub fn s3DefaultClient(rare: *RareData, globalThis: *jsc.JSGlobalObject) jsc.JSV globalThis.reportActiveExceptionAsUnhandled(err); return .js_undefined; }, + error.JSTerminated => { + globalThis.reportActiveExceptionAsUnhandled(err); + return .js_undefined; + }, }; defer aws_options.deinit(); const client = jsc.WebCore.S3Client.new(.{ diff --git a/src/bun.js/test/bun_test.zig b/src/bun.js/test/bun_test.zig index 2ce02522ba..838b92a50e 100644 --- a/src/bun.js/test/bun_test.zig +++ b/src/bun.js/test/bun_test.zig @@ -671,7 +671,7 @@ pub const BunTest = struct { .pending => { // not immediately resolved; register 'then' to handle the result when it becomes available const this_ref: *RefData = if (dcb_ref) |dcb_ref_value| dcb_ref_value.dupe() else ref(this_strong, cfg_data); - result.then(globalThis, this_ref, bunTestThen, bunTestCatch); + result.then(globalThis, this_ref, bunTestThen, bunTestCatch) catch {}; // TODO: properly propagate exception upwards return null; }, .fulfilled => { diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig index 6f98c9bc4a..e55395c57c 100644 --- a/src/bun.js/web_worker.zig +++ b/src/bun.js/web_worker.zig @@ -169,6 +169,10 @@ fn resolveEntryPointSpecifier( error_message.* = bun.String.static("unexpected exception"); return null; }, + error.JSTerminated => { + error_message.* = bun.String.static("unexpected exception"); + return null; + }, }; error_message.* = out; return null; @@ -382,8 +386,10 @@ fn flushLogs(this: *WebWorker) void { const str = err.toBunString(vm.global) catch |e| break :blk e; break :blk .{ err, str }; } catch |err| switch (err) { + // TODO: properly handle exception error.JSError => @panic("unhandled exception"), error.OutOfMemory => bun.outOfMemory(), + error.JSTerminated => @panic("unhandled exception"), }; defer str.deref(); bun.jsc.fromJSHostCallGeneric(vm.global, @src(), WebWorker__dispatchError, .{ vm.global, this.cpp_worker, str, err }) catch |e| { @@ -426,6 +432,7 @@ fn onUnhandledRejection(vm: *jsc.VirtualMachine, globalObject: *jsc.JSGlobalObje switch (err) { error.JSError => {}, error.OutOfMemory => globalObject.throwOutOfMemory() catch {}, + error.JSTerminated => {}, } error_instance = globalObject.tryTakeException().?; }; diff --git a/src/bun.js/webcore/BakeResponse.zig b/src/bun.js/webcore/BakeResponse.zig index b73b231d60..020769ac99 100644 --- a/src/bun.js/webcore/BakeResponse.zig +++ b/src/bun.js/webcore/BakeResponse.zig @@ -25,6 +25,7 @@ pub export fn BakeResponseClass__constructForSSR(globalObject: *jsc.JSGlobalObje globalObject.throwOutOfMemory() catch {}; return null; }, + error.JSTerminated => return null, }); } diff --git a/src/bun.js/webcore/Blob.zig b/src/bun.js/webcore/Blob.zig index 8cb28d1a38..d5d5ab337e 100644 --- a/src/bun.js/webcore/Blob.zig +++ b/src/bun.js/webcore/Blob.zig @@ -111,7 +111,7 @@ pub fn isBunFile(this: *const Blob) bool { return store.data == .file; } -pub fn doReadFromS3(this: *Blob, comptime Function: anytype, global: *JSGlobalObject) JSValue { +pub fn doReadFromS3(this: *Blob, comptime Function: anytype, global: *JSGlobalObject) bun.JSTerminated!JSValue { debug("doReadFromS3", .{}); const WrappedFn = struct { @@ -938,13 +938,13 @@ fn writeFileWithEmptySourceToDestination(ctx: *jsc.JSGlobalObject, destination_b pub const new = bun.TrivialNew(@This()); - pub fn resolve(result: S3.S3UploadResult, opaque_this: *anyopaque) void { + pub fn resolve(result: S3.S3UploadResult, opaque_this: *anyopaque) bun.JSTerminated!void { const this: *@This() = @ptrCast(@alignCast(opaque_this)); + defer this.deinit(); switch (result) { - .success => this.promise.resolve(this.global, .jsNumber(0)), - .failure => |err| this.promise.reject(this.global, err.toJS(this.global, this.store.getPath())), + .success => try this.promise.resolve(this.global, .jsNumber(0)), + .failure => |err| try this.promise.reject(this.global, err.toJS(this.global, this.store.getPath())), } - this.deinit(); } fn deinit(this: *@This()) void { @@ -959,7 +959,7 @@ fn writeFileWithEmptySourceToDestination(ctx: *jsc.JSGlobalObject, destination_b const proxy = ctx.bunVM().transpiler.env.getHttpProxy(true, null); const proxy_url = if (proxy) |p| p.href else null; destination_store.ref(); - S3.upload( + try S3.upload( &aws_options.credentials, s3.path(), "", @@ -1018,6 +1018,7 @@ pub fn writeFileWithSourceDestination(ctx: *jsc.JSGlobalObject, source_blob: *Bl options.mkdirp_if_not_exists orelse true, ) catch |e| switch (e) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => |ex| return ex, }; return promise_value; } @@ -1126,13 +1127,13 @@ pub fn writeFileWithSourceDestination(ctx: *jsc.JSGlobalObject, source_blob: *Bl pub const new = bun.TrivialNew(@This()); - pub fn resolve(result: S3.S3UploadResult, opaque_self: *anyopaque) void { + pub fn resolve(result: S3.S3UploadResult, opaque_self: *anyopaque) bun.JSTerminated!void { const this: *@This() = @ptrCast(@alignCast(opaque_self)); + defer this.deinit(); switch (result) { - .success => this.promise.resolve(this.global, .jsNumber(this.store.data.bytes.len)), - .failure => |err| this.promise.reject(this.global, err.toJS(this.global, this.store.getPath())), + .success => try this.promise.resolve(this.global, .jsNumber(this.store.data.bytes.len)), + .failure => |err| try this.promise.reject(this.global, err.toJS(this.global, this.store.getPath())), } - this.deinit(); } fn deinit(this: *@This()) void { @@ -1144,7 +1145,7 @@ pub fn writeFileWithSourceDestination(ctx: *jsc.JSGlobalObject, source_blob: *Bl const promise = jsc.JSPromise.Strong.init(ctx); const promise_value = promise.value(); - S3.upload( + try S3.upload( &aws_options.credentials, s3.path(), bytes.slice(), @@ -1465,17 +1466,12 @@ pub fn writeFileInternal(globalThis: *jsc.JSGlobalObject, path_or_blob_: *PathOr } } - break :brk Blob.get( + break :brk try Blob.get( globalThis, data, false, false, - ) catch |err| { - if (err == error.InvalidArguments) { - return globalThis.throwInvalidArguments("Expected an Array", .{}); - } - return globalThis.throwOutOfMemory(); - }; + ); }; defer source_blob.detach(); @@ -1720,6 +1716,7 @@ export fn JSDOMFile__construct(globalThis: *jsc.JSGlobalObject, callframe: *jsc. globalThis.throwOutOfMemory() catch {}; return null; }, + error.JSTerminated => null, }; } pub fn JSDOMFile__construct_(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Blob { @@ -1736,12 +1733,7 @@ pub fn JSDOMFile__construct_(globalThis: *jsc.JSGlobalObject, callframe: *jsc.Ca const name_value_str = try bun.String.fromJS(args[1], globalThis); defer name_value_str.deref(); - blob = get(globalThis, args[0], false, true) catch |err| switch (err) { - error.JSError, error.OutOfMemory => |e| return e, - error.InvalidArguments => { - return globalThis.throwInvalidArguments("new Blob() expects an Array", .{}); - }, - }; + blob = try get(globalThis, args[0], false, true); if (blob.store) |store_| { switch (store_.data) { .bytes => |*bytes| { @@ -2053,7 +2045,7 @@ pub fn getText( pub fn getTextClone( this: *Blob, globalObject: *jsc.JSGlobalObject, -) jsc.JSValue { +) bun.JSTerminated!jsc.JSValue { const store = this.store; if (store) |st| st.ref(); defer if (store) |st| st.deref(); @@ -2081,7 +2073,7 @@ pub fn getJSON( pub fn getJSONShare( this: *Blob, globalObject: *jsc.JSGlobalObject, -) jsc.JSValue { +) bun.JSTerminated!jsc.JSValue { const store = this.store; if (store) |st| st.ref(); defer if (store) |st| st.deref(); @@ -2101,7 +2093,7 @@ pub fn getArrayBufferTransfer( pub fn getArrayBufferClone( this: *Blob, globalThis: *jsc.JSGlobalObject, -) jsc.JSValue { +) bun.JSTerminated!jsc.JSValue { const store = this.store; if (store) |st| st.ref(); defer if (store) |st| st.deref(); @@ -2119,7 +2111,7 @@ pub fn getArrayBuffer( pub fn getBytesClone( this: *Blob, globalThis: *jsc.JSGlobalObject, -) JSValue { +) bun.JSTerminated!JSValue { const store = this.store; if (store) |st| st.ref(); defer if (store) |st| st.deref(); @@ -2197,7 +2189,7 @@ const S3BlobDownloadTask = struct { pub fn callHandler(this: *S3BlobDownloadTask, raw_bytes: []u8) JSValue { return this.handler(&this.blob, this.globalThis, raw_bytes); } - pub fn onS3DownloadResolved(result: S3.S3DownloadResult, this: *S3BlobDownloadTask) void { + pub fn onS3DownloadResolved(result: S3.S3DownloadResult, this: *S3BlobDownloadTask) bun.JSTerminated!void { defer this.deinit(); switch (result) { .success => |response| { @@ -2205,15 +2197,15 @@ const S3BlobDownloadTask = struct { if (this.blob.size == Blob.max_size) { this.blob.size = @truncate(bytes.len); } - jsc.AnyPromise.wrap(.{ .normal = this.promise.get() }, this.globalThis, S3BlobDownloadTask.callHandler, .{ this, bytes }); + try jsc.AnyPromise.wrap(.{ .normal = this.promise.get() }, this.globalThis, S3BlobDownloadTask.callHandler, .{ this, bytes }); }, inline .not_found, .failure => |err| { - this.promise.reject(this.globalThis, err.toJS(this.globalThis, this.blob.store.?.getPath())); + try this.promise.reject(this.globalThis, err.toJS(this.globalThis, this.blob.store.?.getPath())); }, } } - pub fn init(globalThis: *jsc.JSGlobalObject, blob: *Blob, handler: S3BlobDownloadTask.S3ReadHandler) JSValue { + pub fn init(globalThis: *jsc.JSGlobalObject, blob: *Blob, handler: S3BlobDownloadTask.S3ReadHandler) bun.JSTerminated!JSValue { blob.store.?.ref(); const this = S3BlobDownloadTask.new(.{ @@ -2231,13 +2223,13 @@ const S3BlobDownloadTask = struct { if (blob.offset > 0) { const len: ?usize = if (blob.size != Blob.max_size) @intCast(blob.size) else null; const offset: usize = @intCast(blob.offset); - S3.downloadSlice(credentials, path, offset, len, @ptrCast(&S3BlobDownloadTask.onS3DownloadResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); + try S3.downloadSlice(credentials, path, offset, len, @ptrCast(&S3BlobDownloadTask.onS3DownloadResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); } else if (blob.size == Blob.max_size) { - S3.download(credentials, path, @ptrCast(&S3BlobDownloadTask.onS3DownloadResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); + try S3.download(credentials, path, @ptrCast(&S3BlobDownloadTask.onS3DownloadResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); } else { const len: usize = @intCast(blob.size); const offset: usize = @intCast(blob.offset); - S3.downloadSlice(credentials, path, offset, len, @ptrCast(&S3BlobDownloadTask.onS3DownloadResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); + try S3.downloadSlice(credentials, path, offset, len, @ptrCast(&S3BlobDownloadTask.onS3DownloadResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); } return promise; } @@ -2356,7 +2348,7 @@ pub fn onFileStreamResolveRequestStream(globalThis: *jsc.JSGlobalObject, callfra if (strong.get(globalThis)) |stream| { stream.done(globalThis); } - this.promise.resolve(globalThis, jsc.JSValue.jsNumber(0)); + try this.promise.resolve(globalThis, jsc.JSValue.jsNumber(0)); return .js_undefined; } @@ -2370,7 +2362,7 @@ pub fn onFileStreamRejectRequestStream(globalThis: *jsc.JSGlobalObject, callfram defer strong.deinit(); this.readable_stream_ref = .{}; - this.promise.reject(globalThis, err); + try this.promise.reject(globalThis, err); if (strong.get(globalThis)) |stream| { stream.cancel(globalThis); @@ -2384,7 +2376,7 @@ comptime { @export(&jsonRejectRequestStream, .{ .name = "Bun__FileStreamWrapper__onRejectRequestStream" }); } -pub fn pipeReadableStreamToBlob(this: *Blob, globalThis: *jsc.JSGlobalObject, readable_stream: jsc.WebCore.ReadableStream, extra_options: ?JSValue) jsc.JSValue { +pub fn pipeReadableStreamToBlob(this: *Blob, globalThis: *jsc.JSGlobalObject, readable_stream: jsc.WebCore.ReadableStream, extra_options: ?JSValue) bun.JSError!jsc.JSValue { var store = this.store orelse { return jsc.JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(globalThis, globalThis.createErrorInstance("Blob is detached", .{})); }; @@ -2557,7 +2549,7 @@ pub fn pipeReadableStreamToBlob(this: *Blob, globalThis: *jsc.JSGlobalObject, re }); const promise_value = wrapper.promise.value(); - assignment_result.then( + try assignment_result.then( globalThis, wrapper, onFileStreamResolveRequestStream, @@ -3186,10 +3178,7 @@ pub fn constructor(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) b blob = Blob.init(empty, allocator, globalThis); }, else => { - blob = get(globalThis, args[0], false, true) catch |err| switch (err) { - error.OutOfMemory, error.JSError => |e| return e, - error.InvalidArguments => return globalThis.throwInvalidArguments("new Blob() expects an Array", .{}), - }; + blob = try get(globalThis, args[0], false, true); if (args.len > 1) { const options = args[1]; @@ -3727,7 +3716,7 @@ pub fn toArrayBufferView(this: *Blob, global: *JSGlobalObject, comptime lifetime return WithBytesFn(this, global, @constCast(view_), lifetime); } -pub fn toFormData(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { +pub fn toFormData(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) bun.JSTerminated!JSValue { if (this.needsToReadFile()) { return this.doReadFile(toFormDataWithBytes, global); } @@ -3743,26 +3732,24 @@ pub fn toFormData(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifet return toFormDataWithBytes(this, global, @constCast(view_), lifetime); } -const FromJsError = bun.JSError || error{InvalidArguments}; - pub inline fn get( global: *JSGlobalObject, arg: JSValue, comptime move: bool, comptime require_array: bool, -) FromJsError!Blob { +) bun.JSError!Blob { return fromJSMovable(global, arg, move, require_array); } -pub inline fn fromJSMove(global: *JSGlobalObject, arg: JSValue) FromJsError!Blob { +pub inline fn fromJSMove(global: *JSGlobalObject, arg: JSValue) bun.JSError!Blob { return fromJSWithoutDeferGC(global, arg, true, false); } -pub inline fn fromJSClone(global: *JSGlobalObject, arg: JSValue) FromJsError!Blob { +pub inline fn fromJSClone(global: *JSGlobalObject, arg: JSValue) bun.JSError!Blob { return fromJSWithoutDeferGC(global, arg, false, true); } -pub inline fn fromJSCloneOptionalArray(global: *JSGlobalObject, arg: JSValue) FromJsError!Blob { +pub inline fn fromJSCloneOptionalArray(global: *JSGlobalObject, arg: JSValue) bun.JSError!Blob { return fromJSWithoutDeferGC(global, arg, false, false); } @@ -3771,7 +3758,7 @@ fn fromJSMovable( arg: JSValue, comptime move: bool, comptime require_array: bool, -) FromJsError!Blob { +) bun.JSError!Blob { const FromJSFunction = if (comptime move and !require_array) fromJSMove else if (!require_array) @@ -3787,7 +3774,7 @@ fn fromJSWithoutDeferGC( arg: JSValue, comptime move: bool, comptime require_array: bool, -) FromJsError!Blob { +) bun.JSError!Blob { var current = arg; if (current.isUndefinedOrNull()) { return Blob{ .globalThis = global }; @@ -3887,7 +3874,7 @@ fn fromJSWithoutDeferGC( // new Blob("ok") // new File("ok", "file.txt") if (fail_if_top_value_is_not_typed_array_like) { - return error.InvalidArguments; + return global.throwInvalidArguments("new Blob() expects an Array", .{}); } } @@ -4148,12 +4135,12 @@ pub const Any = union(enum) { } } - pub fn toPromise(this: *Any, globalThis: *JSGlobalObject, action: streams.BufferAction.Tag) jsc.JSValue { + pub fn toPromise(this: *Any, globalThis: *JSGlobalObject, action: streams.BufferAction.Tag) bun.JSTerminated!jsc.JSValue { return jsc.JSPromise.wrap(globalThis, toActionValue, .{ this, globalThis, action }); } - pub fn wrap(this: *Any, promise: jsc.AnyPromise, globalThis: *JSGlobalObject, action: streams.BufferAction.Tag) void { - promise.wrap(globalThis, toActionValue, .{ this, globalThis, action }); + pub fn wrap(this: *Any, promise: jsc.AnyPromise, globalThis: *JSGlobalObject, action: streams.BufferAction.Tag) bun.JSTerminated!void { + return promise.wrap(globalThis, toActionValue, .{ this, globalThis, action }); } pub fn toJSON(this: *Any, global: *JSGlobalObject, comptime lifetime: jsc.WebCore.Lifetime) bun.JSError!JSValue { diff --git a/src/bun.js/webcore/Body.zig b/src/bun.js/webcore/Body.zig index 64cdfc2a05..b029738622 100644 --- a/src/bun.js/webcore/Body.zig +++ b/src/bun.js/webcore/Body.zig @@ -642,7 +642,7 @@ pub const Value = union(Tag) { new: *Value, global: *JSGlobalObject, headers: ?*FetchHeaders, - ) void { + ) bun.JSTerminated!void { log("resolve", .{}); if (to_resolve.* == .Locked) { var locked = &to_resolve.Locked; @@ -672,37 +672,37 @@ pub const Value = union(Tag) { // .InlineBlob, => { var blob = new.useAsAnyBlobAllowNonUTF8String(); - promise.wrap(global, AnyBlob.toStringTransfer, .{ &blob, global }); + try promise.wrap(global, AnyBlob.toStringTransfer, .{ &blob, global }); }, else => { var blob = new.use(); - promise.wrap(global, Blob.toStringTransfer, .{ &blob, global }); + try promise.wrap(global, Blob.toStringTransfer, .{ &blob, global }); }, } }, .getJSON => { var blob = new.useAsAnyBlobAllowNonUTF8String(); - promise.wrap(global, AnyBlob.toJSONShare, .{ &blob, global }); - blob.detach(); + defer blob.detach(); + try promise.wrap(global, AnyBlob.toJSONShare, .{ &blob, global }); }, .getArrayBuffer => { var blob = new.useAsAnyBlobAllowNonUTF8String(); - promise.wrap(global, AnyBlob.toArrayBufferTransfer, .{ &blob, global }); + try promise.wrap(global, AnyBlob.toArrayBufferTransfer, .{ &blob, global }); }, .getBytes => { var blob = new.useAsAnyBlobAllowNonUTF8String(); - promise.wrap(global, AnyBlob.toUint8ArrayTransfer, .{ &blob, global }); + try promise.wrap(global, AnyBlob.toUint8ArrayTransfer, .{ &blob, global }); }, .getFormData => inner: { var blob = new.useAsAnyBlob(); defer blob.detach(); var async_form_data: *bun.FormData.AsyncFormData = locked.action.getFormData orelse { - promise.reject(global, ZigString.init("Internal error: task for FormData must not be null").toErrorInstance(global)); + try promise.reject(global, ZigString.init("Internal error: task for FormData must not be null").toErrorInstance(global)); break :inner; }; defer async_form_data.deinit(); - async_form_data.toJS(global, blob.slice(), promise); + try async_form_data.toJS(global, blob.slice(), promise); }, .none, .getBlob => { var blob = Blob.new(new.use()); @@ -726,7 +726,7 @@ pub const Value = union(Tag) { blob.content_type_was_set = true; blob.store.?.mime_type = MimeType.text; } - promise.resolve(global, blob.toJS(global)); + try promise.resolve(global, blob.toJS(global)); }, } promise_.unprotect(); @@ -873,7 +873,7 @@ pub const Value = union(Tag) { return any_blob; } - pub fn toErrorInstance(this: *Value, err: ValueError, global: *JSGlobalObject) void { + pub fn toErrorInstance(this: *Value, err: ValueError, global: *JSGlobalObject) bun.JSTerminated!void { if (this.* == .Locked) { var locked = this.Locked; this.* = .{ .Error = err }; @@ -889,7 +889,7 @@ pub const Value = union(Tag) { if (promise_value.asAnyPromise()) |promise| { if (promise.status(global.vm()) == .pending) { - promise.reject(global, this.Error.toJS(global)); + try promise.reject(global, this.Error.toJS(global)); } } } @@ -898,7 +898,7 @@ pub const Value = union(Tag) { // Avoid creating unnecessary duplicate JSValue. if (strong_readable.get(global)) |readable| { if (readable.ptr == .Bytes) { - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .err = this.Error.toStreamError(global) }, bun.default_allocator, ); @@ -916,11 +916,8 @@ pub const Value = union(Tag) { this.* = .{ .Error = err }; } - pub fn toError(this: *Value, err: anyerror, global: *JSGlobalObject) void { - return this.toErrorInstance(.{ .Message = bun.String.createFormat( - "Error reading file {s}", - .{@errorName(err)}, - ) catch |e| bun.handleOom(e) }, global); + pub fn toError(this: *Value, err: anyerror, global: *JSGlobalObject) bun.JSTerminated!void { + return this.toErrorInstance(.{ .Message = bun.handleOom(bun.String.createFormat("Error reading file {s}", .{@errorName(err)})) }, global); } pub fn deinit(this: *Value) void { diff --git a/src/bun.js/webcore/ByteStream.zig b/src/bun.js/webcore/ByteStream.zig index 95a2017230..f90873fa7b 100644 --- a/src/bun.js/webcore/ByteStream.zig +++ b/src/bun.js/webcore/ByteStream.zig @@ -80,7 +80,7 @@ pub fn onData( this: *@This(), stream: streams.Result, allocator: std.mem.Allocator, -) void { +) bun.JSTerminated!void { jsc.markBinding(@src()); if (this.done) { if (stream.isDone() and (stream == .owned or stream == .owned_and_done)) { @@ -115,8 +115,7 @@ pub fn onData( log("ByteStream.onData err action.reject()", .{}); - action.reject(this.parent().globalThis, stream.err); - return; + return action.reject(this.parent().globalThis, stream.err); } if (this.has_received_last_chunk) { @@ -128,7 +127,7 @@ pub fn onData( log("ByteStream.onData done and action.fulfill()", .{}); var blob = this.toAnyBlob().?; - action.fulfill(this.parent().globalThis, &blob); + try action.fulfill(this.parent().globalThis, &blob); return; } if (this.buffer.capacity == 0 and stream == .owned_and_done) { @@ -136,7 +135,7 @@ pub fn onData( this.buffer = std.ArrayList(u8).fromOwnedSlice(bun.default_allocator, @constCast(chunk)); var blob = this.toAnyBlob().?; - action.fulfill(this.parent().globalThis, &blob); + try action.fulfill(this.parent().globalThis, &blob); return; } defer { @@ -148,8 +147,7 @@ pub fn onData( bun.handleOom(this.buffer.appendSlice(chunk)); var blob = this.toAnyBlob().?; - action.fulfill(this.parent().globalThis, &blob); - + try action.fulfill(this.parent().globalThis, &blob); return; } else { bun.handleOom(this.buffer.appendSlice(chunk)); @@ -355,7 +353,7 @@ pub fn onCancel(this: *@This()) void { if (this.buffer_action) |*action| { const global = this.parent().globalThis; - action.reject(global, .{ .AbortReason = .UserAbort }); + action.reject(global, .{ .AbortReason = .UserAbort }) catch {}; // TODO: properly propagate exception upwards this.buffer_action = null; } } diff --git a/src/bun.js/webcore/FileSink.zig b/src/bun.js/webcore/FileSink.zig index 200ef8c845..0d51f1c319 100644 --- a/src/bun.js/webcore/FileSink.zig +++ b/src/bun.js/webcore/FileSink.zig @@ -735,7 +735,7 @@ pub fn assignToStream(this: *FileSink, stream: *jsc.WebCore.ReadableStream, glob .pending => { this.writer.enableKeepingProcessAlive(this.event_loop_handle); this.ref(); - promise_result.then(globalThis, this, onResolveStream, onRejectStream); + promise_result.then(globalThis, this, onResolveStream, onRejectStream) catch {}; // TODO: properly propagate exception upwards }, .fulfilled => { // These don't ref(). diff --git a/src/bun.js/webcore/S3File.zig b/src/bun.js/webcore/S3File.zig index 945a5c9772..9e424ed65a 100644 --- a/src/bun.js/webcore/S3File.zig +++ b/src/bun.js/webcore/S3File.zig @@ -353,11 +353,11 @@ pub const S3BlobStatTask = struct { pub const new = bun.TrivialNew(S3BlobStatTask); - pub fn onS3ExistsResolved(result: S3.S3StatResult, this: *S3BlobStatTask) void { + pub fn onS3ExistsResolved(result: S3.S3StatResult, this: *S3BlobStatTask) bun.JSTerminated!void { defer this.deinit(); switch (result) { .not_found => { - this.promise.resolve(this.global, .false); + try this.promise.resolve(this.global, .false); }, .success => |_| { // calling .exists() should not prevent it to download a bigger file @@ -365,47 +365,47 @@ pub const S3BlobStatTask = struct { // if (this.blob.size == Blob.max_size) { // this.blob.size = @truncate(stat.size); // } - this.promise.resolve(this.global, .true); + try this.promise.resolve(this.global, .true); }, .failure => |err| { - this.promise.reject(this.global, err.toJS(this.global, this.store.data.s3.path())); + try this.promise.reject(this.global, err.toJS(this.global, this.store.data.s3.path())); }, } } - pub fn onS3SizeResolved(result: S3.S3StatResult, this: *S3BlobStatTask) void { + pub fn onS3SizeResolved(result: S3.S3StatResult, this: *S3BlobStatTask) bun.JSTerminated!void { defer this.deinit(); switch (result) { .success => |stat_result| { - this.promise.resolve(this.global, JSValue.jsNumber(stat_result.size)); + try this.promise.resolve(this.global, JSValue.jsNumber(stat_result.size)); }, .not_found, .failure => |err| { - this.promise.reject(this.global, err.toJS(this.global, this.store.data.s3.path())); + try this.promise.reject(this.global, err.toJS(this.global, this.store.data.s3.path())); }, } } - pub fn onS3StatResolved(result: S3.S3StatResult, this: *S3BlobStatTask) void { + pub fn onS3StatResolved(result: S3.S3StatResult, this: *S3BlobStatTask) bun.JSError!void { defer this.deinit(); const globalThis = this.global; switch (result) { .success => |stat_result| { - this.promise.resolve(globalThis, (S3Stat.init( + try this.promise.resolve(globalThis, (try S3Stat.init( stat_result.size, stat_result.etag, stat_result.contentType, stat_result.lastModified, globalThis, - ) catch return).toJS(globalThis)); // TODO: properly propagate exception upwards + )).toJS(globalThis)); }, .not_found, .failure => |err| { - this.promise.reject(globalThis, err.toJS(globalThis, this.store.data.s3.path())); + try this.promise.reject(globalThis, err.toJS(globalThis, this.store.data.s3.path())); }, } } - pub fn exists(globalThis: *jsc.JSGlobalObject, blob: *Blob) JSValue { + pub fn exists(globalThis: *jsc.JSGlobalObject, blob: *Blob) bun.JSTerminated!JSValue { const this = S3BlobStatTask.new(.{ .promise = jsc.JSPromise.Strong.init(globalThis), .store = blob.store.?, @@ -417,10 +417,10 @@ pub const S3BlobStatTask = struct { const path = blob.store.?.data.s3.path(); const env = globalThis.bunVM().transpiler.env; - S3.stat(credentials, path, @ptrCast(&S3BlobStatTask.onS3ExistsResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); + try S3.stat(credentials, path, @ptrCast(&S3BlobStatTask.onS3ExistsResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); return promise; } - pub fn stat(globalThis: *jsc.JSGlobalObject, blob: *Blob) JSValue { + pub fn stat(globalThis: *jsc.JSGlobalObject, blob: *Blob) bun.JSTerminated!JSValue { const this = S3BlobStatTask.new(.{ .promise = jsc.JSPromise.Strong.init(globalThis), .store = blob.store.?, @@ -432,10 +432,10 @@ pub const S3BlobStatTask = struct { const path = blob.store.?.data.s3.path(); const env = globalThis.bunVM().transpiler.env; - S3.stat(credentials, path, @ptrCast(&S3BlobStatTask.onS3StatResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); + try S3.stat(credentials, path, @ptrCast(&S3BlobStatTask.onS3StatResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); return promise; } - pub fn size(globalThis: *jsc.JSGlobalObject, blob: *Blob) JSValue { + pub fn size(globalThis: *jsc.JSGlobalObject, blob: *Blob) bun.JSTerminated!JSValue { const this = S3BlobStatTask.new(.{ .promise = jsc.JSPromise.Strong.init(globalThis), .store = blob.store.?, @@ -447,7 +447,7 @@ pub const S3BlobStatTask = struct { const path = blob.store.?.data.s3.path(); const env = globalThis.bunVM().transpiler.env; - S3.stat(credentials, path, @ptrCast(&S3BlobStatTask.onS3SizeResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); + try S3.stat(credentials, path, @ptrCast(&S3BlobStatTask.onS3SizeResolved), this, if (env.getHttpProxy(true, null)) |proxy| proxy.href else null); return promise; } @@ -537,7 +537,7 @@ pub fn getPresignUrl(this: *Blob, globalThis: *jsc.JSGlobalObject, callframe: *j } pub fn getStat(this: *Blob, globalThis: *jsc.JSGlobalObject, _: *jsc.CallFrame) callconv(jsc.conv) JSValue { - return S3BlobStatTask.stat(globalThis, this); + return S3BlobStatTask.stat(globalThis, this) catch .zero; } pub fn stat(globalThis: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue { @@ -605,18 +605,17 @@ pub fn constructInternal( return constructS3FileInternal(globalObject, path, args.nextEat()); } -pub fn construct( - globalObject: *jsc.JSGlobalObject, - callframe: *jsc.CallFrame, -) callconv(jsc.conv) ?*Blob { +pub fn construct(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) callconv(jsc.conv) ?*Blob { return constructInternal(globalObject, callframe) catch |err| switch (err) { error.JSError => null, error.OutOfMemory => { _ = globalObject.throwOutOfMemoryValue(); return null; }, + error.JSTerminated => null, }; } + pub fn hasInstance(_: jsc.JSValue, _: *jsc.JSGlobalObject, value: jsc.JSValue) callconv(jsc.conv) bool { jsc.markBinding(@src()); const blob = value.as(Blob) orelse return false; diff --git a/src/bun.js/webcore/blob/Store.zig b/src/bun.js/webcore/blob/Store.zig index c8347b3dd0..b00baf4c7b 100644 --- a/src/bun.js/webcore/blob/Store.zig +++ b/src/bun.js/webcore/blob/Store.zig @@ -333,16 +333,16 @@ pub const S3 = struct { pub const new = bun.TrivialNew(@This()); - pub fn resolve(result: bun.S3.S3DeleteResult, opaque_self: *anyopaque) void { + pub fn resolve(result: bun.S3.S3DeleteResult, opaque_self: *anyopaque) bun.JSTerminated!void { const self: *@This() = @ptrCast(@alignCast(opaque_self)); defer self.deinit(); const globalObject = self.global; switch (result) { .success => { - self.promise.resolve(globalObject, .true); + try self.promise.resolve(globalObject, .true); }, .not_found, .failure => |err| { - self.promise.reject(globalObject, err.toJS(globalObject, self.store.getPath())); + try self.promise.reject(globalObject, err.toJS(globalObject, self.store.getPath())); }, } } @@ -361,7 +361,7 @@ pub const S3 = struct { defer aws_options.deinit(); store.ref(); - bun.S3.delete(&aws_options.credentials, this.path(), @ptrCast(&Wrapper.resolve), Wrapper.new(.{ + try bun.S3.delete(&aws_options.credentials, this.path(), @ptrCast(&Wrapper.resolve), Wrapper.new(.{ .promise = promise, .store = store, // store is needed in case of not found error .global = globalThis, @@ -381,7 +381,7 @@ pub const S3 = struct { resolvedlistOptions: bun.S3.S3ListObjectsOptions, global: *JSGlobalObject, - pub fn resolve(result: bun.S3.S3ListObjectsResult, opaque_self: *anyopaque) void { + pub fn resolve(result: bun.S3.S3ListObjectsResult, opaque_self: *anyopaque) bun.JSTerminated!void { const self: *@This() = @ptrCast(@alignCast(opaque_self)); defer self.deinit(); const globalObject = self.global; @@ -390,11 +390,11 @@ pub const S3 = struct { .success => |list_result| { defer list_result.deinit(); const list_result_js = list_result.toJS(globalObject) catch return self.promise.reject(globalObject, error.JSError); - self.promise.resolve(globalObject, list_result_js); + try self.promise.resolve(globalObject, list_result_js); }, inline .not_found, .failure => |err| { - self.promise.reject(globalObject, err.toJS(globalObject, self.store.getPath())); + try self.promise.reject(globalObject, err.toJS(globalObject, self.store.getPath())); }, } } @@ -421,7 +421,7 @@ pub const S3 = struct { const options = try bun.S3.getListObjectsOptionsFromJS(globalThis, listOptions); store.ref(); - bun.S3.listObjects(&aws_options.credentials, options, @ptrCast(&Wrapper.resolve), bun.new(Wrapper, .{ + try bun.S3.listObjects(&aws_options.credentials, options, @ptrCast(&Wrapper.resolve), bun.new(Wrapper, .{ .promise = promise, .store = store, // store is needed in case of not found error .resolvedlistOptions = options, diff --git a/src/bun.js/webcore/blob/copy_file.zig b/src/bun.js/webcore/blob/copy_file.zig index 74bbc8a1c1..8b045280b9 100644 --- a/src/bun.js/webcore/blob/copy_file.zig +++ b/src/bun.js/webcore/blob/copy_file.zig @@ -61,7 +61,7 @@ pub const CopyFile = struct { bun.destroy(this); } - pub fn reject(this: *CopyFile, promise: *jsc.JSPromise) void { + pub fn reject(this: *CopyFile, promise: *jsc.JSPromise) bun.JSTerminated!void { const globalThis = this.globalThis; var system_error: SystemError = this.system_error orelse SystemError{ .message = .empty }; if (this.source_file_store.pathlike == .path and system_error.path.isEmpty()) { @@ -76,18 +76,18 @@ pub const CopyFile = struct { if (this.store) |store| { store.deref(); } - promise.reject(globalThis, instance); + try promise.reject(globalThis, instance); } - pub fn then(this: *CopyFile, promise: *jsc.JSPromise) void { + pub fn then(this: *CopyFile, promise: *jsc.JSPromise) bun.JSTerminated!void { this.source_store.?.deref(); if (this.system_error != null) { - this.reject(promise); + try this.reject(promise); return; } - promise.resolve(this.globalThis, jsc.JSValue.jsNumberFromUint64(this.read_len)); + try promise.resolve(this.globalThis, .jsNumberFromUint64(this.read_len)); } pub fn run(this: *CopyFile) void { @@ -1003,7 +1003,7 @@ pub const CopyFileWindows = struct { event_loop.enter(); defer event_loop.exit(); this.deinit(); - promise.reject(globalThis, err_instance); + promise.reject(globalThis, err_instance) catch {}; // TODO: properly propagate exception upwards } fn onCopyFile(req: *libuv.fs_t) callconv(.C) void { @@ -1057,7 +1057,7 @@ pub const CopyFileWindows = struct { defer event_loop.exit(); this.deinit(); - promise.resolve(globalThis, jsc.JSValue.jsNumberFromUint64(written)); + promise.resolve(globalThis, jsc.JSValue.jsNumberFromUint64(written)) catch {}; // TODO: properly propagate exception upwards } fn truncate(this: *CopyFileWindows) void { diff --git a/src/bun.js/webcore/blob/read_file.zig b/src/bun.js/webcore/blob/read_file.zig index 6dcacad14f..314cd64120 100644 --- a/src/bun.js/webcore/blob/read_file.zig +++ b/src/bun.js/webcore/blob/read_file.zig @@ -8,7 +8,7 @@ pub fn NewReadFileHandler(comptime Function: anytype) type { promise: JSPromise.Strong = .{}, globalThis: *JSGlobalObject, - pub fn run(handler: *@This(), maybe_bytes: ReadFileResultType) void { + pub fn run(handler: *@This(), maybe_bytes: ReadFileResultType) bun.JSTerminated!void { var promise = handler.promise.swap(); var blob = handler.context.takeOwnership(); const globalThis = handler.globalThis; @@ -24,10 +24,10 @@ pub fn NewReadFileHandler(comptime Function: anytype) type { } }; - jsc.AnyPromise.wrap(.{ .normal = promise }, globalThis, WrappedFn.wrapped, .{ &blob, globalThis, bytes }); + try jsc.AnyPromise.wrap(.{ .normal = promise }, globalThis, WrappedFn.wrapped, .{ &blob, globalThis, bytes }); }, .err => |err| { - promise.reject(globalThis, err.toErrorInstance(globalThis)); + try promise.reject(globalThis, err.toErrorInstance(globalThis)); }, } } @@ -106,14 +106,14 @@ pub const ReadFile = struct { max_len: SizeType, comptime Context: type, context: Context, - comptime callback: fn (ctx: Context, bytes: ReadFileResultType) void, + comptime callback: fn (ctx: Context, bytes: ReadFileResultType) bun.JSTerminated!void, ) !*ReadFile { if (Environment.isWindows) @compileError("dont call this function on windows"); const Handler = struct { pub fn run(ptr: *anyopaque, bytes: ReadFileResultType) void { - callback(bun.cast(Context, ptr), bytes); + callback(bun.cast(Context, ptr), bytes) catch {}; // TODO: properly propagate exception upwards } }; @@ -232,7 +232,7 @@ pub const ReadFile = struct { return true; } - pub fn then(this: *ReadFile, _: *jsc.JSGlobalObject) void { + pub fn then(this: *ReadFile, _: *jsc.JSGlobalObject) bun.JSTerminated!void { const cb = this.onCompleteCallback; const cb_ctx = this.onCompleteCtx; diff --git a/src/bun.js/webcore/blob/write_file.zig b/src/bun.js/webcore/blob/write_file.zig index 879dcebd02..6af5b5703b 100644 --- a/src/bun.js/webcore/blob/write_file.zig +++ b/src/bun.js/webcore/blob/write_file.zig @@ -1,5 +1,5 @@ pub const WriteFileResultType = SystemError.Maybe(SizeType); -pub const WriteFileOnWriteFileCallback = *const fn (ctx: *anyopaque, count: WriteFileResultType) void; +pub const WriteFileOnWriteFileCallback = *const fn (ctx: *anyopaque, count: WriteFileResultType) bun.JSTerminated!void; pub const WriteFileTask = jsc.WorkTask(WriteFile); pub const WriteFile = struct { @@ -96,12 +96,12 @@ pub const WriteFile = struct { bytes_blob: Blob, comptime Context: type, context: Context, - comptime callback: fn (ctx: Context, bytes: WriteFileResultType) void, + comptime callback: fn (ctx: Context, bytes: WriteFileResultType) bun.JSTerminated!void, mkdirp_if_not_exists: bool, ) !*WriteFile { const Handler = struct { - pub fn run(ptr: *anyopaque, bytes: WriteFileResultType) void { - callback(bun.cast(Context, ptr), bytes); + pub fn run(ptr: *anyopaque, bytes: WriteFileResultType) bun.JSTerminated!void { + try callback(bun.cast(Context, ptr), bytes); } }; @@ -161,7 +161,7 @@ pub const WriteFile = struct { return true; } - pub fn then(this: *WriteFile, _: *jsc.JSGlobalObject) void { + pub fn then(this: *WriteFile, _: *jsc.JSGlobalObject) bun.JSTerminated!void { const cb = this.onCompleteCallback; const cb_ctx = this.onCompleteCtx; @@ -170,15 +170,13 @@ pub const WriteFile = struct { if (this.system_error) |err| { bun.destroy(this); - cb(cb_ctx, .{ - .err = err, - }); + try cb(cb_ctx, .{ .err = err }); return; } const wrote = this.total_written; bun.destroy(this); - cb(cb_ctx, .{ .result = @as(SizeType, @truncate(wrote)) }); + try cb(cb_ctx, .{ .result = @as(SizeType, @truncate(wrote)) }); } pub fn run(this: *WriteFile, task: *WriteFileTask) void { @@ -356,7 +354,7 @@ pub const WriteFileWindows = struct { const log = bun.Output.scoped(.WriteFile, .hidden); - pub const WriteFileWindowsError = error{WriteFileWindowsDeinitialized}; + pub const WriteFileWindowsError = error{ WriteFileWindowsDeinitialized, JSTerminated }; pub fn createWithCtx( file_blob: Blob, @@ -467,6 +465,7 @@ pub const WriteFileWindows = struct { .syscall = .open, })) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => {}, // TODO: properly propagate exception upwards } return; } @@ -476,6 +475,7 @@ pub const WriteFileWindows = struct { // the loop must be copied this.doWriteLoop(this.loop()) catch |e| switch (e) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => {}, // TODO: properly propagate exception upwards }; } @@ -500,12 +500,14 @@ pub const WriteFileWindows = struct { defer bun.default_allocator.free(err_.path); switch (this.throw(err_)) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => {}, // TODO: properly propagate exception upwards } return; } this.open() catch |e| switch (e) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => {}, // TODO: properly propagate exception upwards }; } @@ -526,6 +528,7 @@ pub const WriteFileWindows = struct { .syscall = .write, })) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => {}, // TODO: properly propagate exception upwards } return; } @@ -533,6 +536,7 @@ pub const WriteFileWindows = struct { this.total_written += @intCast(rc.int()); this.doWriteLoop(this.loop()) catch |e| switch (e) { error.WriteFileWindowsDeinitialized => {}, + error.JSTerminated => {}, // TODO: properly propagate exception upwards }; } @@ -551,13 +555,11 @@ pub const WriteFileWindows = struct { if (this.toSystemError()) |err| { this.deinit(); - cb(cb_ctx, .{ - .err = err, - }); + try cb(cb_ctx, .{ .err = err }); } else { const wrote = this.total_written; this.deinit(); - cb(cb_ctx, .{ .result = @as(SizeType, @truncate(wrote)) }); + try cb(cb_ctx, .{ .result = @as(SizeType, @truncate(wrote)) }); } return error.WriteFileWindowsDeinitialized; @@ -631,7 +633,7 @@ pub const WriteFileWindows = struct { bytes_blob: Blob, comptime Context: type, context: Context, - comptime callback: *const fn (ctx: Context, bytes: WriteFileResultType) void, + comptime callback: *const fn (ctx: Context, bytes: WriteFileResultType) bun.JSTerminated!void, mkdirp_if_not_exists: bool, ) WriteFileWindowsError!*WriteFileWindows { return try WriteFileWindows.createWithCtx( @@ -648,7 +650,7 @@ pub const WriteFileWindows = struct { pub const WriteFilePromise = struct { promise: JSPromise.Strong = .{}, globalThis: *JSGlobalObject, - pub fn run(handler: *@This(), count: WriteFileResultType) void { + pub fn run(handler: *@This(), count: WriteFileResultType) bun.JSTerminated!void { var promise = handler.promise.swap(); const globalThis = handler.globalThis; bun.destroy(handler); @@ -656,10 +658,10 @@ pub const WriteFilePromise = struct { value.ensureStillAlive(); switch (count) { .err => |err| { - promise.reject(globalThis, err.toErrorInstance(globalThis)); + try promise.reject(globalThis, err.toErrorInstance(globalThis)); }, .result => |wrote| { - promise.resolve(globalThis, jsc.JSValue.jsNumberFromUint64(wrote)); + try promise.resolve(globalThis, .jsNumberFromUint64(wrote)); }, } } @@ -672,10 +674,10 @@ pub const WriteFileWaitFromLockedValueTask = struct { mkdirp_if_not_exists: bool = false, pub fn thenWrap(this: *anyopaque, value: *Body.Value) void { - then(bun.cast(*WriteFileWaitFromLockedValueTask, this), value); + then(bun.cast(*WriteFileWaitFromLockedValueTask, this), value) catch {}; // TODO: properly propagate exception upwards } - pub fn then(this: *WriteFileWaitFromLockedValueTask, value: *Body.Value) void { + pub fn then(this: *WriteFileWaitFromLockedValueTask, value: *Body.Value) bun.JSTerminated!void { var promise = this.promise.get(); var globalThis = this.globalThis; var file_blob = this.file_blob; @@ -685,14 +687,14 @@ pub const WriteFileWaitFromLockedValueTask = struct { _ = value.use(); this.promise.deinit(); bun.destroy(this); - promise.reject(globalThis, err_ref.toJS(globalThis)); + try promise.reject(globalThis, err_ref.toJS(globalThis)); }, .Used => { file_blob.detach(); _ = value.use(); this.promise.deinit(); bun.destroy(this); - promise.reject(globalThis, ZigString.init("Body was used after it was consumed").toErrorInstance(globalThis)); + try promise.reject(globalThis, ZigString.init("Body was used after it was consumed").toErrorInstance(globalThis)); }, .WTFStringImpl, .InternalBlob, @@ -706,22 +708,23 @@ pub const WriteFileWaitFromLockedValueTask = struct { file_blob.detach(); this.promise.deinit(); bun.destroy(this); - promise.reject(globalThis, err); + try promise.reject(globalThis, err); return; }; + + defer bun.destroy(this); + defer this.promise.deinit(); + defer file_blob.detach(); + if (new_promise.asAnyPromise()) |p| { switch (p.unwrap(globalThis.vm(), .mark_handled)) { // Fulfill the new promise using the pending promise - .pending => promise.resolve(globalThis, new_promise), + .pending => try promise.resolve(globalThis, new_promise), - .rejected => |err| promise.reject(globalThis, err), - .fulfilled => |result| promise.resolve(globalThis, result), + .rejected => |err| try promise.reject(globalThis, err), + .fulfilled => |result| try promise.resolve(globalThis, result), } } - - file_blob.detach(); - this.promise.deinit(); - bun.destroy(this); }, .Locked => { value.Locked.onReceiveValue = thenWrap; diff --git a/src/bun.js/webcore/fetch.zig b/src/bun.js/webcore/fetch.zig index 80be6e2ef4..c8d00adce4 100644 --- a/src/bun.js/webcore/fetch.zig +++ b/src/bun.js/webcore/fetch.zig @@ -130,7 +130,7 @@ pub const FetchTasklet = struct { bun.debugAssert(count > 0); if (count == 1) { - this.deinit(); + this.deinit() catch |err| switch (err) {}; } } @@ -306,7 +306,8 @@ pub const FetchTasklet = struct { this.clearSink(); } - pub fn deinit(this: *FetchTasklet) void { + // XXX: 'fn (*FetchTasklet) error{}!void' coerces to 'fn (*FetchTasklet) bun.JSError!void' but 'fn (*FetchTasklet) void' does not + pub fn deinit(this: *FetchTasklet) error{}!void { log("deinit", .{}); bun.assert(this.ref_count.load(.monotonic) == 0); @@ -360,7 +361,7 @@ pub const FetchTasklet = struct { } } - pub fn onBodyReceived(this: *FetchTasklet) void { + pub fn onBodyReceived(this: *FetchTasklet) bun.JSTerminated!void { const success = this.result.isSuccess(); const globalThis = this.global_this; // reset the buffer if we are streaming or if we are not waiting for bufferig anymore @@ -382,7 +383,7 @@ pub const FetchTasklet = struct { if (readable.ptr == .Bytes) { js_err = err.toJS(globalThis); js_err.ensureStillAlive(); - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .err = .{ .JSValue = js_err }, }, @@ -402,7 +403,7 @@ pub const FetchTasklet = struct { if (this.getCurrentResponse()) |response| { need_deinit = false; // body value now owns the error const body = response.getBodyValue(); - body.toErrorInstance(err, globalThis); + try body.toErrorInstance(err, globalThis); } return; } @@ -417,7 +418,7 @@ pub const FetchTasklet = struct { const chunk = scheduled_response_buffer.items; if (this.result.has_more) { - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .temporary = bun.ByteList.fromBorrowedSliceDangerous(chunk), }, @@ -429,7 +430,7 @@ pub const FetchTasklet = struct { defer prev.deinit(); buffer_reset = false; this.memory_reporter.discard(scheduled_response_buffer.allocatedSlice()); - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .owned_and_done = bun.ByteList.moveFromList(scheduled_response_buffer), }, @@ -452,7 +453,7 @@ pub const FetchTasklet = struct { const chunk = scheduled_response_buffer.items; if (this.result.has_more) { - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .temporary = bun.ByteList.fromBorrowedSliceDangerous(chunk), }, @@ -461,7 +462,7 @@ pub const FetchTasklet = struct { } else { readable.value.ensureStillAlive(); response.detachReadableStream(globalThis); - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .temporary_and_done = bun.ByteList.fromBorrowedSliceDangerous(chunk), }, @@ -499,13 +500,13 @@ pub const FetchTasklet = struct { if (old == .Locked) { log("onBodyReceived old.resolve", .{}); - old.resolve(body, this.global_this, response.getFetchHeaders()); + try old.resolve(body, this.global_this, response.getFetchHeaders()); } } } } - pub fn onProgressUpdate(this: *FetchTasklet) void { + pub fn onProgressUpdate(this: *FetchTasklet) bun.JSTerminated!void { jsc.markBinding(@src()); log("onProgressUpdate", .{}); this.mutex.lock(); @@ -539,7 +540,7 @@ pub const FetchTasklet = struct { } // if we already respond the metadata and still need to process the body if (this.is_waiting_body) { - this.onBodyReceived(); + try this.onBodyReceived(); return; } if (this.metadata == null and this.result.isSuccess()) return; @@ -572,7 +573,7 @@ pub const FetchTasklet = struct { defer result.deinit(); promise_value.ensureStillAlive(); - promise.reject(globalThis, result.toJS(globalThis)); + try promise.reject(globalThis, result.toJS(globalThis)); tracker.didDispatch(globalThis); this.promise.deinit(); @@ -613,7 +614,7 @@ pub const FetchTasklet = struct { globalObject: *jsc.JSGlobalObject, task: jsc.AnyTask, - pub fn resolve(self: *@This()) void { + pub fn resolve(self: *@This()) bun.JSTerminated!void { // cleanup defer bun.default_allocator.destroy(self); defer self.held.deinit(); @@ -622,10 +623,10 @@ pub const FetchTasklet = struct { var prom = self.promise.swap().asAnyPromise().?; const res = self.held.swap(); res.ensureStillAlive(); - prom.resolve(self.globalObject, res); + try prom.resolve(self.globalObject, res); } - pub fn reject(self: *@This()) void { + pub fn reject(self: *@This()) bun.JSTerminated!void { // cleanup defer bun.default_allocator.destroy(self); defer self.held.deinit(); @@ -635,7 +636,7 @@ pub const FetchTasklet = struct { var prom = self.promise.swap().asAnyPromise().?; const res = self.held.swap(); res.ensureStillAlive(); - prom.reject(self.globalObject, res); + try prom.reject(self.globalObject, res); } }; var holder = bun.handleOom(bun.default_allocator.create(Holder)); @@ -668,6 +669,7 @@ pub const FetchTasklet = struct { switch (err) { error.JSError => {}, error.OutOfMemory => globalObject.throwOutOfMemory() catch {}, + error.JSTerminated => {}, } const check_result = globalObject.tryTakeException().?; // mark to wait until deinit @@ -1182,7 +1184,8 @@ pub const FetchTasklet = struct { } /// This is ALWAYS called from the main thread - pub fn resumeRequestDataStream(this: *FetchTasklet) void { + // XXX: 'fn (*FetchTasklet) error{}!void' coerces to 'fn (*FetchTasklet) bun.JSError!void' but 'fn (*FetchTasklet) void' does not + pub fn resumeRequestDataStream(this: *FetchTasklet) error{}!void { // deref when done because we ref inside onWriteRequestDataDrain defer this.deref(); log("resumeRequestDataStream", .{}); @@ -2528,8 +2531,10 @@ pub fn Bun__fetch_( pub const new = bun.TrivialNew(@This()); - pub fn resolve(result: s3.S3UploadResult, self: *@This()) void { + pub fn resolve(result: s3.S3UploadResult, self: *@This()) bun.JSTerminated!void { const global = self.global; + defer bun.destroy(self); + defer bun.default_allocator.free(self.url_proxy_buffer); switch (result) { .success => { const response = bun.new(Response, Response.init( @@ -2545,7 +2550,7 @@ pub fn Bun__fetch_( )); const response_js = Response.makeMaybePooled(@as(*jsc.JSGlobalObject, global), response); response_js.ensureStillAlive(); - self.promise.resolve(global, response_js); + try self.promise.resolve(global, response_js); }, .failure => |err| { const response = bun.new(Response, Response.init( @@ -2568,11 +2573,9 @@ pub fn Bun__fetch_( const response_js = Response.makeMaybePooled(@as(*jsc.JSGlobalObject, global), response); response_js.ensureStillAlive(); - self.promise.resolve(global, response_js); + try self.promise.resolve(global, response_js); }, } - bun.default_allocator.free(self.url_proxy_buffer); - bun.destroy(self); } }; if (method != .PUT and method != .POST) { @@ -2589,7 +2592,7 @@ pub fn Bun__fetch_( const promise_value = promise.value(); const proxy_url = if (proxy) |p| p.href else ""; - _ = bun.S3.uploadStream( + _ = try bun.S3.uploadStream( credentialsWithOptions.credentials.dupe(), url.s3Path(), body.ReadableStream.get(globalThis).?, diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index ad1bab017c..2b3bd89bb3 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -373,21 +373,17 @@ pub const Result = union(Tag) { }; } - pub fn fulfillPromise( - result: Writable, - promise: *JSPromise, - globalThis: *JSGlobalObject, - ) void { + pub fn fulfillPromise(result: Writable, promise: *JSPromise, globalThis: *JSGlobalObject) void { defer promise.toJS().unprotect(); switch (result) { .err => |err| { - promise.reject(globalThis, err.toJS(globalThis)); + promise.reject(globalThis, err.toJS(globalThis)) catch {}; // TODO: properly propagate exception upwards }, .done => { - promise.resolve(globalThis, .false); + promise.resolve(globalThis, .false) catch {}; // TODO: properly propagate exception upwards }, else => { - promise.resolve(globalThis, result.toJS(globalThis)); + promise.resolve(globalThis, result.toJS(globalThis)) catch {}; // TODO: properly propagate exception upwards }, } } @@ -537,21 +533,21 @@ pub const Result = union(Tag) { break :brk js_err; }; result.* = .{ .temporary = .{} }; - promise.reject(globalThis, value); + promise.reject(globalThis, value) catch {}; // TODO: properly propagate exception upwards }, .done => { - promise.resolve(globalThis, .false); + promise.resolve(globalThis, .false) catch {}; // TODO: properly propagate exception upwards }, else => { const value = result.toJS(globalThis) catch |err| { result.* = .{ .temporary = .{} }; - promise.reject(globalThis, err); + promise.reject(globalThis, err) catch {}; // TODO: properly propagate exception upwards return; }; value.ensureStillAlive(); result.* = .{ .temporary = .{} }; - promise.resolve(globalThis, value); + promise.resolve(globalThis, value) catch {}; // TODO: properly propagate exception upwards }, } } @@ -826,7 +822,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { this.has_backpressure = false; if (this.aborted) { this.signal.close(null); - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards this.finalize(); return false; } @@ -841,7 +837,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { if (chunk.len == 0) { if (this.done) { this.signal.close(null); - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards this.finalize(); return true; } @@ -857,14 +853,14 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { res.clearOnWritable(); } this.signal.close(null); - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards this.finalize(); return true; } } // flush the javascript promise from calling .flush() - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards // pending_flush or callback could have caused another send() // so we check again if we should report readiness @@ -887,7 +883,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { this.wrote = 0; this.wrote_at_start_of_flush = 0; - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards if (this.buffer.cap == 0) { bun.assert(this.pooled_buffer == null); @@ -1194,7 +1190,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { } this.markDone(); - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards this.signal.close(null); this.finalize(); @@ -1215,7 +1211,7 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { this.signal.close(null); - this.flushPromise(); + this.flushPromise() catch {}; // TODO: properly propagate exception upwards this.finalize(); } @@ -1308,15 +1304,15 @@ pub fn HTTPServerWritable(comptime ssl: bool) type { } } - pub fn flushPromise(this: *@This()) void { + pub fn flushPromise(this: *@This()) bun.JSTerminated!void { if (this.pending_flush) |prom| { log("flushPromise()", .{}); this.pending_flush = null; const globalThis = this.globalThis; prom.toJS().unprotect(); - prom.resolve(globalThis, jsc.JSValue.jsNumber(this.wrote -| this.wrote_at_start_of_flush)); - this.wrote_at_start_of_flush = this.wrote; + defer this.wrote_at_start_of_flush = this.wrote; + try prom.resolve(globalThis, jsc.JSValue.jsNumber(this.wrote -| this.wrote_at_start_of_flush)); } } @@ -1394,10 +1390,10 @@ pub const NetworkSink = struct { } } - pub fn onWritable(task: *bun.S3.MultiPartUpload, this: *@This(), flushed: u64) void { + pub fn onWritable(task: *bun.S3.MultiPartUpload, this: *@This(), flushed: u64) bun.JSTerminated!void { log("onWritable flushed: {d} state: {s}", .{ flushed, @tagName(task.state) }); if (this.flushPromise.hasValue()) { - this.flushPromise.resolve(this.globalThis, jsc.JSValue.jsNumber(flushed)); + try this.flushPromise.resolve(this.globalThis, jsc.JSValue.jsNumber(flushed)); } } @@ -1548,16 +1544,16 @@ pub const BufferAction = union(enum) { pub const Tag = @typeInfo(BufferAction).@"union".tag_type.?; - pub fn fulfill(this: *BufferAction, global: *jsc.JSGlobalObject, blob: *AnyBlob) void { - blob.wrap(.{ .normal = this.swap() }, global, this.*); + pub fn fulfill(this: *BufferAction, global: *jsc.JSGlobalObject, blob: *AnyBlob) bun.JSTerminated!void { + return blob.wrap(.{ .normal = this.swap() }, global, this.*); } - pub fn reject(this: *BufferAction, global: *jsc.JSGlobalObject, err: Result.StreamError) void { - this.swap().reject(global, err.toJSWeak(global)[0]); + pub fn reject(this: *BufferAction, global: *jsc.JSGlobalObject, err: Result.StreamError) bun.JSTerminated!void { + return this.swap().reject(global, err.toJSWeak(global)[0]); } - pub fn resolve(this: *BufferAction, global: *jsc.JSGlobalObject, result: jsc.JSValue) void { - this.swap().resolve(global, result); + pub fn resolve(this: *BufferAction, global: *jsc.JSGlobalObject, result: jsc.JSValue) bun.JSTerminated!void { + return this.swap().resolve(global, result); } pub fn value(this: *BufferAction) jsc.JSValue { diff --git a/src/bun.zig b/src/bun.zig index 032e843178..acaa0ec66a 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -157,15 +157,17 @@ pub const JSError = error{ JSError, // XXX: This is temporary! meghan will remove this soon OutOfMemory, + /// Similar to error.JSError but always represents a termination exception. + JSTerminated, }; -pub const JSExecutionTerminated = error{ +pub const JSTerminated = error{ /// JavaScript execution has been terminated. /// This condition is indicated by throwing an exception, so most code should still handle it /// with JSError. If you expect that you will not throw any errors other than the termination /// exception, you can catch JSError, assert that the exception is the termination exception, - /// and return error.JSExecutionTerminated. - JSExecutionTerminated, + /// and return error.JSTerminated. + JSTerminated, }; pub const JSOOM = OOM || JSError; diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 29f2e80497..1d380fc9cb 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2071,7 +2071,7 @@ pub const BundleV2 = struct { return value != .js_undefined; } - fn toJSError(this: *JSBundleCompletionTask, promise: *jsc.JSPromise, globalThis: *jsc.JSGlobalObject) void { + fn toJSError(this: *JSBundleCompletionTask, promise: *jsc.JSPromise, globalThis: *jsc.JSGlobalObject) bun.JSTerminated!void { const throw_on_error = this.config.throw_on_error; const build_result = jsc.JSValue.createEmptyObject(globalThis, 3); @@ -2094,14 +2094,12 @@ pub const BundleV2 = struct { const aggregate_error = this.log.toJSAggregateError(globalThis, bun.String.static("Bundle failed")) catch |e| globalThis.takeException(e); break :blk runOnEndCallbacks(globalThis, plugin, promise, build_result, aggregate_error) catch |err| { const exception = globalThis.takeException(err); - promise.reject(globalThis, exception); - return; + return promise.reject(globalThis, exception); }; } else { break :blk runOnEndCallbacks(globalThis, plugin, promise, build_result, .js_undefined) catch |err| { const exception = globalThis.takeException(err); - promise.reject(globalThis, exception); - return; + return promise.reject(globalThis, exception); }; } } else false; @@ -2109,14 +2107,14 @@ pub const BundleV2 = struct { if (!didHandleCallbacks) { if (throw_on_error) { const aggregate_error = this.log.toJSAggregateError(globalThis, bun.String.static("Bundle failed")) catch |e| globalThis.takeException(e); - promise.reject(globalThis, aggregate_error); + return promise.reject(globalThis, aggregate_error); } else { - promise.resolve(globalThis, build_result); + return promise.resolve(globalThis, build_result); } } } - pub fn onComplete(this: *JSBundleCompletionTask) void { + pub fn onComplete(this: *JSBundleCompletionTask) bun.JSTerminated!void { var globalThis = this.globalThis; defer this.deref(); @@ -2148,7 +2146,7 @@ pub const BundleV2 = struct { switch (this.result) { .pending => unreachable, - .err => this.toJSError(promise, globalThis), + .err => try this.toJSError(promise, globalThis), .value => |*build| { const build_output = jsc.JSValue.createEmptyObject(globalThis, 3); const output_files = build.output_files.items; @@ -2198,7 +2196,9 @@ pub const BundleV2 = struct { to_assign_on_sourcemap = result; } - output_files_js.putIndex(globalThis, @as(u32, @intCast(i)), result) catch return; // TODO: properly propagate exception upwards + output_files_js.putIndex(globalThis, @as(u32, @intCast(i)), result) catch |err| { + return promise.reject(globalThis, err); + }; } build_output.put(globalThis, jsc.ZigString.static("outputs"), output_files_js); @@ -2213,12 +2213,11 @@ pub const BundleV2 = struct { const didHandleCallbacks = if (this.plugins) |plugin| runOnEndCallbacks(globalThis, plugin, promise, build_output, .js_undefined) catch |err| { const exception = globalThis.takeException(err); - promise.reject(globalThis, exception); - return; + return promise.reject(globalThis, exception); } else false; if (!didHandleCallbacks) { - promise.resolve(globalThis, build_output); + return promise.resolve(globalThis, build_output); } }, } diff --git a/src/codegen/bindgen.ts b/src/codegen/bindgen.ts index a33ad4a470..6e9fd9d408 100644 --- a/src/codegen/bindgen.ts +++ b/src/codegen/bindgen.ts @@ -1436,6 +1436,7 @@ for (const [filename, { functions, typedefs }] of files) { zigInternal.line(`)) catch |err| switch (err) {`); zigInternal.line(` error.JSError => return false,`); zigInternal.line(` error.OutOfMemory => ${globalObjectArg}.throwOutOfMemory() catch return false,`); + zigInternal.line(` error.JSTerminated => return false,`); zigInternal.line(`};`); zigInternal.line(`return true;`); break; diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 85a304578d..a5622f8e60 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -2187,6 +2187,7 @@ const JavaScriptCoreBindings = struct { globalObject.throwOutOfMemory() catch {}; return null; }, + error.JSTerminated => return null, }); } `; @@ -2200,6 +2201,7 @@ const JavaScriptCoreBindings = struct { globalObject.throwOutOfMemory() catch {}; return null; }, + error.JSTerminated => return null, }); } `; diff --git a/src/codegen/shared-types.ts b/src/codegen/shared-types.ts index 9611a3983d..e998b7f3e2 100644 --- a/src/codegen/shared-types.ts +++ b/src/codegen/shared-types.ts @@ -57,6 +57,9 @@ export const sharedTypes: Record = { "JSC::CallFrame": "bun.jsc.CallFrame", "JSC::JSObject": "bun.jsc.JSObject", "JSC::JSString": "bun.jsc.JSString", + "JSC::Exception": "bun.jsc.Exception", + "JSC::JSInternalPromise": "bun.jsc.JSInternalPromise", + "WebCore::EventLoopTask": "bun.jsc.CppTask", }; export const bannedTypes: Record = { diff --git a/src/deps/c_ares.zig b/src/deps/c_ares.zig index 19c982c3d9..7a20dd4ed4 100644 --- a/src/deps/c_ares.zig +++ b/src/deps/c_ares.zig @@ -508,7 +508,7 @@ pub const AddrInfo = extern struct { try array.putIndex( globalThis, j, - GetAddrInfo.Result.toJS( + try GetAddrInfo.Result.toJS( &.{ .address = switch (this_node.family) { AF.INET => std.net.Address{ .in = .{ .sa = bun.cast(*const std.posix.sockaddr.in, this_node.addr.?).* } }, @@ -1682,7 +1682,7 @@ pub const Error = enum(i32) { }); } - pub fn reject(this: *Deferred, globalThis: *jsc.JSGlobalObject) void { + pub fn reject(this: *Deferred, globalThis: *jsc.JSGlobalObject) bun.JSTerminated!void { const system_error = jsc.SystemError{ .errno = @intFromEnum(this.errno), .code = bun.String.static(this.errno.code()), @@ -1697,18 +1697,18 @@ pub const Error = enum(i32) { const instance = system_error.toErrorInstance(globalThis); instance.put(globalThis, "name", bun.String.static("DNSException").toJS(globalThis)); - this.promise.reject(globalThis, instance); - this.hostname = null; - this.deinit(); + defer this.deinit(); + defer this.hostname = null; + return this.promise.reject(globalThis, instance); } pub fn rejectLater(this: *Deferred, globalThis: *jsc.JSGlobalObject) void { const Context = struct { deferred: *Deferred, globalThis: *jsc.JSGlobalObject, - pub fn callback(context: *@This()) void { - context.deferred.reject(context.globalThis); - bun.default_allocator.destroy(context); + pub fn callback(context: *@This()) bun.JSTerminated!void { + defer bun.default_allocator.destroy(context); + try context.deferred.reject(context.globalThis); } }; diff --git a/src/deps/uws/socket.zig b/src/deps/uws/socket.zig index d4f81b8f77..5baee7f364 100644 --- a/src/deps/uws/socket.zig +++ b/src/deps/uws/socket.zig @@ -158,75 +158,110 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { ); } } - Fields.onOpen( + const res = Fields.onOpen( getValue(socket), TLSSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_close(socket: *us_socket_t, code: i32, reason: ?*anyopaque) callconv(.C) ?*us_socket_t { - Fields.onClose( + const res = Fields.onClose( getValue(socket), TLSSocket.from(socket), code, reason, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_data(socket: *us_socket_t, buf: ?[*]u8, len: i32) callconv(.C) ?*us_socket_t { - Fields.onData( + const res = Fields.onData( getValue(socket), TLSSocket.from(socket), buf.?[0..@as(usize, @intCast(len))], ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onWritable( + const res = Fields.onWritable( getValue(socket), TLSSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onTimeout( + const res = Fields.onTimeout( getValue(socket), TLSSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_long_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onLongTimeout( + const res = Fields.onLongTimeout( getValue(socket), TLSSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_connect_error(socket: *us_socket_t, code: i32) callconv(.C) ?*us_socket_t { - Fields.onConnectError( + const res = Fields.onConnectError( TLSSocket.from(socket).ext(ContextType).?.*, TLSSocket.from(socket), code, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { - Fields.onConnectError( + const res = Fields.onConnectError( @as(*align(alignment) ContextType, @ptrCast(@alignCast(socket.ext(comptime is_ssl)))).*, TLSSocket.fromConnecting(socket), code, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onEnd( + const res = Fields.onEnd( getValue(socket), TLSSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_handshake(socket: *us_socket_t, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { - Fields.onHandshake(getValue(socket), TLSSocket.from(socket), success, verify_error); + const res = Fields.onHandshake( + getValue(socket), + TLSSocket.from(socket), + success, + verify_error, + ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return, + }; } }; @@ -671,41 +706,56 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { ); } } - Fields.onOpen( + const res = Fields.onOpen( getValue(socket), SocketHandlerType.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_close(socket: *us_socket_t, code: i32, reason: ?*anyopaque) callconv(.C) ?*us_socket_t { - Fields.onClose( + const res = Fields.onClose( getValue(socket), SocketHandlerType.from(socket), code, reason, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_data(socket: *us_socket_t, buf: ?[*]u8, len: i32) callconv(.C) ?*us_socket_t { - Fields.onData( + const res = Fields.onData( getValue(socket), SocketHandlerType.from(socket), buf.?[0..@as(usize, @intCast(len))], ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onWritable( + const res = Fields.onWritable( getValue(socket), SocketHandlerType.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onTimeout( + const res = Fields.onTimeout( getValue(socket), SocketHandlerType.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { @@ -715,11 +765,14 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { SocketHandlerType.fromConnecting(socket).ext(ContextType).?.* else SocketHandlerType.fromConnecting(socket).ext(ContextType); - Fields.onConnectError( + const res = Fields.onConnectError( val, SocketHandlerType.fromConnecting(socket), code, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_connect_error(socket: *us_socket_t, code: i32) callconv(.C) ?*us_socket_t { @@ -729,22 +782,36 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { SocketHandlerType.from(socket).ext(ContextType).?.* else SocketHandlerType.from(socket).ext(ContextType); - Fields.onConnectError( + const res = Fields.onConnectError( val, SocketHandlerType.from(socket), code, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onEnd( + const res = Fields.onEnd( getValue(socket), SocketHandlerType.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_handshake(socket: *us_socket_t, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { - Fields.onHandshake(getValue(socket), SocketHandlerType.from(socket), success, verify_error); + const res = Fields.onHandshake( + getValue(socket), + SocketHandlerType.from(socket), + success, + verify_error, + ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return, + }; } }; @@ -805,56 +872,77 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { ); } } - Fields.onOpen( + const res = Fields.onOpen( getValue(socket), ThisSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_close(socket: *us_socket_t, code: i32, reason: ?*anyopaque) callconv(.C) ?*us_socket_t { - Fields.onClose( + const res = Fields.onClose( getValue(socket), ThisSocket.from(socket), code, reason, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_data(socket: *us_socket_t, buf: ?[*]u8, len: i32) callconv(.C) ?*us_socket_t { - Fields.onData( + const res = Fields.onData( getValue(socket), ThisSocket.from(socket), buf.?[0..@as(usize, @intCast(len))], ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_fd(socket: *us_socket_t, file_descriptor: c_int) callconv(.C) ?*us_socket_t { - Fields.onFd( + const res = Fields.onFd( getValue(socket), ThisSocket.from(socket), file_descriptor, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_writable(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onWritable( + const res = Fields.onWritable( getValue(socket), ThisSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onTimeout( + const res = Fields.onTimeout( getValue(socket), ThisSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_long_timeout(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onLongTimeout( + const res = Fields.onLongTimeout( getValue(socket), ThisSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_connect_error_connecting_socket(socket: *ConnectingSocket, code: i32) callconv(.C) ?*ConnectingSocket { @@ -864,11 +952,14 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { ThisSocket.fromConnecting(socket).ext(ContextType).?.* else ThisSocket.fromConnecting(socket).ext(ContextType); - Fields.onConnectError( + const res = Fields.onConnectError( val, ThisSocket.fromConnecting(socket), code, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_connect_error(socket: *us_socket_t, code: i32) callconv(.C) ?*us_socket_t { @@ -884,22 +975,36 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type { // So we need to close it like a TCP socket. NewSocketHandler(false).from(socket).close(.failure); - Fields.onConnectError( + const res = Fields.onConnectError( val, ThisSocket.from(socket), code, ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_end(socket: *us_socket_t) callconv(.C) ?*us_socket_t { - Fields.onEnd( + const res = Fields.onEnd( getValue(socket), ThisSocket.from(socket), ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return null, // TODO: declare throw scope + }; return socket; } pub fn on_handshake(socket: *us_socket_t, success: i32, verify_error: us_bun_verify_error_t, _: ?*anyopaque) callconv(.C) void { - Fields.onHandshake(getValue(socket), ThisSocket.from(socket), success, verify_error); + const res = Fields.onHandshake( + getValue(socket), + ThisSocket.from(socket), + success, + verify_error, + ); + if (@TypeOf(res) != void) res catch |err| switch (err) { + error.JSTerminated => return, + }; } }; diff --git a/src/dns.zig b/src/dns.zig index 0875024c4c..946b602c6b 100644 --- a/src/dns.zig +++ b/src/dns.zig @@ -331,7 +331,7 @@ pub const GetAddrInfo = struct { var i: u32 = 0; const items: []const Result = list.items; for (items) |item| { - try array.putIndex(globalThis, i, item.toJS(globalThis)); + try array.putIndex(globalThis, i, try item.toJS(globalThis)); i += 1; } break :brk array; @@ -373,12 +373,9 @@ pub const GetAddrInfo = struct { }; } - pub fn toJS(this: *const Result, globalThis: *jsc.JSGlobalObject) JSValue { + pub fn toJS(this: *const Result, globalThis: *jsc.JSGlobalObject) bun.JSError!JSValue { const obj = jsc.JSValue.createEmptyObject(globalThis, 3); - obj.put(globalThis, jsc.ZigString.static("address"), addressToJS(&this.address, globalThis) catch |err| return switch (err) { - error.JSError => .zero, - error.OutOfMemory => globalThis.throwOutOfMemoryValue(), - }); + obj.put(globalThis, jsc.ZigString.static("address"), try addressToJS(&this.address, globalThis)); obj.put(globalThis, jsc.ZigString.static("family"), switch (this.address.any.family) { std.posix.AF.INET => JSValue.jsNumber(4), std.posix.AF.INET6 => JSValue.jsNumber(6), @@ -449,7 +446,7 @@ pub fn addrInfoToJSArray(addr_info: *std.c.addrinfo, globalThis: *jsc.JSGlobalOb try array.putIndex( globalThis, j, - GetAddrInfo.Result.toJS( + try GetAddrInfo.Result.toJS( &(GetAddrInfo.Result.fromAddrInfo(this_node) orelse continue), globalThis, ), diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 779e0acaf3..708b453b1c 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -943,11 +943,11 @@ pub export fn napi_resolve_deferred(env_: napi_env, deferred: napi_deferred, res const env = env_ orelse { return envIsNull(); }; + defer bun.default_allocator.destroy(deferred); + defer deferred.deinit(); const resolution = resolution_.get(); var prom = deferred.get(); - prom.resolve(env.toJS(), resolution); - deferred.deinit(); - bun.default_allocator.destroy(deferred); + prom.resolve(env.toJS(), resolution) catch return env.setLastError(.pending_exception); return env.ok(); } pub export fn napi_reject_deferred(env_: napi_env, deferred: napi_deferred, rejection_: napi_value) napi_status { @@ -955,11 +955,11 @@ pub export fn napi_reject_deferred(env_: napi_env, deferred: napi_deferred, reje const env = env_ orelse { return envIsNull(); }; + defer bun.default_allocator.destroy(deferred); + defer deferred.deinit(); const rejection = rejection_.get(); var prom = deferred.get(); - prom.reject(env.toJS(), rejection); - deferred.deinit(); - bun.default_allocator.destroy(deferred); + prom.reject(env.toJS(), rejection) catch return env.setLastError(.pending_exception); return env.ok(); } pub export fn napi_is_promise(env_: napi_env, value_: napi_value, is_promise_: ?*bool) napi_status { @@ -1579,7 +1579,7 @@ pub const ThreadSafeFunction = struct { /// This function can be called multiple times in one tick of the event loop. /// See: https://github.com/nodejs/node/pull/38506 /// In that case, we need to drain microtasks. - fn call(this: *ThreadSafeFunction, task: ?*anyopaque, is_first: bool) bun.JSExecutionTerminated!void { + fn call(this: *ThreadSafeFunction, task: ?*anyopaque, is_first: bool) bun.JSTerminated!void { const env = this.env; if (!is_first) { try this.event_loop.drainMicrotasks(); diff --git a/src/s3/client.zig b/src/s3/client.zig index 9908aef366..3726d046eb 100644 --- a/src/s3/client.zig +++ b/src/s3/client.zig @@ -23,11 +23,11 @@ pub const getListObjectsOptionsFromJS = S3ListObjects.getListObjectsOptionsFromJ pub fn stat( this: *S3Credentials, path: []const u8, - callback: *const fn (S3StatResult, *anyopaque) void, + callback: *const fn (S3StatResult, *anyopaque) bun.JSTerminated!void, callback_context: *anyopaque, proxy_url: ?[]const u8, -) void { - S3SimpleRequest.executeSimpleS3Request(this, .{ +) bun.JSTerminated!void { + try S3SimpleRequest.executeSimpleS3Request(this, .{ .path = path, .method = .HEAD, .proxy_url = proxy_url, @@ -38,11 +38,11 @@ pub fn stat( pub fn download( this: *S3Credentials, path: []const u8, - callback: *const fn (S3DownloadResult, *anyopaque) void, + callback: *const fn (S3DownloadResult, *anyopaque) bun.JSTerminated!void, callback_context: *anyopaque, proxy_url: ?[]const u8, -) void { - S3SimpleRequest.executeSimpleS3Request(this, .{ +) bun.JSTerminated!void { + try S3SimpleRequest.executeSimpleS3Request(this, .{ .path = path, .method = .GET, .proxy_url = proxy_url, @@ -55,10 +55,10 @@ pub fn downloadSlice( path: []const u8, offset: usize, size: ?usize, - callback: *const fn (S3DownloadResult, *anyopaque) void, + callback: *const fn (S3DownloadResult, *anyopaque) bun.JSTerminated!void, callback_context: *anyopaque, proxy_url: ?[]const u8, -) void { +) bun.JSTerminated!void { const range = brk: { if (size) |size_| { var end = (offset + size_); @@ -71,7 +71,7 @@ pub fn downloadSlice( break :brk bun.handleOom(std.fmt.allocPrint(bun.default_allocator, "bytes={}-", .{offset})); }; - S3SimpleRequest.executeSimpleS3Request(this, .{ + try S3SimpleRequest.executeSimpleS3Request(this, .{ .path = path, .method = .GET, .proxy_url = proxy_url, @@ -83,11 +83,11 @@ pub fn downloadSlice( pub fn delete( this: *S3Credentials, path: []const u8, - callback: *const fn (S3DeleteResult, *anyopaque) void, + callback: *const fn (S3DeleteResult, *anyopaque) bun.JSTerminated!void, callback_context: *anyopaque, proxy_url: ?[]const u8, -) void { - S3SimpleRequest.executeSimpleS3Request(this, .{ +) bun.JSTerminated!void { + try S3SimpleRequest.executeSimpleS3Request(this, .{ .path = path, .method = .DELETE, .proxy_url = proxy_url, @@ -98,10 +98,10 @@ pub fn delete( pub fn listObjects( this: *S3Credentials, listOptions: S3ListObjectsOptions, - callback: *const fn (S3ListObjectsResult, *anyopaque) void, + callback: *const fn (S3ListObjectsResult, *anyopaque) bun.JSTerminated!void, callback_context: *anyopaque, proxy_url: ?[]const u8, -) void { +) bun.JSTerminated!void { var search_params: bun.ByteList = .{}; bun.handleOom(search_params.appendSlice(bun.default_allocator, "?")); @@ -173,7 +173,7 @@ pub fn listObjects( search_params.deinit(bun.default_allocator); const error_code_and_message = Error.getSignErrorCodeAndMessage(sign_err); - callback(.{ .failure = .{ .code = error_code_and_message.code, .message = error_code_and_message.message } }, callback_context); + try callback(.{ .failure = .{ .code = error_code_and_message.code, .message = error_code_and_message.message } }, callback_context); return; }; @@ -232,10 +232,10 @@ pub fn upload( acl: ?ACL, proxy_url: ?[]const u8, storage_class: ?StorageClass, - callback: *const fn (S3UploadResult, *anyopaque) void, + callback: *const fn (S3UploadResult, *anyopaque) bun.JSTerminated!void, callback_context: *anyopaque, -) void { - S3SimpleRequest.executeSimpleS3Request(this, .{ +) bun.JSTerminated!void { + try S3SimpleRequest.executeSimpleS3Request(this, .{ .path = path, .method = .PUT, .proxy_url = proxy_url, @@ -256,7 +256,7 @@ pub fn writableStream( storage_class: ?StorageClass, ) bun.JSError!jsc.JSValue { const Wrapper = struct { - pub fn callback(result: S3UploadResult, sink: *jsc.WebCore.NetworkSink) void { + pub fn callback(result: S3UploadResult, sink: *jsc.WebCore.NetworkSink) bun.JSTerminated!void { if (sink.endPromise.hasValue() or sink.flushPromise.hasValue()) { const event_loop = sink.globalThis.bunVM().eventLoop(); event_loop.enter(); @@ -264,19 +264,19 @@ pub fn writableStream( switch (result) { .success => { if (sink.flushPromise.hasValue()) { - sink.flushPromise.resolve(sink.globalThis, .jsNumber(0)); + try sink.flushPromise.resolve(sink.globalThis, .jsNumber(0)); } if (sink.endPromise.hasValue()) { - sink.endPromise.resolve(sink.globalThis, .jsNumber(0)); + try sink.endPromise.resolve(sink.globalThis, .jsNumber(0)); } }, .failure => |err| { const js_err = err.toJS(sink.globalThis, sink.path()); if (sink.flushPromise.hasValue()) { - sink.flushPromise.reject(sink.globalThis, js_err); + try sink.flushPromise.reject(sink.globalThis, js_err); } if (sink.endPromise.hasValue()) { - sink.endPromise.reject(sink.globalThis, js_err); + try sink.endPromise.reject(sink.globalThis, js_err); } if (!sink.done) { sink.abort(); @@ -373,27 +373,27 @@ pub const S3UploadStreamWrapper = struct { // if we have a explicit error, reject the promise // if not when calling .fail will create a S3Error instance // this match the previous behavior - this.endPromise.reject(this.global, js_err); + this.endPromise.reject(this.global, js_err) catch {}; // TODO: properly propagate exception upwards this.endPromise = .empty; } if (!this.task.ended) { this.task.fail(.{ .code = "UnknownError", .message = "ReadableStream ended with an error", - }); + }) catch {}; // TODO: properly propagate exception upwards } } else { _ = bun.handleOom(this.task.writeBytes("", true)); } } - pub fn resolve(result: S3UploadResult, self: *@This()) void { + pub fn resolve(result: S3UploadResult, self: *@This()) bun.JSTerminated!void { log("resolve {any}", .{result}); defer self.deref(); switch (result) { .success => { if (self.endPromise.hasValue()) { - self.endPromise.resolve(self.global, .jsNumber(0)); + try self.endPromise.resolve(self.global, .jsNumber(0)); self.endPromise = .empty; } }, @@ -404,7 +404,7 @@ pub const S3UploadStreamWrapper = struct { sink.cancel(err.toJS(self.global, self.path)); sink.deref(); } else if (self.endPromise.hasValue()) { - self.endPromise.reject(self.global, err.toJS(self.global, self.path)); + try self.endPromise.reject(self.global, err.toJS(self.global, self.path)); self.endPromise = .empty; } }, @@ -437,7 +437,7 @@ pub fn uploadStream( proxy: ?[]const u8, callback: ?*const fn (S3UploadResult, *anyopaque) void, callback_context: *anyopaque, -) jsc.JSValue { +) bun.JSError!jsc.JSValue { this.ref(); // ref the credentials const proxy_url = (proxy orelse ""); if (readable_stream.isDisturbed(globalThis)) { @@ -617,27 +617,27 @@ pub fn readableStream( path: []const u8, global: *jsc.JSGlobalObject, - pub fn callback(chunk: bun.MutableString, has_more: bool, request_err: ?Error.S3Error, self: *@This()) void { + pub fn callback(chunk: bun.MutableString, has_more: bool, request_err: ?Error.S3Error, self: *@This()) bun.JSTerminated!void { defer if (!has_more) self.deinit(); if (self.readable_stream_ref.get(self.global)) |readable| { if (readable.ptr == .Bytes) { if (request_err) |err| { - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .err = .{ .JSValue = err.toJS(self.global, self.path) } }, bun.default_allocator, ); return; } if (has_more) { - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .temporary = bun.ByteList.fromBorrowedSliceDangerous(chunk.list.items) }, bun.default_allocator, ); return; } - readable.ptr.Bytes.onData( + try readable.ptr.Bytes.onData( .{ .temporary_and_done = bun.ByteList.fromBorrowedSliceDangerous(chunk.list.items) }, bun.default_allocator, ); @@ -654,7 +654,7 @@ pub fn readableStream( pub fn opaqueCallback(chunk: bun.MutableString, has_more: bool, err: ?Error.S3Error, opaque_self: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(opaque_self)); - callback(chunk, has_more, err, self); + callback(chunk, has_more, err, self) catch {}; // TODO: properly propagate exception upwards } }; diff --git a/src/s3/multipart.zig b/src/s3/multipart.zig index 66b012a195..90afe0c7e5 100644 --- a/src/s3/multipart.zig +++ b/src/s3/multipart.zig @@ -130,7 +130,7 @@ pub const MultiPartUpload = struct { finished, } = .not_started, - callback: *const fn (S3SimpleRequest.S3UploadResult, *anyopaque) void, + callback: *const fn (S3SimpleRequest.S3UploadResult, *anyopaque) bun.JSTerminated!void, onWritable: ?*const fn (task: *MultiPartUpload, ctx: *anyopaque, flushed: u64) void = null, callback_context: *anyopaque, @@ -181,7 +181,7 @@ pub const MultiPartUpload = struct { return ""; } - pub fn onPartResponse(result: S3SimpleRequest.S3PartResult, this: *@This()) void { + pub fn onPartResponse(result: S3SimpleRequest.S3PartResult, this: *@This()) bun.JSTerminated!void { if (this.state == .canceled or this.ctx.state == .finished) { log("onPartResponse {} canceled", .{this.partNumber}); this.freeAllocatedSlice(); @@ -197,7 +197,7 @@ pub const MultiPartUpload = struct { log("onPartResponse {} retry", .{this.partNumber}); this.retry -= 1; // retry failed - this.perform(); + try this.perform(); return; } else { this.state = .not_assigned; @@ -221,18 +221,18 @@ pub const MultiPartUpload = struct { // mark as available this.ctx.available.set(this.index); // drain more - this.ctx.drainEnqueuedParts(sent); + try this.ctx.drainEnqueuedParts(sent); }, } } - fn perform(this: *@This()) void { + fn perform(this: *@This()) bun.JSTerminated!void { var params_buffer: [2048]u8 = undefined; const search_params = std.fmt.bufPrint(¶ms_buffer, "?partNumber={}&uploadId={s}&x-id=UploadPart", .{ this.partNumber, this.ctx.upload_id, }) catch unreachable; - executeSimpleS3Request(this.ctx.credentials, .{ + try executeSimpleS3Request(this.ctx.credentials, .{ .path = this.ctx.path, .method = .PUT, .proxy_url = this.ctx.proxyUrl(), @@ -240,11 +240,11 @@ pub const MultiPartUpload = struct { .search_params = search_params, }, .{ .part = @ptrCast(&onPartResponse) }, this); } - pub fn start(this: *@This()) void { + pub fn start(this: *@This()) bun.JSTerminated!void { if (this.state != .pending or this.ctx.state != .multipart_completed) return; this.ctx.ref(); this.state = .started; - this.perform(); + try this.perform(); } pub fn cancel(this: *@This()) void { const state = this.state; @@ -288,14 +288,14 @@ pub const MultiPartUpload = struct { bun.destroy(this); } - pub fn singleSendUploadResponse(result: S3SimpleRequest.S3UploadResult, this: *@This()) void { + pub fn singleSendUploadResponse(result: S3SimpleRequest.S3UploadResult, this: *@This()) bun.JSError!void { if (this.state == .finished) return; switch (result) { .failure => |err| { if (this.options.retry > 0) { log("singleSendUploadResponse {} retry", .{this.options.retry}); this.options.retry -= 1; - executeSimpleS3Request(this.credentials, .{ + try executeSimpleS3Request(this.credentials, .{ .path = this.path, .method = .PUT, .proxy_url = this.proxyUrl(), @@ -317,7 +317,7 @@ pub const MultiPartUpload = struct { if (this.onWritable) |callback| { callback(this, this.callback_context, this.buffered.size()); } - this.done(); + try this.done(); }, } } @@ -368,7 +368,7 @@ pub const MultiPartUpload = struct { } /// Drain the parts, this is responsible for starting the parts and processing the buffered data - fn drainEnqueuedParts(this: *@This(), flushed: u64) void { + fn drainEnqueuedParts(this: *@This(), flushed: u64) bun.JSTerminated!void { if (this.state == .finished or this.state == .singlefile_started) { return; } @@ -378,14 +378,14 @@ pub const MultiPartUpload = struct { for (queue) |*part| { if (part.state == .pending) { // lets start the part request - part.start(); + try part.start(); } } } } const partSize = this.partSizeInBytes(); if (this.ended or this.buffered.size() >= partSize) { - this.processMultiPart(partSize); + try this.processMultiPart(partSize); } // empty queue @@ -395,7 +395,7 @@ pub const MultiPartUpload = struct { } if (this.ended) { // we are done and no more parts are running - this.done(); + try this.done(); } } else if (!this.hasBackpressure() and flushed > 0) { // we have more space in the queue, we can drain more @@ -405,7 +405,7 @@ pub const MultiPartUpload = struct { } } /// Finalize the upload with a failure - pub fn fail(this: *@This(), _err: S3Error) void { + pub fn fail(this: *@This(), _err: S3Error) bun.JSTerminated!void { log("fail {s}:{s}", .{ _err.code, _err.message }); this.ended = true; if (this.queue) |queue| { @@ -418,12 +418,12 @@ pub const MultiPartUpload = struct { if (this.state != .finished) { const old_state = this.state; this.state = .finished; - this.callback(.{ .failure = _err }, this.callback_context); + try this.callback(.{ .failure = _err }, this.callback_context); if (old_state == .multipart_completed) { // we are a multipart upload so we need to rollback // will deref after rollback - this.rollbackMultiPartRequest(); + try this.rollbackMultiPartRequest(); } else { // single file upload no need to rollback this.deref(); @@ -431,7 +431,7 @@ pub const MultiPartUpload = struct { } } /// Finalize successful the upload - fn done(this: *@This()) void { + fn done(this: *@This()) bun.JSTerminated!void { if (this.state == .multipart_completed) { // we are a multipart upload so we need to send the etags and commit this.state = .finished; @@ -454,23 +454,23 @@ pub const MultiPartUpload = struct { "", )); // will deref and ends after commit - this.commitMultiPartRequest(); + try this.commitMultiPartRequest(); } else if (this.state == .singlefile_started) { this.state = .finished; // single file upload no need to commit - this.callback(.{ .success = {} }, this.callback_context); - this.deref(); + defer this.deref(); + try this.callback(.{ .success = {} }, this.callback_context); } } /// Result of the Multipart request, after this we can start draining the parts - pub fn startMultiPartRequestResult(result: S3SimpleRequest.S3DownloadResult, this: *@This()) void { + pub fn startMultiPartRequestResult(result: S3SimpleRequest.S3DownloadResult, this: *@This()) bun.JSError!void { defer this.deref(); if (this.state == .finished) return; switch (result) { .failure => |err| { log("startMultiPartRequestResult {s} failed {s}: {s}", .{ this.path, err.message, err.message }); - this.fail(err); + try this.fail(err); }, .success => |response| { const slice = response.body.list.items; @@ -484,7 +484,7 @@ pub const MultiPartUpload = struct { if (this.upload_id.len == 0) { // Unknown type of response error from AWS log("startMultiPartRequestResult {s} failed invalid id", .{this.path}); - this.fail(.{ + try this.fail(.{ .code = "UnknownError", .message = "Failed to initiate multipart upload", }); @@ -493,10 +493,10 @@ pub const MultiPartUpload = struct { log("startMultiPartRequestResult {s} success id: {s}", .{ this.path, this.upload_id }); this.state = .multipart_completed; // start draining the parts - this.drainEnqueuedParts(0); + try this.drainEnqueuedParts(0); }, // this is "unreachable" but we cover in case AWS returns 404 - .not_found => this.fail(.{ + .not_found => try this.fail(.{ .code = "UnknownError", .message = "Failed to initiate multipart upload", }), @@ -504,7 +504,7 @@ pub const MultiPartUpload = struct { } /// We do a best effort to commit the multipart upload, if it fails we will retry, if it still fails we will fail the upload - pub fn onCommitMultiPartRequest(result: S3SimpleRequest.S3CommitResult, this: *@This()) void { + pub fn onCommitMultiPartRequest(result: S3SimpleRequest.S3CommitResult, this: *@This()) bun.JSTerminated!void { log("onCommitMultiPartRequest {s}", .{this.upload_id}); switch (result) { @@ -512,29 +512,29 @@ pub const MultiPartUpload = struct { if (this.options.retry > 0) { this.options.retry -= 1; // retry commit - this.commitMultiPartRequest(); + try this.commitMultiPartRequest(); return; } + defer this.deref(); this.state = .finished; - this.callback(.{ .failure = err }, this.callback_context); - this.deref(); + try this.callback(.{ .failure = err }, this.callback_context); }, .success => { + defer this.deref(); this.state = .finished; - this.callback(.{ .success = {} }, this.callback_context); - this.deref(); + try this.callback(.{ .success = {} }, this.callback_context); }, } } /// We do a best effort to rollback the multipart upload, if it fails we will retry, if it still we just deinit the upload - pub fn onRollbackMultiPartRequest(result: S3SimpleRequest.S3UploadResult, this: *@This()) void { + pub fn onRollbackMultiPartRequest(result: S3SimpleRequest.S3UploadResult, this: *@This()) bun.JSTerminated!void { log("onRollbackMultiPartRequest {s}", .{this.upload_id}); switch (result) { .failure => { if (this.options.retry > 0) { this.options.retry -= 1; // retry rollback - this.rollbackMultiPartRequest(); + try this.rollbackMultiPartRequest(); return; } this.deref(); @@ -545,14 +545,14 @@ pub const MultiPartUpload = struct { } } - fn commitMultiPartRequest(this: *@This()) void { + fn commitMultiPartRequest(this: *@This()) bun.JSTerminated!void { log("commitMultiPartRequest {s}", .{this.upload_id}); var params_buffer: [2048]u8 = undefined; const searchParams = std.fmt.bufPrint(¶ms_buffer, "?uploadId={s}", .{ this.upload_id, }) catch unreachable; - executeSimpleS3Request(this.credentials, .{ + try executeSimpleS3Request(this.credentials, .{ .path = this.path, .method = .POST, .proxy_url = this.proxyUrl(), @@ -560,14 +560,14 @@ pub const MultiPartUpload = struct { .search_params = searchParams, }, .{ .commit = @ptrCast(&onCommitMultiPartRequest) }, this); } - fn rollbackMultiPartRequest(this: *@This()) void { + fn rollbackMultiPartRequest(this: *@This()) bun.JSTerminated!void { log("rollbackMultiPartRequest {s}", .{this.upload_id}); var params_buffer: [2048]u8 = undefined; const search_params = std.fmt.bufPrint(¶ms_buffer, "?uploadId={s}", .{ this.upload_id, }) catch unreachable; - executeSimpleS3Request(this.credentials, .{ + try executeSimpleS3Request(this.credentials, .{ .path = this.path, .method = .DELETE, .proxy_url = this.proxyUrl(), @@ -575,14 +575,14 @@ pub const MultiPartUpload = struct { .search_params = search_params, }, .{ .upload = @ptrCast(&onRollbackMultiPartRequest) }, this); } - fn enqueuePart(this: *@This(), chunk: []const u8, allocated_size: usize, needs_clone: bool) bool { + fn enqueuePart(this: *@This(), chunk: []const u8, allocated_size: usize, needs_clone: bool) bun.JSTerminated!bool { const part = this.getCreatePart(chunk, allocated_size, needs_clone) orelse return false; if (this.state == .not_started) { // will auto start later this.state = .multipart_started; this.ref(); - executeSimpleS3Request(this.credentials, .{ + try executeSimpleS3Request(this.credentials, .{ .path = this.path, .method = .POST, .proxy_url = this.proxyUrl(), @@ -593,16 +593,16 @@ pub const MultiPartUpload = struct { .storage_class = this.storage_class, }, .{ .download = @ptrCast(&startMultiPartRequestResult) }, this); } else if (this.state == .multipart_completed) { - part.start(); + try part.start(); } return true; } - fn processMultiPart(this: *@This(), part_size: usize) void { + fn processMultiPart(this: *@This(), part_size: usize) bun.JSTerminated!void { log("processMultiPart {s} {d}", .{ this.path, part_size }); if (this.buffered.isEmpty() and this.isQueueEmpty() and this.ended) { // no more data to send and we are done - this.done(); + try this.done(); return; } // need to split in multiple parts because of the size @@ -624,7 +624,7 @@ pub const MultiPartUpload = struct { const slice = this.buffered.slice(); // we dont care about the result because we are sending everything - if (this.enqueuePart(slice, allocated_size, false)) { + if (try this.enqueuePart(slice, allocated_size, false)) { log("processMultiPart {s} {d} full buffer enqueued", .{ this.path, slice.len }); // queue is not full, we can clear the buffer part now owns the data @@ -639,7 +639,7 @@ pub const MultiPartUpload = struct { const slice = this.buffered.slice()[0..len]; // allocated size is the slice len because we dupe the buffer - if (this.enqueuePart(slice, slice.len, true)) { + if (try this.enqueuePart(slice, slice.len, true)) { log("processMultiPart {s} {d} slice enqueued", .{ this.path, slice.len }); // queue is not full, we can set the offset this.buffered.wrote(len); @@ -667,10 +667,10 @@ pub const MultiPartUpload = struct { .content_type = this.content_type, .acl = this.acl, .storage_class = this.storage_class, - }, .{ .upload = @ptrCast(&singleSendUploadResponse) }, this); + }, .{ .upload = @ptrCast(&singleSendUploadResponse) }, this) catch {}; // TODO: properly propagate exception upwards } else { // we need to split - this.processMultiPart(part_size); + this.processMultiPart(part_size) catch {}; // TODO: properly propagate exception upwards } } diff --git a/src/s3/simple_request.zig b/src/s3/simple_request.zig index 3ee6d60e60..40c08470af 100644 --- a/src/s3/simple_request.zig +++ b/src/s3/simple_request.zig @@ -78,15 +78,15 @@ pub const S3HttpSimpleTask = struct { pub const new = bun.TrivialNew(@This()); pub const Callback = union(enum) { - stat: *const fn (S3StatResult, *anyopaque) void, - download: *const fn (S3DownloadResult, *anyopaque) void, - upload: *const fn (S3UploadResult, *anyopaque) void, - delete: *const fn (S3DeleteResult, *anyopaque) void, - listObjects: *const fn (S3ListObjectsResult, *anyopaque) void, - commit: *const fn (S3CommitResult, *anyopaque) void, - part: *const fn (S3PartResult, *anyopaque) void, + stat: *const fn (S3StatResult, *anyopaque) bun.JSTerminated!void, + download: *const fn (S3DownloadResult, *anyopaque) bun.JSTerminated!void, + upload: *const fn (S3UploadResult, *anyopaque) bun.JSTerminated!void, + delete: *const fn (S3DeleteResult, *anyopaque) bun.JSTerminated!void, + listObjects: *const fn (S3ListObjectsResult, *anyopaque) bun.JSTerminated!void, + commit: *const fn (S3CommitResult, *anyopaque) bun.JSTerminated!void, + part: *const fn (S3PartResult, *anyopaque) bun.JSTerminated!void, - pub fn fail(this: @This(), code: []const u8, message: []const u8, context: *anyopaque) void { + pub fn fail(this: @This(), code: []const u8, message: []const u8, context: *anyopaque) bun.JSTerminated!void { switch (this) { inline .upload, .download, @@ -95,7 +95,7 @@ pub const S3HttpSimpleTask = struct { .listObjects, .commit, .part, - => |callback| callback(.{ + => |callback| try callback(.{ .failure = .{ .code = code, .message = message, @@ -103,19 +103,19 @@ pub const S3HttpSimpleTask = struct { }, context), } } - pub fn notFound(this: @This(), code: []const u8, message: []const u8, context: *anyopaque) void { + pub fn notFound(this: @This(), code: []const u8, message: []const u8, context: *anyopaque) bun.JSTerminated!void { switch (this) { inline .download, .stat, .delete, .listObjects, - => |callback| callback(.{ + => |callback| try callback(.{ .not_found = .{ .code = code, .message = message, }, }, context), - else => this.fail(code, message, context), + else => try this.fail(code, message, context), } } }; @@ -142,7 +142,7 @@ pub const S3HttpSimpleTask = struct { not_found, failure, }; - fn errorWithBody(this: @This(), comptime error_type: ErrorType) void { + fn errorWithBody(this: @This(), comptime error_type: ErrorType) bun.JSTerminated!void { var code: []const u8 = "UnknownError"; var message: []const u8 = "an unexpected error has occurred"; var has_error_code = false; @@ -172,13 +172,13 @@ pub const S3HttpSimpleTask = struct { code = "NoSuchKey"; message = "The specified key does not exist."; } - this.callback.notFound(code, message, this.callback_context); + try this.callback.notFound(code, message, this.callback_context); } else { - this.callback.fail(code, message, this.callback_context); + try this.callback.fail(code, message, this.callback_context); } } - fn failIfContainsError(this: *@This(), status: u32) bool { + fn failIfContainsError(this: *@This(), status: u32) bun.JSTerminated!bool { var code: []const u8 = "UnknownError"; var message: []const u8 = "an unexpected error has occurred"; @@ -209,14 +209,14 @@ pub const S3HttpSimpleTask = struct { } else if (status == 200 or status == 206) { return false; } - this.callback.fail(code, message, this.callback_context); + try this.callback.fail(code, message, this.callback_context); return true; } /// this is the task callback from the last task result and is always in the main thread - pub fn onResponse(this: *@This()) void { + pub fn onResponse(this: *@This()) bun.JSTerminated!void { defer this.deinit(); if (!this.result.isSuccess()) { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); return; } bun.assert(this.result.metadata != null); @@ -225,7 +225,7 @@ pub const S3HttpSimpleTask = struct { .stat => |callback| { switch (response.status_code) { 200 => { - callback(.{ + try callback(.{ .success = .{ .etag = response.headers.get("etag") orelse "", .lastModified = response.headers.get("last-modified") orelse "", @@ -235,23 +235,23 @@ pub const S3HttpSimpleTask = struct { }, this.callback_context); }, 404 => { - this.errorWithBody(.not_found); + try this.errorWithBody(.not_found); }, else => { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); }, } }, .delete => |callback| { switch (response.status_code) { 200, 204 => { - callback(.{ .success = {} }, this.callback_context); + try callback(.{ .success = {} }, this.callback_context); }, 404 => { - this.errorWithBody(.not_found); + try this.errorWithBody(.not_found); }, else => { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); }, } }, @@ -260,30 +260,30 @@ pub const S3HttpSimpleTask = struct { 200 => { if (this.result.body) |body| { const success = ListObjects.parseS3ListObjectsResult(body.slice()) catch { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); return; }; - callback(.{ .success = success }, this.callback_context); + try callback(.{ .success = success }, this.callback_context); } else { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); } }, 404 => { - this.errorWithBody(.not_found); + try this.errorWithBody(.not_found); }, else => { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); }, } }, .upload => |callback| { switch (response.status_code) { 200 => { - callback(.{ .success = {} }, this.callback_context); + try callback(.{ .success = {} }, this.callback_context); }, else => { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); }, } }, @@ -298,7 +298,7 @@ pub const S3HttpSimpleTask = struct { .capacity = 0, }, }; - callback(.{ + try callback(.{ .success = .{ .etag = response.headers.get("etag") orelse "", .body = body, @@ -306,26 +306,26 @@ pub const S3HttpSimpleTask = struct { }, this.callback_context); }, 404 => { - this.errorWithBody(.not_found); + try this.errorWithBody(.not_found); }, else => { //error - this.errorWithBody(.failure); + try this.errorWithBody(.failure); }, } }, .commit => |callback| { // commit multipart upload can fail with status 200 - if (!this.failIfContainsError(response.status_code)) { - callback(.{ .success = {} }, this.callback_context); + if (!try this.failIfContainsError(response.status_code)) { + try callback(.{ .success = {} }, this.callback_context); } }, .part => |callback| { - if (!this.failIfContainsError(response.status_code)) { + if (!try this.failIfContainsError(response.status_code)) { if (response.headers.get("etag")) |etag| { - callback(.{ .etag = etag }, this.callback_context); + try callback(.{ .etag = etag }, this.callback_context); } else { - this.errorWithBody(.failure); + try this.errorWithBody(.failure); } } }, @@ -365,7 +365,7 @@ pub fn executeSimpleS3Request( options: S3SimpleRequestOptions, callback: S3HttpSimpleTask.Callback, callback_context: *anyopaque, -) void { +) bun.JSTerminated!void { var result = this.signRequest(.{ .path = options.path, .method = options.method, @@ -376,7 +376,7 @@ pub fn executeSimpleS3Request( }, false, null) catch |sign_err| { if (options.range) |range_| bun.default_allocator.free(range_); const error_code_and_message = getSignErrorCodeAndMessage(sign_err); - callback.fail(error_code_and_message.code, error_code_and_message.message, callback_context); + try callback.fail(error_code_and_message.code, error_code_and_message.message, callback_context); return; }; diff --git a/src/sql/mysql/protocol/AnyMySQLError.zig b/src/sql/mysql/protocol/AnyMySQLError.zig index f93a28f383..ab1a53949c 100644 --- a/src/sql/mysql/protocol/AnyMySQLError.zig +++ b/src/sql/mysql/protocol/AnyMySQLError.zig @@ -13,6 +13,7 @@ pub const Error = error{ LocalInfileNotSupported, JSError, + JSTerminated, OutOfMemory, Overflow, @@ -71,6 +72,9 @@ pub fn mysqlErrorToJS(globalObject: *jsc.JSGlobalObject, message: ?[]const u8, e error.JSError => { return globalObject.takeException(error.JSError); }, + error.JSTerminated => { + return globalObject.takeException(error.JSTerminated); + }, error.OutOfMemory => { // TODO: add binding for creating an out of memory error? return globalObject.takeException(globalObject.throwOutOfMemory()); diff --git a/src/sql/postgres/AnyPostgresError.zig b/src/sql/postgres/AnyPostgresError.zig index 8be278832b..6e93fa39c4 100644 --- a/src/sql/postgres/AnyPostgresError.zig +++ b/src/sql/postgres/AnyPostgresError.zig @@ -14,6 +14,7 @@ pub const AnyPostgresError = error{ InvalidServerSignature, InvalidTimeFormat, JSError, + JSTerminated, MultidimensionalArrayNotSupportedYet, NullsInArrayNotSupportedYet, OutOfMemory, @@ -111,6 +112,9 @@ pub fn postgresErrorToJS(globalObject: *jsc.JSGlobalObject, message: ?[]const u8 error.JSError => { return globalObject.takeException(error.JSError); }, + error.JSTerminated => { + return globalObject.takeException(error.JSTerminated); + }, error.OutOfMemory => { // TODO: add binding for creating an out of memory error? return globalObject.takeException(globalObject.throwOutOfMemory()); diff --git a/src/url.zig b/src/url.zig index 99523263e1..50114549ea 100644 --- a/src/url.zig +++ b/src/url.zig @@ -925,10 +925,10 @@ pub const FormData = struct { this.allocator.destroy(this); } - pub fn toJS(this: *AsyncFormData, global: *jsc.JSGlobalObject, data: []const u8, promise: jsc.AnyPromise) void { + pub fn toJS(this: *AsyncFormData, global: *jsc.JSGlobalObject, data: []const u8, promise: jsc.AnyPromise) bun.JSTerminated!void { if (this.encoding == .Multipart and this.encoding.Multipart.len == 0) { log("AsnycFormData.toJS -> promise.reject missing boundary", .{}); - promise.reject(global, jsc.ZigString.init("FormData missing boundary").toErrorInstance(global)); + try promise.reject(global, jsc.ZigString.init("FormData missing boundary").toErrorInstance(global)); return; } @@ -938,10 +938,10 @@ pub const FormData = struct { this.encoding, ) catch |err| { log("AsnycFormData.toJS -> failed ", .{}); - promise.reject(global, global.createErrorInstance("FormData {s}", .{@errorName(err)})); + try promise.reject(global, global.createErrorInstance("FormData {s}", .{@errorName(err)})); return; }; - promise.resolve(global, js_value); + try promise.resolve(global, js_value); } }; diff --git a/src/valkey/ValkeyCommand.zig b/src/valkey/ValkeyCommand.zig index 2c1fa6d3b9..563329ec46 100644 --- a/src/valkey/ValkeyCommand.zig +++ b/src/valkey/ValkeyCommand.zig @@ -127,20 +127,20 @@ pub const Promise = struct { }; } - pub fn resolve(self: *Promise, globalObject: *jsc.JSGlobalObject, value: *protocol.RESPValue) void { + pub fn resolve(self: *Promise, globalObject: *jsc.JSGlobalObject, value: *protocol.RESPValue) bun.JSTerminated!void { const options = protocol.RESPValue.ToJSOptions{ .return_as_buffer = self.meta.return_as_buffer, }; const js_value = value.toJSWithOptions(globalObject, options) catch |err| { - self.reject(globalObject, globalObject.takeError(err)); + try self.reject(globalObject, globalObject.takeError(err)); return; }; - self.promise.resolve(globalObject, js_value); + try self.promise.resolve(globalObject, js_value); } - pub fn reject(self: *Promise, globalObject: *jsc.JSGlobalObject, jsvalue: JSError!jsc.JSValue) void { - self.promise.reject(globalObject, jsvalue); + pub fn reject(self: *Promise, globalObject: *jsc.JSGlobalObject, jsvalue: JSError!jsc.JSValue) bun.JSTerminated!void { + try self.promise.reject(globalObject, jsvalue); } pub fn deinit(self: *Promise) void { @@ -155,8 +155,8 @@ pub const PromisePair = struct { pub const Queue = std.fifo.LinearFifo(PromisePair, .Dynamic); - pub fn rejectCommand(self: *PromisePair, globalObject: *jsc.JSGlobalObject, jsvalue: jsc.JSValue) void { - self.promise.reject(globalObject, jsvalue); + pub fn rejectCommand(self: *PromisePair, globalObject: *jsc.JSGlobalObject, jsvalue: jsc.JSValue) bun.JSTerminated!void { + try self.promise.reject(globalObject, jsvalue); } }; diff --git a/src/valkey/js_valkey.zig b/src/valkey/js_valkey.zig index 69c8d88b00..24f77d2648 100644 --- a/src/valkey/js_valkey.zig +++ b/src/valkey/js_valkey.zig @@ -586,7 +586,7 @@ pub const JSValkeyClient = struct { const event_loop = this.client.vm.eventLoop(); event_loop.enter(); defer event_loop.exit(); - promise_ptr.reject(globalObject, err_value); + try promise_ptr.reject(globalObject, err_value); return promise; }; @@ -722,11 +722,11 @@ pub const JSValkeyClient = struct { switch (this.client.status) { .connected => { const msg = std.fmt.bufPrintZ(&buf, "Idle timeout reached after {d}ms", .{this.client.idle_timeout_interval_ms}) catch unreachable; - this.clientFail(msg, protocol.RedisError.IdleTimeout); + this.clientFail(msg, protocol.RedisError.IdleTimeout) catch {}; // TODO: properly propagate exception upwards }, .disconnected, .connecting => { const msg = std.fmt.bufPrintZ(&buf, "Connection timeout reached after {d}ms", .{this.client.connection_timeout_ms}) catch unreachable; - this.clientFail(msg, protocol.RedisError.ConnectionTimeout); + this.clientFail(msg, protocol.RedisError.ConnectionTimeout) catch {}; // TODO: properly propagate exception upwards }, } @@ -781,7 +781,7 @@ pub const JSValkeyClient = struct { } // Callback for when Valkey client connects - pub fn onValkeyConnect(this: *JSValkeyClient, value: *protocol.RESPValue) void { + pub fn onValkeyConnect(this: *JSValkeyClient, value: *protocol.RESPValue) bun.JSTerminated!void { bun.debugAssert(this.client.status == .connected); // we should always have a strong reference to the object here bun.debugAssert(this.this_value.isStrong()); @@ -818,10 +818,10 @@ pub const JSValkeyClient = struct { const js_promise = promise.asPromise().?; if (this.client.flags.connection_promise_returns_client) { debug("Resolving connection promise with client instance", .{}); - js_promise.resolve(globalObject, this_value); + try js_promise.resolve(globalObject, this_value); } else { debug("Resolving connection promise with HELLO response", .{}); - js_promise.resolve(globalObject, hello_value); + try js_promise.resolve(globalObject, hello_value); } this.client.flags.connection_promise_returns_client = false; } @@ -914,7 +914,7 @@ pub const JSValkeyClient = struct { } // Callback for when Valkey client closes - pub fn onValkeyClose(this: *JSValkeyClient) void { + pub fn onValkeyClose(this: *JSValkeyClient) bun.JSTerminated!void { const globalObject = this.globalObject; defer { @@ -936,7 +936,7 @@ pub const JSValkeyClient = struct { if (!this_jsvalue.isUndefined()) { if (js.connectionPromiseGetCached(this_jsvalue)) |promise| { js.connectionPromiseSetCached(this_jsvalue, globalObject, .zero); - promise.asPromise().?.reject(globalObject, error_value); + try promise.asPromise().?.reject(globalObject, error_value); } } @@ -955,8 +955,8 @@ pub const JSValkeyClient = struct { this.clientFail("Connection timeout", protocol.RedisError.ConnectionClosed); } - pub fn clientFail(this: *JSValkeyClient, message: []const u8, err: protocol.RedisError) void { - this.client.fail(message, err); + pub fn clientFail(this: *JSValkeyClient, message: []const u8, err: protocol.RedisError) bun.JSTerminated!void { + try this.client.fail(message, err); } pub fn failWithJSValue(this: *JSValkeyClient, value: JSValue) void { @@ -1025,11 +1025,11 @@ pub const JSValkeyClient = struct { } } - fn failWithInvalidSocketContext(this: *JSValkeyClient) void { + fn failWithInvalidSocketContext(this: *JSValkeyClient) bun.JSTerminated!void { // if the context is invalid is not worth retrying this.client.flags.enable_auto_reconnect = false; - this.clientFail(if (this.client.tls == .none) "Failed to create TCP context" else "Failed to create TLS context", protocol.RedisError.ConnectionClosed); - this.client.onValkeyClose(); + try this.clientFail(if (this.client.tls == .none) "Failed to create TCP context" else "Failed to create TLS context", protocol.RedisError.ConnectionClosed); + try this.client.onValkeyClose(); } fn connect(this: *JSValkeyClient) !void { @@ -1044,7 +1044,7 @@ pub const JSValkeyClient = struct { vm.rareData().valkey_context.tcp orelse brk_ctx: { // TCP socket const ctx_ = uws.SocketContext.createNoSSLContext(vm.uwsLoop(), @sizeOf(*JSValkeyClient)) orelse { - this.failWithInvalidSocketContext(); + try this.failWithInvalidSocketContext(); this.client.status = .disconnected; return; }; @@ -1059,7 +1059,7 @@ pub const JSValkeyClient = struct { // TLS socket, default config var err: uws.create_bun_socket_error_t = .none; const ctx_ = uws.SocketContext.createSSLContext(vm.uwsLoop(), @sizeOf(*JSValkeyClient), uws.SocketContext.BunSocketContextOptions{}, &err) orelse { - this.failWithInvalidSocketContext(); + try this.failWithInvalidSocketContext(); this.client.status = .disconnected; return; }; @@ -1078,7 +1078,7 @@ pub const JSValkeyClient = struct { const options = custom.asUSockets(); const ctx_ = uws.SocketContext.createSSLContext(vm.uwsLoop(), @sizeOf(*JSValkeyClient), options, &err) orelse { - this.failWithInvalidSocketContext(); + try this.failWithInvalidSocketContext(); this.client.status = .disconnected; return; }; @@ -1113,7 +1113,7 @@ pub const JSValkeyClient = struct { const event_loop = this.client.vm.eventLoop(); event_loop.enter(); defer event_loop.exit(); - promise.reject(globalThis, err_value); + try promise.reject(globalThis, err_value); return promise; }; this.resetConnectionTimeout(); @@ -1420,12 +1420,12 @@ fn SocketHandler(comptime ssl: bool) type { return Socket{ .SocketTCP = s }; } - pub fn onOpen(this: *JSValkeyClient, socket: SocketType) void { + pub fn onOpen(this: *JSValkeyClient, socket: SocketType) bun.JSTerminated!void { this.client.socket = _socket(socket); - this.client.onOpen(_socket(socket)); + try this.client.onOpen(_socket(socket)); } - fn onHandshake_(this: *JSValkeyClient, _: anytype, success: i32, ssl_error: uws.us_bun_verify_error_t) void { + fn onHandshake_(this: *JSValkeyClient, _: anytype, success: i32, ssl_error: uws.us_bun_verify_error_t) bun.JSTerminated!void { debug("onHandshake: {d} error={d} reason={s} code={s}", .{ success, ssl_error.error_no, @@ -1451,14 +1451,14 @@ fn SocketHandler(comptime ssl: bool) type { loop.enter(); defer loop.exit(); this.client.flags.is_manually_closed = true; - this.client.failWithJSValue(this.globalObject, ssl_error.toJS(this.globalObject)); - this.client.close(); + defer this.client.close(); + try this.client.failWithJSValue(this.globalObject, ssl_error.toJS(this.globalObject)); return; } } } } - this.client.start(); + try this.client.start(); } } @@ -1477,7 +1477,7 @@ fn SocketHandler(comptime ssl: bool) type { this.deref(); } - this.client.onClose(); + this.client.onClose() catch {}; // TODO: properly propagate exception upwards } pub fn onEnd(this: *JSValkeyClient, socket: SocketType) void { @@ -1488,7 +1488,7 @@ fn SocketHandler(comptime ssl: bool) type { // usockets will always call onClose after onEnd in this case so we don't need to do anything here } - pub fn onConnectError(this: *JSValkeyClient, _: SocketType, _: i32) void { + pub fn onConnectError(this: *JSValkeyClient, _: SocketType, _: i32) bun.JSTerminated!void { // Ensure the socket pointer is updated. this.client.socket = .{ .SocketTCP = .detached }; this.ref(); @@ -1498,7 +1498,7 @@ fn SocketHandler(comptime ssl: bool) type { this.deref(); } - this.client.onClose(); + try this.client.onClose(); } pub fn onTimeout(this: *JSValkeyClient, socket: SocketType) void { @@ -1514,7 +1514,7 @@ fn SocketHandler(comptime ssl: bool) type { this.ref(); defer this.deref(); - this.client.onData(data); + this.client.onData(data) catch {}; // TODO: properly propagate exception upwards this.updatePollRef(); } diff --git a/src/valkey/valkey.zig b/src/valkey/valkey.zig index dbce0e4699..2d0a58f9c8 100644 --- a/src/valkey/valkey.zig +++ b/src/valkey/valkey.zig @@ -220,12 +220,12 @@ pub const ValkeyClient = struct { const object = protocol.valkeyErrorToJS(globalThis, "Connection closed", protocol.RedisError.ConnectionClosed); for (pending.readableSlice(0)) |pair| { var pair_ = pair; - pair_.rejectCommand(globalThis, object); + pair_.rejectCommand(globalThis, object) catch {}; // TODO: properly propagate exception upwards } for (commands.readableSlice(0)) |cmd| { var offline_cmd = cmd; - offline_cmd.promise.reject(globalThis, object); + offline_cmd.promise.reject(globalThis, object) catch {}; // TODO: properly propagate exception upwards offline_cmd.deinit(this.allocator); } } else { @@ -358,7 +358,7 @@ pub const ValkeyClient = struct { } /// Reject all pending commands with an error - fn rejectAllPendingCommands(pending_ptr: *Command.PromisePair.Queue, entries_ptr: *Command.Entry.Queue, globalThis: *jsc.JSGlobalObject, allocator: std.mem.Allocator, jsvalue: jsc.JSValue) void { + fn rejectAllPendingCommands(pending_ptr: *Command.PromisePair.Queue, entries_ptr: *Command.Entry.Queue, globalThis: *jsc.JSGlobalObject, allocator: std.mem.Allocator, jsvalue: jsc.JSValue) bun.JSTerminated!void { var pending = pending_ptr.*; var entries = entries_ptr.*; defer pending.deinit(); @@ -369,14 +369,14 @@ pub const ValkeyClient = struct { // Reject commands in the command queue for (pending.readableSlice(0)) |item| { var command_pair = item; - command_pair.rejectCommand(globalThis, jsvalue); + try command_pair.rejectCommand(globalThis, jsvalue); } // Reject commands in the offline queue for (entries.readableSlice(0)) |item| { var cmd = item; - cmd.promise.reject(globalThis, jsvalue); - cmd.deinit(allocator); + defer cmd.deinit(allocator); + try cmd.promise.reject(globalThis, jsvalue); } } @@ -399,14 +399,14 @@ pub const ValkeyClient = struct { in_flight: Command.PromisePair.Queue, queue: Command.Entry.Queue, - pub fn run(this: *DeferredFailure) void { + pub fn run(this: *DeferredFailure) bun.JSTerminated!void { defer { bun.default_allocator.free(this.message); bun.destroy(this); } debug("running deferred failure", .{}); const err = protocol.valkeyErrorToJS(this.globalThis, this.message, this.err); - rejectAllPendingCommands(&this.in_flight, &this.queue, this.globalThis, bun.default_allocator, err); + try rejectAllPendingCommands(&this.in_flight, &this.queue, this.globalThis, bun.default_allocator, err); } pub fn enqueue(this: *DeferredFailure) void { @@ -417,7 +417,7 @@ pub const ValkeyClient = struct { }; /// Mark the connection as failed with error message - pub fn fail(this: *ValkeyClient, message: []const u8, err: protocol.RedisError) void { + pub fn fail(this: *ValkeyClient, message: []const u8, err: protocol.RedisError) bun.JSTerminated!void { debug("failed: {s}: {}", .{ message, err }); if (this.flags.failed) return; @@ -444,18 +444,19 @@ pub const ValkeyClient = struct { } const globalThis = this.globalObject(); - this.failWithJSValue(globalThis, protocol.valkeyErrorToJS(globalThis, message, err)); + try this.failWithJSValue(globalThis, protocol.valkeyErrorToJS(globalThis, message, err)); } - pub fn failWithJSValue(this: *ValkeyClient, globalThis: *jsc.JSGlobalObject, jsvalue: jsc.JSValue) void { + pub fn failWithJSValue(this: *ValkeyClient, globalThis: *jsc.JSGlobalObject, jsvalue: jsc.JSValue) bun.JSTerminated!void { if (this.flags.failed) return; this.flags.failed = true; - rejectAllPendingCommands(&this.in_flight, &this.queue, globalThis, this.allocator, jsvalue); + const val = rejectAllPendingCommands(&this.in_flight, &this.queue, globalThis, this.allocator, jsvalue); if (!this.connectionReady()) { this.flags.is_manually_closed = true; this.close(); } + return val; } pub fn close(this: *ValkeyClient) void { @@ -465,23 +466,23 @@ pub const ValkeyClient = struct { } /// Handle connection closed event - pub fn onClose(this: *ValkeyClient) void { + pub fn onClose(this: *ValkeyClient) bun.JSTerminated!void { this.unregisterAutoFlusher(); this.write_buffer.clearAndFree(this.allocator); // If manually closing, don't attempt to reconnect if (this.flags.is_manually_closed) { debug("skip reconnecting since the connection is manually closed", .{}); - this.fail("Connection closed", protocol.RedisError.ConnectionClosed); - this.onValkeyClose(); + try this.fail("Connection closed", protocol.RedisError.ConnectionClosed); + try this.onValkeyClose(); return; } // If auto reconnect is disabled, just fail if (!this.flags.enable_auto_reconnect) { debug("skip reconnecting since auto reconnect is disabled", .{}); - this.fail("Connection closed", protocol.RedisError.ConnectionClosed); - this.onValkeyClose(); + try this.fail("Connection closed", protocol.RedisError.ConnectionClosed); + try this.onValkeyClose(); return; } @@ -491,8 +492,8 @@ pub const ValkeyClient = struct { if (delay_ms == 0 or this.retry_attempts > this.max_retries) { debug("Max retries reached or retry strategy returned 0, giving up reconnection", .{}); - this.fail("Max reconnection attempts reached", protocol.RedisError.ConnectionClosed); - this.onValkeyClose(); + try this.fail("Max reconnection attempts reached", protocol.RedisError.ConnectionClosed); + try this.onValkeyClose(); return; } @@ -541,7 +542,7 @@ pub const ValkeyClient = struct { /// Process data received from socket /// /// Caller refs / derefs. - pub fn onData(this: *ValkeyClient, data: []const u8) void { + pub fn onData(this: *ValkeyClient, data: []const u8) bun.JSTerminated!void { debug("Low-level onData called with {d} bytes: {s}", .{ data.len, data }); // Path 1: Buffer already has data, append and process from buffer if (this.read_buffer.remaining().len > 0) { @@ -565,7 +566,7 @@ pub const ValkeyClient = struct { } return; } else { - this.fail("Failed to read data (buffer path)", err); + try this.fail("Failed to read data (buffer path)", err); return; } }; @@ -573,7 +574,7 @@ pub const ValkeyClient = struct { const bytes_consumed = reader.pos - before_read_pos; if (bytes_consumed == 0 and remaining_buffer.len > 0) { - this.fail("Parser consumed 0 bytes unexpectedly (buffer path)", error.InvalidResponse); + try this.fail("Parser consumed 0 bytes unexpectedly (buffer path)", error.InvalidResponse); return; } @@ -581,7 +582,7 @@ pub const ValkeyClient = struct { var value_to_handle = value; // Use temp var for defer this.handleResponse(&value_to_handle) catch |err| { - this.fail("Failed to handle response (buffer path)", err); + try this.fail("Failed to handle response (buffer path)", err); return; }; @@ -611,7 +612,7 @@ pub const ValkeyClient = struct { return; // Exit onData, next call will use the buffer path } else { // Any other error is fatal - this.fail("Failed to read data (stack path)", err); + try this.fail("Failed to read data (stack path)", err); return; } }; @@ -621,7 +622,7 @@ pub const ValkeyClient = struct { const bytes_consumed = reader.pos - before_read_pos; if (bytes_consumed == 0) { // This case should ideally not happen if readValue succeeded and slice wasn't empty - this.fail("Parser consumed 0 bytes unexpectedly (stack path)", error.InvalidResponse); + try this.fail("Parser consumed 0 bytes unexpectedly (stack path)", error.InvalidResponse); return; } @@ -631,7 +632,7 @@ pub const ValkeyClient = struct { // Handle the successfully parsed response var value_to_handle = value; // Use temp var for defer this.handleResponse(&value_to_handle) catch |err| { - this.fail("Failed to handle response (stack path)", err); + try this.fail("Failed to handle response (stack path)", err); return; }; @@ -667,7 +668,7 @@ pub const ValkeyClient = struct { return switch (value.*) { .Error => { if (pair) |p| { - p.promise.reject(globalThis, value.toJS(globalThis)); + try p.promise.reject(globalThis, value.toJS(globalThis)); } return .handled; }, @@ -688,7 +689,7 @@ pub const ValkeyClient = struct { // For SUBSCRIBE responses, only resolve the promise for the first channel confirmation // Additional channel confirmations from multi-channel SUBSCRIBE commands don't need promise pairs if (pair) |req_pair| { - req_pair.promise.promise.resolve(globalThis, .jsNumber(sub_count)); + try req_pair.promise.promise.resolve(globalThis, .jsNumber(sub_count)); } return .handled; }, @@ -699,7 +700,7 @@ pub const ValkeyClient = struct { // For UNSUBSCRIBE responses, only resolve the promise if we have one // Additional channel confirmations from multi-channel UNSUBSCRIBE commands don't need promise pairs if (pair) |req_pair| { - req_pair.promise.promise.resolve(globalThis, .js_undefined); + try req_pair.promise.promise.resolve(globalThis, .js_undefined); } return .handled; }, @@ -708,7 +709,7 @@ pub const ValkeyClient = struct { // We should rarely reach this point. If we're guaranteed to be handling a subscribe/unsubscribe, // then this is an unexpected path. @branchHint(.cold); - this.fail( + try this.fail( "Push message is not a subscription message.", protocol.RedisError.InvalidResponseType, ); @@ -723,22 +724,22 @@ pub const ValkeyClient = struct { }; } - fn handleHelloResponse(this: *ValkeyClient, value: *protocol.RESPValue) void { + fn handleHelloResponse(this: *ValkeyClient, value: *protocol.RESPValue) bun.JSTerminated!void { debug("Processing HELLO response", .{}); switch (value.*) { .Error => |err| { - this.fail(err, protocol.RedisError.AuthenticationFailed); + try this.fail(err, protocol.RedisError.AuthenticationFailed); return; }, .SimpleString => |str| { if (std.mem.eql(u8, str, "OK")) { this.status = .connected; this.flags.is_authenticated = true; - this.onValkeyConnect(value); + try this.onValkeyConnect(value); return; } - this.fail("Authentication failed (unexpected response)", protocol.RedisError.AuthenticationFailed); + try this.fail("Authentication failed (unexpected response)", protocol.RedisError.AuthenticationFailed); return; }, @@ -755,7 +756,7 @@ pub const ValkeyClient = struct { const proto_version = entry.value.Integer; debug("Server protocol version: {d}", .{proto_version}); if (proto_version != 3) { - this.fail("Server does not support RESP3", protocol.RedisError.UnsupportedProtocol); + try this.fail("Server does not support RESP3", protocol.RedisError.UnsupportedProtocol); return; } } @@ -768,11 +769,11 @@ pub const ValkeyClient = struct { // Authentication successful via HELLO this.status = .connected; this.flags.is_authenticated = true; - this.onValkeyConnect(value); + try this.onValkeyConnect(value); return; }, else => { - this.fail("Authentication failed with unexpected response", protocol.RedisError.AuthenticationFailed); + try this.fail("Authentication failed with unexpected response", protocol.RedisError.AuthenticationFailed); return; }, } @@ -782,7 +783,7 @@ pub const ValkeyClient = struct { fn handleResponse(this: *ValkeyClient, value: *protocol.RESPValue) !void { // Special handling for the initial HELLO response if (!this.flags.is_authenticated) { - this.handleHelloResponse(value); + try this.handleHelloResponse(value); // We've handled the HELLO response without consuming anything from the command queue return; @@ -794,12 +795,12 @@ pub const ValkeyClient = struct { return switch (value.*) { .Error => |err_str| { - this.fail(err_str, protocol.RedisError.InvalidCommand); + try this.fail(err_str, protocol.RedisError.InvalidCommand); }, .SimpleString => |ok_str| { if (!std.mem.eql(u8, ok_str, "OK")) { // SELECT returned something other than "OK" - this.fail("SELECT command failed with non-OK response", protocol.RedisError.InvalidResponse); + try this.fail("SELECT command failed with non-OK response", protocol.RedisError.InvalidResponse); return; } @@ -810,7 +811,7 @@ pub const ValkeyClient = struct { this.sendNextCommand(); }, else => { // Unexpected response type for SELECT - this.fail("Received non-SELECT response while in the SELECT state.", protocol.RedisError.InvalidResponse); + try this.fail("Received non-SELECT response while in the SELECT state.", protocol.RedisError.InvalidResponse); }, }; } @@ -858,7 +859,7 @@ pub const ValkeyClient = struct { switch (value.*) { .Error => |err| { - this.fail(err, protocol.RedisError.InvalidResponse); + try this.fail(err, protocol.RedisError.InvalidResponse); return; }, .Push => |push| { @@ -868,7 +869,7 @@ pub const ValkeyClient = struct { } } else { @branchHint(.cold); - this.fail( + try this.fail( "Unexpected push message kind without promise", protocol.RedisError.InvalidResponseType, ); @@ -910,14 +911,14 @@ pub const ValkeyClient = struct { defer loop.exit(); if (value.* == .Error) { - promise_ptr.reject(globalThis, value.toJS(globalThis) catch |err| globalThis.takeError(err)); + try promise_ptr.reject(globalThis, value.toJS(globalThis) catch |err| globalThis.takeError(err)); } else { - promise_ptr.resolve(globalThis, value); + try promise_ptr.resolve(globalThis, value); } } /// Send authentication command to Valkey server - fn authenticate(this: *ValkeyClient) void { + fn authenticate(this: *ValkeyClient) bun.JSTerminated!void { // First send HELLO command for RESP3 protocol debug("Sending HELLO 3 command", .{}); @@ -949,7 +950,7 @@ pub const ValkeyClient = struct { }; hello_cmd.write(this.writer()) catch |err| { - this.fail("Failed to write HELLO command", err); + try this.fail("Failed to write HELLO command", err); return; }; @@ -962,7 +963,7 @@ pub const ValkeyClient = struct { .args = .{ .raw = &[_][]const u8{db_str} }, }; select_cmd.write(this.writer()) catch |err| { - this.fail("Failed to write SELECT command", err); + try this.fail("Failed to write SELECT command", err); return; }; this.flags.is_selecting_db_internal = true; @@ -970,20 +971,20 @@ pub const ValkeyClient = struct { } /// Handle socket open event - pub fn onOpen(this: *ValkeyClient, socket: uws.AnySocket) void { + pub fn onOpen(this: *ValkeyClient, socket: uws.AnySocket) bun.JSTerminated!void { this.socket = socket; this.write_buffer.clearAndFree(this.allocator); this.read_buffer.clearAndFree(this.allocator); if (this.socket == .SocketTCP) { // if is tcp, we need to start the connection process // if is tls, we need to wait for the handshake to complete - this.start(); + try this.start(); } } /// Start the connection process - pub fn start(this: *ValkeyClient) void { - this.authenticate(); + pub fn start(this: *ValkeyClient) bun.JSTerminated!void { + try this.authenticate(); _ = this.flushData(); } @@ -1078,7 +1079,7 @@ pub const ValkeyClient = struct { switch (this.status) { .connecting, .connected => { command.write(this.writer()) catch { - promise.reject(this.globalObject(), this.globalObject().createOutOfMemoryError()); + try promise.reject(this.globalObject(), this.globalObject().createOutOfMemoryError()); return; }; }, @@ -1105,7 +1106,7 @@ pub const ValkeyClient = struct { const js_promise = promise.promise.get(); if (this.flags.failed) { - promise.reject(globalThis, globalThis.ERR(.REDIS_CONNECTION_CLOSED, "Connection has failed", .{}).toJS()); + try promise.reject(globalThis, globalThis.ERR(.REDIS_CONNECTION_CLOSED, "Connection has failed", .{}).toJS()); } else { // Handle disconnected state with offline queue switch (this.status) { @@ -1126,7 +1127,7 @@ pub const ValkeyClient = struct { if (this.flags.enable_offline_queue) { try this.enqueue(&checked_command, &promise); } else { - promise.reject( + try promise.reject( globalThis, globalThis.ERR( .REDIS_CONNECTION_CLOSED, @@ -1179,8 +1180,8 @@ pub const ValkeyClient = struct { return this.parent().globalObject; } - pub fn onValkeyConnect(this: *ValkeyClient, value: *protocol.RESPValue) void { - this.parent().onValkeyConnect(value); + pub fn onValkeyConnect(this: *ValkeyClient, value: *protocol.RESPValue) bun.JSTerminated!void { + return this.parent().onValkeyConnect(value); } pub fn onValkeySubscribe(this: *ValkeyClient, value: *protocol.RESPValue) void { @@ -1199,8 +1200,8 @@ pub const ValkeyClient = struct { this.parent().onValkeyReconnect(); } - pub fn onValkeyClose(this: *ValkeyClient) void { - this.parent().onValkeyClose(); + pub fn onValkeyClose(this: *ValkeyClient) bun.JSTerminated!void { + return this.parent().onValkeyClose(); } pub fn onValkeyTimeout(this: *ValkeyClient) void { diff --git a/src/valkey/valkey_protocol.zig b/src/valkey/valkey_protocol.zig index ed63377925..cd325af06b 100644 --- a/src/valkey/valkey_protocol.zig +++ b/src/valkey/valkey_protocol.zig @@ -22,6 +22,7 @@ pub const RedisError = error{ InvalidVerbatimString, JSError, OutOfMemory, + JSTerminated, UnsupportedProtocol, ConnectionTimeout, IdleTimeout, @@ -55,8 +56,8 @@ pub fn valkeyErrorToJS(globalObject: *jsc.JSGlobalObject, message: ?[]const u8, error.ConnectionTimeout => .REDIS_CONNECTION_TIMEOUT, error.IdleTimeout => .REDIS_IDLE_TIMEOUT, error.JSError => return globalObject.takeException(error.JSError), - error.OutOfMemory => globalObject.throwOutOfMemory() catch - return globalObject.takeException(error.JSError), + error.OutOfMemory => globalObject.throwOutOfMemory() catch return globalObject.takeException(error.JSError), + error.JSTerminated => return globalObject.takeException(error.JSTerminated), }; if (message) |msg| { diff --git a/test/internal/ban-limits.json b/test/internal/ban-limits.json index e513d7f719..4da3f15cf3 100644 --- a/test/internal/ban-limits.json +++ b/test/internal/ban-limits.json @@ -17,6 +17,7 @@ "EXCEPTION_ASSERT(!scope.exception())": 0, "JSValue.false": 0, "JSValue.true": 0, + "TODO: properly propagate exception upwards": 118, "alloc.ptr !=": 0, "alloc.ptr ==": 0, "allocator.ptr !=": 1, diff --git a/test/internal/ban-words.test.ts b/test/internal/ban-words.test.ts index 3e8b5a4962..2d5331451a 100644 --- a/test/internal/ban-words.test.ts +++ b/test/internal/ban-words.test.ts @@ -53,6 +53,7 @@ const words: Record = { "globalThis.hasException": { reason: "Incompatible with strict exception checks. Use a CatchScope instead." }, "EXCEPTION_ASSERT(!scope.exception())": { reason: "Use scope.assertNoException() instead" }, " catch bun.outOfMemory()": { reason: "Use bun.handleOom to avoid catching unrelated errors" }, + "TODO: properly propagate exception upwards": { reason: "This entry is here for tracking" }, }; const words_keys = [...Object.keys(words)];