Compare commits

...

4 Commits

Author SHA1 Message Date
Jarred Sumner
822b8f80f2 Implement Request IDs 2024-08-03 23:27:34 -07:00
Jarred Sumner
a9ea41f433 fixup 2024-08-03 19:46:04 -07:00
Jarred Sumner
1227d81260 Update NodeError.cpp 2024-08-03 19:14:09 -07:00
Jarred Sumner
c504f8a21e Make signal.abort() fast 2024-08-03 19:11:47 -07:00
19 changed files with 652 additions and 202 deletions

View File

@@ -32,6 +32,10 @@ function generate(name) {
fn: "doRequestIP",
length: 1,
},
requestID: {
fn: "doRequestID",
length: 1,
},
port: {
getter: "getPort",
},

View File

@@ -1298,6 +1298,7 @@ fn NewFlags(comptime debug_mode: bool) type {
response_protected: bool = false,
aborted: bool = false,
has_finalized: bun.DebugOnly(bool) = bun.DebugOnlyDefault(false),
must_bust_abort_reason_cache_to_prevent_leaking_error_instance: bool = false,
is_error_promise_pending: bool = false,
};
@@ -1305,12 +1306,15 @@ fn NewFlags(comptime debug_mode: bool) type {
/// A generic wrapper for the HTTP(s) Server`RequestContext`s.
/// Only really exists because of `NewServer()` and `NewRequestContext()` generics.
/// When the RequestContext is finalized, this points to the request ID.
pub const AnyRequestContext = struct {
const RequestIDInt = usize;
pub const Pointer = bun.TaggedPointerUnion(.{
HTTPServer.RequestContext,
HTTPSServer.RequestContext,
DebugHTTPServer.RequestContext,
DebugHTTPSServer.RequestContext,
RequestIDInt,
});
tagged_pointer: Pointer,
@@ -1339,7 +1343,7 @@ pub const AnyRequestContext = struct {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).getRemoteSocketInfo();
},
else => @panic("Unexpected AnyRequestContext tag"),
else => return null,
}
}
@@ -1366,6 +1370,34 @@ pub const AnyRequestContext = struct {
}
}
pub fn getRequestID(self: AnyRequestContext) ?RequestID {
if (self.tagged_pointer.isNull()) {
return null;
}
switch (self.tagged_pointer.tag()) {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPServer.RequestContext).getRequestID();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(HTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(HTTPSServer.RequestContext).getRequestID();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPServer.RequestContext).getRequestID();
},
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).getRequestID();
},
Pointer.Tag.usize => return self.tagged_pointer.repr._ptr,
else => @panic("Unexpected AnyRequestContext tag"),
}
}
pub fn setRequestID(self: *AnyRequestContext, id: JSC.CommonAbortReason.Cacheable.ID) void {
self.tagged_pointer.repr._ptr = @intCast(id);
self.tagged_pointer.repr.data = @intFromEnum(Pointer.Tag.usize);
}
pub fn getRequest(self: AnyRequestContext) ?*uws.Request {
if (self.tagged_pointer.isNull()) {
return null;
@@ -1384,11 +1416,27 @@ pub const AnyRequestContext = struct {
@field(Pointer.Tag, bun.meta.typeBaseName(@typeName(DebugHTTPSServer.RequestContext))) => {
return self.tagged_pointer.as(DebugHTTPSServer.RequestContext).req;
},
Pointer.Tag.usize => return null,
else => @panic("Unexpected AnyRequestContext tag"),
}
}
};
pub const RequestID = struct {
pub const Int = u48;
};
pub fn nextRequestID() JSC.CommonAbortReason.Cacheable.SizedID {
const Holder = struct {
pub var current_request_id = std.atomic.Value(u64).init(1);
};
const max = Holder.current_request_id.fetchAdd(1, .monotonic);
// Never return 0
// Number between 1 and 2^31 - 1
return @truncate((max % JSC.CommonAbortReason.Cacheable.max_id) + 1);
}
// This is defined separately partially to work-around an LLVM debugger bug.
fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comptime ThisServer: type) type {
return struct {
@@ -1445,6 +1493,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
/// Defer finalization until after the request handler task is completed?
defer_deinit_until_callback_completes: ?*bool = null,
request_id: JSC.CommonAbortReason.Cacheable.ID = 0,
// TODO: support builtin compression
const can_sendfile = !ssl_enabled and !Environment.isWindows;
@@ -1455,7 +1504,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
fn drainMicrotasks(this: *const RequestContext) void {
if (this.isAsync()) return;
if(this.server) |server| server.vm.drainMicrotasks();
if (this.server) |server| server.vm.drainMicrotasks();
}
pub fn setAbortHandler(this: *RequestContext) void {
@@ -1484,7 +1533,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
var class_name = value.getClassInfoName() orelse bun.String.empty;
defer class_name.deref();
if(ctx.server) |server| {
if (ctx.server) |server| {
const globalThis: *JSC.JSGlobalObject = server.globalThis;
Output.enableBuffering();
@@ -1516,7 +1565,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return;
}
if(ctx.server == null) {
if (ctx.server == null) {
ctx.renderMissingInvalidResponse(value);
return;
}
@@ -1588,7 +1637,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.request_body = null;
}
if(this.server) |server| {
if (this.server) |server| {
this.server = null;
server.request_pool_allocator.put(this);
server.onRequestComplete();
@@ -1784,7 +1833,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
this.detachResponse();
this.endRequestStreamingAndDrain();
this.deref();
this.deref();
}
/// Drain a partial response buffer
@@ -1882,6 +1931,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
.req = req,
.method = HTTP.Method.which(req.method()) orelse .GET,
.server = server,
.request_id = nextRequestID(),
};
ctxLog("create<d> ({*})<r>", .{this});
@@ -1911,9 +1961,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.signal = null;
defer signal.unref();
if (!signal.aborted()) {
const reason = JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, globalThis);
reason.ensureStillAlive();
_ = signal.signal(reason);
this.flags.must_bust_abort_reason_cache_to_prevent_leaking_error_instance = true;
signal.signal(globalThis, .{
.ConnectionClosed = this.request_id,
});
any_js_calls = true;
}
}
@@ -1921,6 +1972,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
//if have sink, call onAborted on sink
if (this.sink) |wrapper| {
wrapper.sink.abort();
this.flags.must_bust_abort_reason_cache_to_prevent_leaking_error_instance = true;
return;
}
@@ -1928,7 +1980,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
if (this.isDeadRequest()) {
this.finalizeWithoutDeinit();
} else {
if(this.endRequestStreaming()) {
if (this.endRequestStreaming()) {
any_js_calls = true;
}
@@ -1954,6 +2006,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
assert(this.server != null);
const globalThis = this.server.?.globalThis;
var must_bust_cache = this.flags.must_bust_abort_reason_cache_to_prevent_leaking_error_instance;
if (comptime Environment.allow_assert) {
ctxLog("finalizeWithoutDeinit: has_finalized {any}", .{this.flags.has_finalized});
this.flags.has_finalized = true;
@@ -1973,13 +2027,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.signal = null;
defer signal.unref();
if (this.flags.aborted and !signal.aborted()) {
const reason = JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, globalThis);
reason.ensureStillAlive();
_ = signal.signal(reason);
this.flags.must_bust_abort_reason_cache_to_prevent_leaking_error_instance = true;
signal.signal(globalThis, .{
.ConnectionClosed = this.request_id,
});
must_bust_cache = true;
}
}
// Case 1:
// User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
// but we received nothing or the connection was aborted
@@ -1988,11 +2043,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
// 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();
if (this.endRequestStreaming()) {
must_bust_cache = true;
}
if (this.byte_stream) |stream| {
ctxLog("finalizeWithoutDeinit: stream != null", .{});
must_bust_cache = true;
this.byte_stream = null;
stream.unpipeWithoutDeref();
}
@@ -2003,6 +2060,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.pathname.deref();
this.pathname = bun.String.empty;
}
if (must_bust_cache) {
this.flags.must_bust_abort_reason_cache_to_prevent_leaking_error_instance = false;
JSC.CommonAbortReason.Cacheable.bust(.{ .ConnectionClosed = this.request_id }, globalThis);
}
}
fn writeHeaders(
@@ -2306,9 +2368,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
if (comptime can_sendfile) {
return this.renderSendFile(blob);
}
if(this.server) |server| {
this.ref();
this.blob.Blob.doReadFileInternal(*RequestContext, this, onReadFile, server.globalThis);
if (this.server) |server| {
this.ref();
this.blob.Blob.doReadFileInternal(*RequestContext, this, onReadFile, server.globalThis);
}
}
@@ -2320,7 +2382,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
if (result == .err) {
if(this.server) |server| {
if (this.server) |server| {
this.runErrorHandler(result.err.toErrorInstance(server.globalThis));
}
return;
@@ -2475,7 +2537,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
streamLog("returned a promise", .{});
this.drainMicrotasks();
switch (promise.status(globalThis.vm())) {
.Pending => {
streamLog("promise still Pending", .{});
@@ -2574,10 +2635,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
fn toAsyncWithoutAbortHandler(ctx: *RequestContext, req: *uws.Request, request_object: *Request) void {
const id = ctx.getRequestID();
request_object.request_context.setRequest(req);
assert(ctx.server != null);
request_object.ensureURL() catch {
request_object.url = bun.String.empty;
};
@@ -2589,7 +2650,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
// This object dies after the stack frame is popped
// so we have to clear it in here too
request_object.request_context = JSC.API.AnyRequestContext.Null;
request_object.request_context.setRequestID(id);
}
pub fn getRequestID(this: *const RequestContext) JSC.CommonAbortReason.Cacheable.ID {
return this.request_id;
}
fn toAsync(
@@ -2608,19 +2673,19 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
fn endRequestStreamingAndDrain(this: *RequestContext) void {
assert(this.server != null);
if(this.endRequestStreaming()) {
if (this.endRequestStreaming()) {
this.server.?.vm.drainMicrotasks();
}
}
fn endRequestStreaming(this: *RequestContext) bool {
assert(this.server != null);
// if we cannot, we have to reject pending promises
// first, we reject the request body promise
if (this.request_body) |body| {
// if we cannot, we have to reject pending promises
// first, we reject the request body promise
if (this.request_body) |body| {
// 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(.{ .Aborted = {} }, this.server.?.globalThis);
body.value.toErrorInstance(.{ .ConnectionClosed = this.request_id }, this.server.?.globalThis);
return true;
}
}
@@ -2629,7 +2694,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
fn detachResponse(this: *RequestContext) void {
if (this.resp) |resp| {
this.resp = null;
if (this.flags.is_waiting_for_request_body) {
this.flags.is_waiting_for_request_body = false;
resp.clearOnData();
@@ -2807,8 +2872,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
resp.body.value = .{ .Used = {} };
}
}
if(req.isAbortedOrEnded()) {
if (req.isAbortedOrEnded()) {
return;
}
@@ -2883,7 +2948,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
req.endStream(true);
if (comptime debug_mode) {
if(req.server) |server| {
if (req.server) |server| {
if (!err.isEmptyOrUndefinedOrNull()) {
var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(req.allocator);
defer exception_list.deinit();
@@ -3148,7 +3213,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
fn finishRunningErrorHandler(this: *RequestContext, value: JSC.JSValue, status: u16) void {
if(this.server == null) return this.renderProductionError(status);
if (this.server == null) return this.renderProductionError(status);
var vm: *JSC.VirtualMachine = this.server.?.vm;
const globalThis = this.server.?.globalThis;
if (comptime debug_mode) {
@@ -3182,7 +3247,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
status: u16,
) void {
JSC.markBinding(@src());
if(this.server) |server| {
if (this.server) |server| {
if (!server.config.onError.isEmpty() and !this.flags.has_called_error_handler) {
this.flags.has_called_error_handler = true;
const result = server.config.onError.call(
@@ -3565,7 +3630,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
}
const max_request_body_preallocate_length = 1024 * 256;
pub fn onStartBuffering(this: *RequestContext) void {
if(this.server) |server| {
if (this.server) |server| {
ctxLog("onStartBuffering", .{});
// TODO: check if is someone calling onStartBuffering other than onStartBufferingCallback
// if is not, this should be removed and only keep protect + setAbortHandler
@@ -5302,6 +5367,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
pub const doReload = onReload;
pub const doFetch = onFetch;
pub const doRequestIP = JSC.wrapInstanceMethod(ThisServer, "requestIP", false);
pub const doRequestID = JSC.wrapInstanceMethod(ThisServer, "requestID", false);
pub usingnamespace NamespaceType;
pub usingnamespace bun.New(@This());
@@ -5328,6 +5394,14 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
JSValue.jsNull();
}
pub fn requestID(_: *ThisServer, request: *JSC.WebCore.Request) JSC.JSValue {
if (request.request_context.getRequestID()) |id| {
return JSValue.jsNumber(id);
}
return JSValue.jsNull();
}
pub fn publish(this: *ThisServer, globalThis: *JSC.JSGlobalObject, topic: ZigString, message_value: JSValue, compress_value: ?JSValue, exception: JSC.C.ExceptionRef) JSValue {
if (this.config.websocket == null)
return JSValue.jsNumber(0);
@@ -5511,7 +5585,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
// obviously invalid pointer marks it as used
upgrader.upgrade_context = @as(*uws.uws_socket_context_s, @ptrFromInt(std.math.maxInt(usize)));
// set the abort handler so we can receive onAbort to deref the context
upgrader.setAbortHandler();
upgrader.setAbortHandler();
// after upgrading we should not use the response anymore
upgrader.resp = null;
request.upgrader = null;
@@ -6321,9 +6395,10 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
request_value.ensureStillAlive();
const response_value = this.config.onRequest.call(this.globalThis, this.thisObject, &args);
const id = ctx.getRequestID();
defer {
// uWS request will not live longer than this function
request_object.request_context = JSC.API.AnyRequestContext.Null;
request_object.request_context.setRequestID(id);
}
const original_state = ctx.defer_deinit_until_callback_completes;
var should_deinit_context = false;
@@ -6338,7 +6413,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
ctx.defer_deinit_until_callback_completes = original_state;
if (should_deinit_context) {
request_object.request_context = JSC.API.AnyRequestContext.Null;
request_object.request_context.setRequestID(id);
ctx.deinit();
return;
}
@@ -6387,11 +6462,11 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
const request_value = args[0];
request_value.ensureStillAlive();
const response_value = this.config.onRequest.call(this.globalThis, this.thisObject, &args);
const id = ctx.getRequestID();
defer {
// uWS request will not live longer than this function
request_object.request_context = JSC.API.AnyRequestContext.Null;
request_object.request_context.setRequestID(id);
}
const original_state = ctx.defer_deinit_until_callback_completes;
var should_deinit_context = false;
ctx.defer_deinit_until_callback_completes = &should_deinit_context;
@@ -6405,7 +6480,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
ctx.defer_deinit_until_callback_completes = original_state;
if (should_deinit_context) {
request_object.request_context = JSC.API.AnyRequestContext.Null;
request_object.request_context.setRequestID(id);
ctx.deinit();
return;
}

View File

@@ -1,29 +1,178 @@
#include "root.h"
#include "JavaScriptCore/Error.h"
#include "JavaScriptCore/ErrorType.h"
#include "JavaScriptCore/ObjectConstructor.h"
#include "JavaScriptCore/WriteBarrier.h"
#include "root.h"
#include "headers-handwritten.h"
#include "BunClientData.h"
#include "helpers.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "JavaScriptCore/ErrorInstance.h"
#include "JavaScriptCore/ExceptionScope.h"
#include "JavaScriptCore/JSString.h"
#include "JavaScriptCore/JSType.h"
#include "JavaScriptCore/Symbol.h"
#include "wtf/text/ASCIILiteral.h"
#include "wtf/text/MakeString.h"
#include "wtf/text/WTFString.h"
#include <cstdio>
#include "AbortSignal.h"
#include "JavaScriptCore/ErrorInstanceInlines.h"
#include "JavaScriptCore/JSInternalFieldObjectImplInlines.h"
JSC::EncodedJSValue JSC__JSValue__createTypeError(const ZigString* message, const ZigString* arg1, JSC::JSGlobalObject* globalObject);
JSC::EncodedJSValue JSC__JSValue__createRangeError(const ZigString* message, const ZigString* arg1, JSC::JSGlobalObject* globalObject);
#include "NodeError.h"
static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code)
{
JSC::JSObject* prototype;
switch (type) {
case JSC::ErrorType::TypeError:
prototype = JSC::constructEmptyObject(globalObject, globalObject->m_typeErrorStructure.prototype(globalObject));
break;
case JSC::ErrorType::RangeError:
prototype = JSC::constructEmptyObject(globalObject, globalObject->m_rangeErrorStructure.prototype(globalObject));
break;
case JSC::ErrorType::Error:
prototype = JSC::constructEmptyObject(globalObject, globalObject->errorPrototype());
break;
default: {
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO: Add support for more error types");
break;
}
}
prototype->putDirect(vm, vm.propertyNames->name, jsString(vm, String(name)), 0);
prototype->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), jsString(vm, String(code)), 0);
return prototype;
}
static JSC::Structure* createErrorStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code)
{
JSC::JSObject* prototype = createErrorPrototype(vm, globalObject, type, name, code);
return JSC::ErrorInstance::createStructure(vm, globalObject, prototype);
}
extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue val_arg_name, JSC::EncodedJSValue val_expected_type, JSC::EncodedJSValue val_actual_value);
extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3);
extern "C" JSC::EncodedJSValue Bun__ERR_IPC_CHANNEL_CLOSED(JSC::JSGlobalObject* globalObject);
// clang-format on
namespace Bun {
using namespace JSC;
struct NodeErrorData {
JSC::ErrorType type;
WTF::ASCIILiteral name;
WTF::ASCIILiteral code;
};
static constexpr NodeErrorData errors[NODE_ERROR_COUNT] = {
#define DECLARE_ERROR_WITH_CODE_ENUM(E, name, code) { JSC::ErrorType::E, #name ""_s, #code ""_s },
FOR_EACH_NODE_ERROR_WITH_CODE(DECLARE_ERROR_WITH_CODE_ENUM)
#undef DECLARE_ERROR_WITH_CODE_ENUM
};
const ClassInfo NodeErrorCache::s_info = { "NodeErrorCache"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeErrorCache) };
NodeErrorCache::NodeErrorCache(VM& vm, Structure* structure)
: Base(vm, structure)
{
}
template<typename Visitor>
void NodeErrorCache::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<NodeErrorCache*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_cachedReason);
}
DEFINE_VISIT_CHILDREN_WITH_MODIFIER(JS_EXPORT_PRIVATE, NodeErrorCache);
Structure* NodeErrorCache::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
return Structure::create(vm, globalObject, jsNull(), TypeInfo(InternalFieldTupleType, StructureFlags), info(), 0, 0);
}
NodeErrorCache* NodeErrorCache::create(VM& vm, Structure* structure)
{
NodeErrorCache* object = new (NotNull, allocateCell<NodeErrorCache>(vm)) NodeErrorCache(vm, structure);
object->finishCreation(vm);
return object;
}
void NodeErrorCache::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
for (unsigned i = 0; i < NODE_ERROR_COUNT; i++) {
this->internalField(i).clear();
}
this->m_cachedReason.clear();
}
static NodeErrorCache* errorCache(Zig::GlobalObject* globalObject)
{
return static_cast<NodeErrorCache*>(globalObject->nodeErrorCache());
}
// clang-format on
static Structure* createErrorStructure(JSC::VM& vm, JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code)
{
auto* prototype = createErrorPrototype(vm, globalObject, type, name, code);
return ErrorInstance::createStructure(vm, globalObject, prototype);
}
JSObject* NodeErrorCache::createError(VM& vm, Zig::GlobalObject* globalObject, NodeErrorCode code, JSValue message, JSValue options)
{
auto* cache = errorCache(globalObject);
if (!cache->internalField(static_cast<unsigned>(code))) {
const auto& data = errors[code];
auto* structure = createErrorStructure(vm, globalObject, data.type, data.name, data.code);
cache->internalField(static_cast<unsigned>(code)).set(vm, cache, structure);
}
auto* structure = jsCast<Structure*>(cache->internalField(static_cast<unsigned>(code)).get());
return JSC::ErrorInstance::create(globalObject, structure, message, options, nullptr, JSC::RuntimeType::TypeNothing, errors[code].type, true);
}
JSObject* createError(VM& vm, Zig::GlobalObject* globalObject, NodeErrorCode code, const String& message)
{
return errorCache(globalObject)->createError(vm, globalObject, code, jsString(vm, message), jsUndefined());
}
JSObject* createError(VM& vm, JSC::JSGlobalObject* globalObject, NodeErrorCode code, JSValue message)
{
if (auto* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject))
return createError(vm, zigGlobalObject, code, message, jsUndefined());
auto* structure = createErrorStructure(vm, globalObject, errors[code].type, errors[code].name, errors[code].code);
return JSC::ErrorInstance::create(globalObject, structure, message, jsUndefined(), nullptr, JSC::RuntimeType::TypeNothing, errors[code].type, true);
}
JSC::JSObject* createError(VM& vm, Zig::GlobalObject* globalObject, NodeErrorCode code, JSValue message, JSValue options)
{
return errorCache(globalObject)->createError(vm, globalObject, code, message, options);
}
JSObject* createError(JSC::JSGlobalObject* globalObject, NodeErrorCode code, const String& message)
{
auto& vm = globalObject->vm();
return createError(vm, globalObject, code, jsString(vm, message));
}
JSObject* createError(Zig::JSGlobalObject* globalObject, NodeErrorCode code, JSC::JSValue message)
{
auto& vm = globalObject->vm();
return createError(vm, globalObject, code, message);
}
WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg)
{
ASSERT(!arg.isEmpty());
@@ -45,48 +194,6 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg)
return arg.toString(globalObject)->getString(globalObject);
}
JSC::JSValue createErrorWithCode(JSC::JSGlobalObject* globalObject, String message, ASCIILiteral code)
{
JSC::VM& vm = globalObject->vm();
JSC::JSObject* result = JSC::createError(globalObject, message);
JSC::EnsureStillAliveScope ensureAlive(result);
auto typeError = JSC::JSValue(result).asCell()->getObject();
auto clientData = WebCore::clientData(vm);
typeError->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, String(code)), 0);
return typeError;
}
JSC::JSValue createTypeErrorWithCode(JSC::JSGlobalObject* globalObject, String message, ASCIILiteral code)
{
JSC::VM& vm = globalObject->vm();
JSC::JSObject* result = JSC::createTypeError(globalObject, message);
JSC::EnsureStillAliveScope ensureAlive(result);
auto typeError = JSC::JSValue(result).asCell()->getObject();
auto clientData = WebCore::clientData(vm);
typeError->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, String(code)), 0);
return typeError;
}
JSC::JSValue createRangeErrorWithCode(JSC::JSGlobalObject* globalObject, String message, ASCIILiteral code)
{
JSC::VM& vm = globalObject->vm();
JSC::JSObject* result = JSC::createRangeError(globalObject, message);
JSC::EnsureStillAliveScope ensureAlive(result);
auto typeError = JSC::JSValue(result).asCell()->getObject();
auto clientData = WebCore::clientData(vm);
typeError->putDirect(vm, clientData->builtinNames().codePublicName(), jsString(vm, String(code)), 0);
return typeError;
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_ARG_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
JSC::VM& vm = globalObject->vm();
@@ -117,7 +224,7 @@ extern "C" JSC::EncodedJSValue Bun__ERR_INVALID_ARG_TYPE(JSC::JSGlobalObject* gl
RETURN_IF_EXCEPTION(scope, {});
auto message = makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value);
return JSC::JSValue::encode(createTypeErrorWithCode(globalObject, message, "ERR_INVALID_ARG_TYPE"_s));
return JSValue::encode(createError(globalObject, NodeErrorCode::ERR_INVALID_ARG_TYPE, message));
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_OUT_OF_RANGE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
@@ -141,17 +248,17 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_OUT_OF_RANGE, (JSC::JSGlobalObject * glo
RETURN_IF_EXCEPTION(scope, {});
auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, range, ". Received "_s, input);
return JSC::JSValue::encode(createRangeErrorWithCode(globalObject, message, "ERR_OUT_OF_RANGE"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_OUT_OF_RANGE, message));
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_DISCONNECTED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*))
{
return JSC::JSValue::encode(createErrorWithCode(globalObject, "IPC channel is already disconnected"_s, "ERR_IPC_DISCONNECTED"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s));
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SERVER_NOT_RUNNING, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*))
{
return JSC::JSValue::encode(createErrorWithCode(globalObject, "Server is not running."_s, "ERR_SERVER_NOT_RUNNING"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_SERVER_NOT_RUNNING, "Server is not running."_s));
}
extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3)
@@ -170,7 +277,7 @@ extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* global
if (arg2 == 0) {
// 1 arg name passed
auto message = makeString("The \""_s, name1, "\" argument must be specified"_s);
return JSC::JSValue::encode(createTypeErrorWithCode(globalObject, message, "ERR_MISSING_ARGS"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_MISSING_ARGS, message));
}
auto name2 = JSValue::decode(arg2).toWTFString(globalObject);
@@ -179,7 +286,7 @@ extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* global
if (arg3 == 0) {
// 2 arg names passed
auto message = makeString("The \""_s, name1, "\" and \""_s, name2, "\" arguments must be specified"_s);
return JSC::JSValue::encode(createTypeErrorWithCode(globalObject, message, "ERR_MISSING_ARGS"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_MISSING_ARGS, message));
}
auto name3 = JSValue::decode(arg3).toWTFString(globalObject);
@@ -187,7 +294,7 @@ extern "C" JSC::EncodedJSValue Bun__ERR_MISSING_ARGS(JSC::JSGlobalObject* global
// 3 arg names passed
auto message = makeString("The \""_s, name1, "\", \""_s, name2, "\", and \""_s, name3, "\" arguments must be specified"_s);
return JSC::JSValue::encode(createTypeErrorWithCode(globalObject, message, "ERR_MISSING_ARGS"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_MISSING_ARGS, message));
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*))
@@ -196,12 +303,78 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED, (JSC::JSGlobalObject
}
extern "C" JSC::EncodedJSValue Bun__ERR_IPC_CHANNEL_CLOSED(JSC::JSGlobalObject* globalObject)
{
return JSC::JSValue::encode(createErrorWithCode(globalObject, "Channel closed."_s, "ERR_IPC_CHANNEL_CLOSED"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_IPC_CHANNEL_CLOSED, "Channel closed."_s));
}
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*))
{
return JSC::JSValue::encode(createTypeErrorWithCode(globalObject, "Bad socket type specified. Valid types are: udp4, udp6"_s, "ERR_SOCKET_BAD_TYPE"_s));
return JSC::JSValue::encode(createError(globalObject, NodeErrorCode::ERR_SOCKET_BAD_TYPE, "Bad socket type specified. Valid types are: udp4, udp6"_s));
}
} // namespace Bun
JSC::JSValue WebCore::toJS(JSC::JSGlobalObject* globalObject, CacheableAbortReason abortReason)
{
if (auto* zigGlobal = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
auto* cache = Bun::errorCache(zigGlobal);
if (abortReason.shouldCache()) {
if (cache->m_cacheableAbortReason.m_identifier == abortReason.identifier() && cache->m_cacheableAbortReason.m_reason == abortReason.reason()) {
return cache->m_cachedReason.get();
}
}
JSValue reason = toJS(globalObject, abortReason.reason());
if (abortReason.shouldCache()) {
auto& vm = globalObject->vm();
cache->m_cacheableAbortReason = abortReason;
cache->m_cachedReason.set(vm, cache, reason);
}
return reason;
}
return toJS(globalObject, abortReason.reason());
}
JSC::JSValue WebCore::toJS(JSC::JSGlobalObject* globalObject, CommonAbortReason abortReason)
{
switch (abortReason) {
case CommonAbortReason::Timeout: {
return createError(globalObject, Bun::NodeErrorCode::ABORT_ERR, "The operation timed out"_s);
}
case CommonAbortReason::UserAbort: {
return createError(globalObject, Bun::NodeErrorCode::ABORT_ERR, "The operation was aborted by the user"_s);
}
case CommonAbortReason::ConnectionClosed: {
return createError(globalObject, Bun::NodeErrorCode::ABORT_ERR, "The connection was closed"_s);
}
default: {
break;
}
}
RELEASE_ASSERT_NOT_REACHED();
}
extern "C" JSC::EncodedJSValue WebCore__CommonAbortReason__toJS(JSC::JSGlobalObject* globalObject, WebCore::CommonAbortReason abortReason)
{
return JSC::JSValue::encode(WebCore::toJS(globalObject, abortReason));
}
extern "C" JSC::EncodedJSValue WebCore__CommonAbortReason__toJSCached(JSC::JSGlobalObject* globalObject, WebCore::CommonAbortReason abortReason, size_t id)
{
return JSC::JSValue::encode(WebCore::toJS(globalObject, { id, abortReason }));
}
extern "C" void WebCore__CommonAbortReason__bustCached(JSC::JSGlobalObject* globalObject, WebCore::CommonAbortReason abortReason, size_t id)
{
if (auto* zigGlobal = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
if (zigGlobal->m_nodeErrorCache.isInitialized()) {
auto* cache = Bun::errorCache(zigGlobal);
if (cache->m_cacheableAbortReason.m_identifier == id) {
cache->m_cacheableAbortReason = { 0, WebCore::CommonAbortReason::None };
cache->m_cachedReason.clear();
}
}
}
}

View File

@@ -1,7 +1,85 @@
#pragma once
#include "AbortSignal.h"
#include "JavaScriptCore/WriteBarrier.h"
#include "ZigGlobalObject.h"
#include "root.h"
#include <JavaScriptCore/JSInternalFieldObjectImpl.h>
#include <JavaScriptCore/JSInternalFieldObjectImplInlines.h>
#include "BunClientData.h"
namespace Bun {
// clang-format off
#define FOR_EACH_NODE_ERROR_WITH_CODE(macro) \
macro(TypeError, TypeError, ERR_INVALID_ARG_TYPE) \
macro(RangeError, RangeError, ERR_OUT_OF_RANGE) \
macro(Error, Error, ERR_IPC_DISCONNECTED) \
macro(Error, Error, ERR_SERVER_NOT_RUNNING) \
macro(TypeError, TypeError, ERR_MISSING_ARGS) \
macro(Error, Error, ERR_IPC_CHANNEL_CLOSED) \
macro(TypeError, TypeError, ERR_SOCKET_BAD_TYPE) \
macro(Error, AbortError, ABORT_ERR)
#define COUNT_ERROR_WITH_CODE_ENUM(E, name, code) +1
static constexpr size_t NODE_ERROR_COUNT = 0 FOR_EACH_NODE_ERROR_WITH_CODE(COUNT_ERROR_WITH_CODE_ENUM);
#undef COUNT_ERROR_WITH_CODE_ENUM
using namespace JSC;
// clang-format on
enum NodeErrorCode : uint8_t {
#define DECLARE_ERROR_WITH_CODE_ENUM(E, name, code) code,
FOR_EACH_NODE_ERROR_WITH_CODE(DECLARE_ERROR_WITH_CODE_ENUM)
#undef DECLARE_ERROR_WITH_CODE_ENUM
};
class NodeErrorCache : public JSC::JSInternalFieldObjectImpl<NODE_ERROR_COUNT> {
public:
using Base = JSInternalFieldObjectImpl<NODE_ERROR_COUNT>;
using Field = NodeErrorCode;
DECLARE_EXPORT_INFO;
static size_t allocationSize(Checked<size_t> inlineCapacity)
{
ASSERT_UNUSED(inlineCapacity, inlineCapacity == 0U);
return sizeof(NodeErrorCache);
}
template<typename, SubspaceAccess mode>
static GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<NodeErrorCache, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForNodeErrors.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeErrors = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForNodeErrors.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForNodeErrors = std::forward<decltype(space)>(space); });
}
static NodeErrorCache* create(VM& vm, Structure* structure);
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject);
JSObject* createError(VM& vm, Zig::GlobalObject* globalObject, NodeErrorCode code, JSValue message, JSValue options);
CacheableAbortReason m_cacheableAbortReason { 0, CommonAbortReason::None };
mutable WriteBarrier<Unknown> m_cachedReason;
private:
JS_EXPORT_PRIVATE NodeErrorCache(VM&, Structure*);
DECLARE_VISIT_CHILDREN_WITH_MODIFIER(JS_EXPORT_PRIVATE);
void finishCreation(VM&);
};
JSC::JSObject* createError(Zig::GlobalObject* globalObject, NodeErrorCode code, const WTF::String& message);
JSC::JSObject* createError(JSC::JSGlobalObject* globalObject, NodeErrorCode code, const WTF::String& message);
JSC::JSObject* createError(Zig::GlobalObject* globalObject, NodeErrorCode code, JSC::JSValue message);
JSC::JSObject* createError(VM& vm, Zig::GlobalObject* globalObject, NodeErrorCode code, JSValue message, JSValue options = jsUndefined());
JSC::JSValue toJS(JSC::JSGlobalObject*, NodeErrorCode);
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_ARG_TYPE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame));
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_OUT_OF_RANGE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame));
JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_IPC_DISCONNECTED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame*));

View File

@@ -146,6 +146,7 @@
#include "UtilInspect.h"
#include "Base64Helpers.h"
#include "wtf/text/OrdinalNumber.h"
#include "NodeError.h"
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
@@ -2695,6 +2696,15 @@ void GlobalObject::finishCreation(VM& vm)
init.set(Bun::createUtilInspectOptionsStructure(init.vm, init.owner));
});
m_nodeErrorCache.initLater(
[](const Initializer<JSObject>& init) {
auto* structure = NodeErrorCache::createStructure(
init.vm,
init.owner);
init.set(NodeErrorCache::create(init.vm, structure));
});
m_utilInspectStylizeColorFunction.initLater(
[](const Initializer<JSFunction>& init) {
JSC::MarkedArgumentBuffer args;
@@ -3553,6 +3563,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->mockModule.mockWithImplementationCleanupDataStructure.visit(visitor);
thisObject->mockModule.withImplementationCleanupFunction.visit(visitor);
thisObject->m_nodeErrorCache.visit(visitor);
for (auto& barrier : thisObject->m_thenables) {
visitor.append(barrier);
}

View File

@@ -402,6 +402,9 @@ public:
//
LazyProperty<JSGlobalObject, JSC::JSFunction> m_errorConstructorPrepareStackTraceInternalValue;
LazyProperty<JSGlobalObject, JSObject> m_nodeErrorCache;
JSObject* nodeErrorCache() const { return m_nodeErrorCache.getInitializedOnMainThread(this); }
Structure* memoryFootprintStructure()
{
return m_memoryFootprintStructure.getInitializedOnMainThread(this);

View File

@@ -1,3 +1,4 @@
#include "AbortSignal.h"
#include "root.h"
#include "JavaScriptCore/JSCast.h"
#include "JavaScriptCore/JSType.h"
@@ -5484,11 +5485,14 @@ extern "C" JSC__JSValue WebCore__AbortSignal__toJS(WebCore__AbortSignal* arg0, J
return JSValue::encode(toJS<IDLInterface<WebCore__AbortSignal>>(*globalObject, *jsCast<JSDOMGlobalObject*>(globalObject), *abortSignal));
}
extern "C" WebCore__AbortSignal* WebCore__AbortSignal__signal(WebCore__AbortSignal* arg0, JSC__JSValue JSValue1)
extern "C" WebCore__AbortSignal* WebCore__AbortSignal__signal(WebCore__AbortSignal* arg0, JSC::JSGlobalObject* globalObject, WebCore::CommonAbortReason reason, uint32_t id)
{
WebCore::AbortSignal* abortSignal = reinterpret_cast<WebCore::AbortSignal*>(arg0);
abortSignal->signalAbort(JSC::JSValue::decode(JSValue1));
abortSignal->signalAbort(
globalObject,
{ id, reason });
;
return arg0;
}
@@ -5547,49 +5551,6 @@ extern "C" WebCore__AbortSignal* WebCore__AbortSignal__fromJS(JSC__JSValue value
return reinterpret_cast<WebCore__AbortSignal*>(&object->wrapped());
}
static auto ABORT_ERROR_NAME = MAKE_STATIC_STRING_IMPL("AbortError");
extern "C" JSC__JSValue WebCore__AbortSignal__createAbortError(const ZigString* message, const ZigString* arg1,
JSC__JSGlobalObject* globalObject)
{
JSC::VM& vm = globalObject->vm();
ZigString code = *arg1;
JSC::JSObject* error = Zig::getErrorInstance(message, globalObject).asCell()->getObject();
error->putDirect(
vm, vm.propertyNames->name,
JSC::JSValue(JSC::jsOwnedString(vm, ABORT_ERROR_NAME)),
0);
if (code.len > 0) {
auto clientData = WebCore::clientData(vm);
JSC::JSValue codeValue = Zig::toJSStringValue(code, globalObject);
error->putDirect(vm, clientData->builtinNames().codePublicName(), codeValue, 0);
}
return JSC::JSValue::encode(error);
}
static auto TIMEOUT_ERROR_NAME = MAKE_STATIC_STRING_IMPL("TimeoutError");
extern "C" JSC__JSValue WebCore__AbortSignal__createTimeoutError(const ZigString* message, const ZigString* arg1,
JSC__JSGlobalObject* globalObject)
{
JSC::VM& vm = globalObject->vm();
ZigString code = *arg1;
JSC::JSObject* error = Zig::getErrorInstance(message, globalObject).asCell()->getObject();
error->putDirect(
vm, vm.propertyNames->name,
JSC::JSValue(JSC::jsOwnedString(vm, TIMEOUT_ERROR_NAME)),
0);
if (code.len > 0) {
auto clientData = WebCore::clientData(vm);
JSC::JSValue codeValue = Zig::toJSStringValue(code, globalObject);
error->putDirect(vm, clientData->builtinNames().codePublicName(), codeValue, 0);
}
return JSC::JSValue::encode(error);
}
CPP_DECL double JSC__JSValue__getUnixTimestamp(JSC__JSValue timeValue)
{

View File

@@ -2003,6 +2003,45 @@ pub fn PromiseCallback(comptime Type: type, comptime CallbackFunction: fn (*Type
}.callback;
}
// These objects are extremely common.
pub const CommonAbortReason = enum(u8) {
Timeout = 1,
UserAbort = 2,
ConnectionClosed = 3,
pub fn toJS(this: CommonAbortReason, global: *JSGlobalObject) JSValue {
return WebCore__CommonAbortReason__toJS(global, this);
}
pub const Cacheable = union(CommonAbortReason) {
Timeout: ID,
UserAbort: ID,
ConnectionClosed: ID,
pub const ID = usize;
// Using the u48 size is generally pretty slow and won't work across C ABI.
pub const SizedID = u48;
pub const max_id = std.math.maxInt(SizedID) - 1;
pub fn toJS(this: Cacheable, global: *JSGlobalObject) JSValue {
return switch (this) {
inline else => |x| WebCore__CommonAbortReason__toJSCached(global, this, x),
};
}
pub fn bust(this: Cacheable, globalObject: *JSC.JSGlobalObject) void {
switch (this) {
inline else => |x| WebCore__CommonAbortReason__bustCached(globalObject, this, x),
}
}
};
extern fn WebCore__CommonAbortReason__toJS(*JSGlobalObject, CommonAbortReason) JSValue;
extern fn WebCore__CommonAbortReason__toJSCached(*JSGlobalObject, CommonAbortReason, Cacheable.ID) JSValue;
extern fn WebCore__CommonAbortReason__bustCached(*JSGlobalObject, CommonAbortReason, Cacheable.ID) void;
};
pub const AbortSignal = extern opaque {
pub const shim = Shimmer("WebCore", "AbortSignal", @This());
const cppFn = shim.cppFn;
@@ -2042,12 +2081,22 @@ pub const AbortSignal = extern opaque {
return cppFn("cleanNativeBindings", .{ this, ctx });
}
extern fn WebCore__AbortSignal__signal(*AbortSignal, *JSC.JSGlobalObject, CommonAbortReason, CommonAbortReason.Cacheable.ID) void;
pub fn signal(
this: *AbortSignal,
reason: JSValue,
) *AbortSignal {
globalObject: *JSC.JSGlobalObject,
reason: CommonAbortReason.Cacheable,
) void {
bun.Analytics.Features.abort_signal += 1;
return cppFn("signal", .{ this, reason });
return WebCore__AbortSignal__signal(
this,
globalObject,
reason,
switch (reason) {
inline else => |x| x,
},
);
}
/// This function is not threadsafe. aborted is a boolean, not an atomic!
@@ -2095,15 +2144,7 @@ pub const AbortSignal = extern opaque {
return WebCore__AbortSignal__new(global);
}
pub fn createAbortError(message: *const ZigString, code: *const ZigString, global: *JSGlobalObject) JSValue {
return cppFn("createAbortError", .{ message, code, global });
}
pub fn createTimeoutError(message: *const ZigString, code: *const ZigString, global: *JSGlobalObject) JSValue {
return cppFn("createTimeoutError", .{ message, code, global });
}
pub const Extern = [_][]const u8{ "createAbortError", "createTimeoutError", "create", "ref", "unref", "signal", "abortReason", "aborted", "addListener", "fromJS", "toJS", "cleanNativeBindings" };
pub const Extern = [_][]const u8{ "create", "ref", "unref", "signal", "abortReason", "aborted", "addListener", "fromJS", "toJS", "cleanNativeBindings" };
};
pub const JSPromise = extern struct {

View File

@@ -231,11 +231,8 @@ CPP_DECL JSC__JSValue WebCore__AbortSignal__abortReason(WebCore__AbortSignal* ar
CPP_DECL WebCore__AbortSignal* WebCore__AbortSignal__addListener(WebCore__AbortSignal* arg0, void* arg1, void(* ArgFn2)(void* arg0, JSC__JSValue JSValue1));
CPP_DECL void WebCore__AbortSignal__cleanNativeBindings(WebCore__AbortSignal* arg0, void* arg1);
CPP_DECL JSC__JSValue WebCore__AbortSignal__create(JSC__JSGlobalObject* arg0);
CPP_DECL JSC__JSValue WebCore__AbortSignal__createAbortError(const ZigString* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
CPP_DECL JSC__JSValue WebCore__AbortSignal__createTimeoutError(const ZigString* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
CPP_DECL WebCore__AbortSignal* WebCore__AbortSignal__fromJS(JSC__JSValue JSValue0);
CPP_DECL WebCore__AbortSignal* WebCore__AbortSignal__ref(WebCore__AbortSignal* arg0);
CPP_DECL WebCore__AbortSignal* WebCore__AbortSignal__signal(WebCore__AbortSignal* arg0, JSC__JSValue JSValue1);
CPP_DECL JSC__JSValue WebCore__AbortSignal__toJS(WebCore__AbortSignal* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL void WebCore__AbortSignal__unref(WebCore__AbortSignal* arg0);

View File

@@ -143,11 +143,8 @@ pub extern fn WebCore__AbortSignal__abortReason(arg0: ?*bindings.AbortSignal) JS
pub extern fn WebCore__AbortSignal__addListener(arg0: ?*bindings.AbortSignal, arg1: ?*anyopaque, ArgFn2: ?*const fn (?*anyopaque, JSC__JSValue) callconv(.C) void) ?*bindings.AbortSignal;
pub extern fn WebCore__AbortSignal__cleanNativeBindings(arg0: ?*bindings.AbortSignal, arg1: ?*anyopaque) void;
pub extern fn WebCore__AbortSignal__create(arg0: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__AbortSignal__createAbortError(arg0: [*c]const ZigString, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__AbortSignal__createTimeoutError(arg0: [*c]const ZigString, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__AbortSignal__fromJS(JSValue0: JSC__JSValue) ?*bindings.AbortSignal;
pub extern fn WebCore__AbortSignal__ref(arg0: ?*bindings.AbortSignal) ?*bindings.AbortSignal;
pub extern fn WebCore__AbortSignal__signal(arg0: ?*bindings.AbortSignal, JSValue1: JSC__JSValue) ?*bindings.AbortSignal;
pub extern fn WebCore__AbortSignal__toJS(arg0: ?*bindings.AbortSignal, arg1: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__AbortSignal__unref(arg0: ?*bindings.AbortSignal) void;
pub extern fn JSC__JSPromise__asValue(arg0: ?*bindings.JSPromise, arg1: *bindings.JSGlobalObject) JSC__JSValue;

View File

@@ -32,6 +32,7 @@
#include "Event.h"
#include "EventNames.h"
#include "JSDOMException.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "ScriptExecutionContext.h"
#include "WebCoreOpaqueRoot.h"
#include "wtf/DebugHeap.h"
@@ -112,6 +113,20 @@ AbortSignal::AbortSignal(ScriptExecutionContext* context, Aborted aborted, JSC::
AbortSignal::~AbortSignal() = default;
JSValue AbortSignal::jsReason(JSC::JSGlobalObject& globalObject)
{
JSValue existingValue = m_reason.getValue(jsUndefined());
if (existingValue.isUndefined()) {
if (m_cacheableReason.reason() != CommonAbortReason::None) {
existingValue = toJS(&globalObject, m_cacheableReason);
m_cacheableReason = { 0, CommonAbortReason::None };
m_reason.setWeakly(existingValue);
}
}
return existingValue;
}
void AbortSignal::addSourceSignal(AbortSignal& signal)
{
if (signal.isDependent()) {
@@ -164,6 +179,26 @@ void AbortSignal::signalAbort(JSC::JSValue reason)
dependentSignal->signalAbort(reason);
}
void AbortSignal::signalAbort(JSC::JSGlobalObject* globalObject, CommonAbortReason reason)
{
// 1. If signal's aborted flag is set, then return.
if (m_aborted)
return;
m_cacheableReason = { 0, reason };
signalAbort(toJS(globalObject, reason));
}
void AbortSignal::signalAbort(JSC::JSGlobalObject* globalObject, CacheableAbortReason reason)
{
// 1. If signal's aborted flag is set, then return.
if (m_aborted)
return;
m_cacheableReason = reason;
signalAbort(toJS(globalObject, reason));
}
void AbortSignal::cleanNativeBindings(void* ref)
{
auto callbacks = std::exchange(m_native_callbacks, {});
@@ -183,7 +218,7 @@ void AbortSignal::signalFollow(AbortSignal& signal)
return;
if (signal.aborted()) {
signalAbort(signal.reason().getValue());
signalAbort(signal.jsReason(*scriptExecutionContext()->jsGlobalObject()));
return;
}
@@ -203,7 +238,8 @@ void AbortSignal::eventListenersDidChange()
uint32_t AbortSignal::addAbortAlgorithmToSignal(AbortSignal& signal, Ref<AbortAlgorithm>&& algorithm)
{
if (signal.aborted()) {
algorithm->handleEvent(signal.m_reason.getValue());
// TODO: Null check.
algorithm->handleEvent(signal.jsReason(*signal.scriptExecutionContext()->jsGlobalObject()));
return 0;
}
return signal.addAlgorithm([algorithm = WTFMove(algorithm)](JSC::JSValue value) mutable {

View File

@@ -30,6 +30,8 @@
#include "ContextDestructionObserver.h"
#include "EventTarget.h"
#include "JSValueInWrappedObject.h"
#include "JavaScriptCore/JSGlobalObject.h"
#include "ZigGlobalObject.h"
#include "wtf/DebugHeap.h"
#include "wtf/FastMalloc.h"
#include <wtf/Function.h>
@@ -46,6 +48,33 @@ class WebCoreOpaqueRoot;
DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(AbortSignal);
enum class CommonAbortReason : uint8_t {
None,
Timeout,
UserAbort,
ConnectionClosed,
};
class CacheableAbortReason {
public:
CacheableAbortReason() = default;
CacheableAbortReason(size_t identifier = 0, CommonAbortReason reason = CommonAbortReason::None)
: m_reason(reason)
, m_identifier(identifier)
{
}
size_t identifier() const { return m_identifier; }
CommonAbortReason reason() const { return m_reason; }
bool shouldCache() const { return m_identifier > 0; }
size_t m_identifier = 0;
CommonAbortReason m_reason { CommonAbortReason::None };
};
JSC::JSValue toJS(JSC::JSGlobalObject*, CommonAbortReason);
JSC::JSValue toJS(JSC::JSGlobalObject* globalObject, CacheableAbortReason abortReason);
class AbortSignal final : public RefCounted<AbortSignal>, public EventTargetWithInlineData, private ContextDestructionObserver {
WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(AbortSignal);
@@ -61,11 +90,14 @@ public:
static uint32_t addAbortAlgorithmToSignal(AbortSignal&, Ref<AbortAlgorithm>&&);
static void removeAbortAlgorithmFromSignal(AbortSignal&, uint32_t algorithmIdentifier);
void signalAbort(JSC::JSGlobalObject* globalObject, CacheableAbortReason reason);
void signalAbort(JSC::JSGlobalObject* globalObject, CommonAbortReason reason);
void signalAbort(JSC::JSValue reason);
void signalFollow(AbortSignal&);
bool aborted() const { return m_aborted; }
const JSValueInWrappedObject& reason() const { return m_reason; }
JSValue jsReason(JSC::JSGlobalObject& globalObject);
void cleanNativeBindings(void* ref);
void addNativeCallback(NativeCallbackTuple callback) { m_native_callbacks.append(callback); }
@@ -89,8 +121,10 @@ public:
AbortSignalSet& sourceSignals() { return m_sourceSignals; }
private:
enum class Aborted : bool { No,
Yes };
enum class Aborted : bool {
No,
Yes
};
explicit AbortSignal(ScriptExecutionContext*, Aborted = Aborted::No, JSC::JSValue reason = JSC::jsUndefined());
void setHasActiveTimeoutTimer(bool hasActiveTimeoutTimer) { m_hasActiveTimeoutTimer = hasActiveTimeoutTimer; }
@@ -112,12 +146,13 @@ private:
AbortSignalSet m_sourceSignals;
AbortSignalSet m_dependentSignals;
JSValueInWrappedObject m_reason;
CacheableAbortReason m_cacheableReason { 0, CommonAbortReason::None };
Vector<NativeCallbackTuple, 2> m_native_callbacks;
uint32_t m_algorithmIdentifier { 0 };
bool m_aborted { false };
bool m_hasActiveTimeoutTimer { false };
bool m_hasAbortEventListener { false };
bool m_isDependent { false };
bool m_aborted : 1 { false };
bool m_hasActiveTimeoutTimer : 1 { false };
bool m_hasAbortEventListener : 1 { false };
bool m_isDependent : 1 { false };
};
WebCoreOpaqueRoot root(AbortSignal*);

View File

@@ -44,6 +44,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMockWithImplementationCleanupData;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInternalModuleRegistry;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeErrors;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBunInspectorConnection;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNextTickQueue;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNAPIFunction;

View File

@@ -44,6 +44,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForMockWithImplementationCleanupData;
std::unique_ptr<IsoSubspace> m_subspaceForProcessObject;
std::unique_ptr<IsoSubspace> m_subspaceForInternalModuleRegistry;
std::unique_ptr<IsoSubspace> m_subspaceForNodeErrors;
std::unique_ptr<IsoSubspace> m_subspaceForBunInspectorConnection;
std::unique_ptr<IsoSubspace> m_subspaceForJSNextTickQueue;
std::unique_ptr<IsoSubspace> m_subspaceForNAPIFunction;

View File

@@ -225,7 +225,7 @@ static inline JSValue jsAbortSignal_reasonGetter(JSGlobalObject& lexicalGlobalOb
auto& vm = JSC::getVM(&lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = thisObject.wrapped();
RELEASE_AND_RETURN(throwScope, (toJS<IDLAny>(lexicalGlobalObject, throwScope, impl.reason())));
RELEASE_AND_RETURN(throwScope, (toJS<IDLAny>(lexicalGlobalObject, throwScope, impl.jsReason(lexicalGlobalObject))));
}
JSC_DEFINE_CUSTOM_GETTER(jsAbortSignal_reason, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))

View File

@@ -535,7 +535,7 @@ pub const FSWatcher = struct {
listener.ensureStillAlive();
var args = [_]JSC.JSValue{
EventType.@"error".toJS(this.globalThis),
if (err.isEmptyOrUndefinedOrNull()) JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, this.globalThis) else err,
JSC.CommonAbortReason.UserAbort.toJS(this.globalThis),
};
_ = listener.callWithGlobalThis(
this.globalThis,

View File

@@ -315,16 +315,35 @@ pub const Body = struct {
pub const heap_breakdown_label = "BodyValue";
pub const ValueError = union(enum) {
Aborted: void,
Timeout: void,
Aborted: JSC.CommonAbortReason.Cacheable.ID,
ConnectionClosed: JSC.CommonAbortReason.Cacheable.ID,
Timeout: JSC.CommonAbortReason.Cacheable.ID,
SystemError: JSC.SystemError,
Message: bun.String,
JSValue: JSC.Strong,
pub fn toStreamError(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.WebCore.StreamResult.StreamError {
return switch (this.*) {
.Aborted => |id| .{
.AbortReason = .{ .UserAbort = id },
},
.ConnectionClosed => |id| .{
.AbortReason = .{ .ConnectionClosed = id },
},
.Timeout => |id| .{
.AbortReason = .{ .Timeout = id },
},
else => .{
.JSValue = this.toJS(globalObject),
},
};
}
pub fn toJS(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue {
const js_value = switch (this.*) {
.Timeout => JSC.WebCore.AbortSignal.createTimeoutError(JSC.ZigString.static("The operation timed out"), &JSC.ZigString.Empty, globalObject),
.Aborted => JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, globalObject),
.Timeout => |id| JSC.CommonAbortReason.Cacheable.toJS(.{ .Timeout = id }, globalObject),
.ConnectionClosed => |id| JSC.CommonAbortReason.Cacheable.toJS(.{ .ConnectionClosed = id }, globalObject),
.Aborted => |id| JSC.CommonAbortReason.Cacheable.toJS(.{ .UserAbort = id }, globalObject),
.SystemError => |system_error| system_error.toErrorInstance(globalObject),
.Message => |message| message.toErrorInstance(globalObject),
// do a early return in this case we don't need to create a new Strong
@@ -345,7 +364,7 @@ pub const Body = struct {
}
return .{ .JSValue = .{} };
},
.Aborted, .Timeout => {},
.ConnectionClosed, .Aborted, .Timeout => {},
}
return value;
}
@@ -355,7 +374,7 @@ pub const Body = struct {
.SystemError => |system_error| system_error.deref(),
.Message => |message| message.deref(),
.JSValue => this.JSValue.deinit(),
.Aborted, .Timeout => {},
.ConnectionClosed, .Aborted, .Timeout => {},
}
// safe empty value after deinit
this.* = .{ .JSValue = .{} };
@@ -927,12 +946,12 @@ pub const Body = struct {
}
}
// The Promise version goes before the ReadableStream version incase the Promise version is used too.
// Avoid creating unnecessary duplicate JSValue.
if (strong_readable.get()) |readable| {
if (readable.ptr == .Bytes) {
readable.ptr.Bytes.onData(
.{
.err = .{ .JSValue = this.Error.toJS(global) },
},
.{ .err = this.Error.toStreamError(global) },
bun.default_allocator,
);
} else {

View File

@@ -1262,7 +1262,7 @@ pub const Fetch = struct {
if (signal.aborted()) {
this.abort_reason = signal.abortReason();
if (this.abort_reason.isEmptyOrUndefinedOrNull()) {
return JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, this.global_this);
return JSC.CommonAbortReason.UserAbort.toJS(this.global_this);
}
this.abort_reason.protect();
return this.abort_reason;
@@ -1281,12 +1281,12 @@ pub const Fetch = struct {
if (this.result.isTimeout()) {
// Timeout without reason
return .{ .Timeout = {} };
return .{ .Timeout = 0 };
}
if (this.result.isAbort()) {
// Abort without reason
return .{ .Aborted = {} };
return .{ .Aborted = 0 };
}
// some times we don't have metadata so we also check http.url

View File

@@ -660,7 +660,7 @@ pub const DrainResult = union(enum) {
pub const StreamResult = union(Tag) {
pending: *Pending,
err: union(Err) { Error: Syscall.Error, JSValue: JSC.JSValue },
err: StreamError,
done: void,
owned: bun.ByteList,
owned_and_done: bun.ByteList,
@@ -682,9 +682,32 @@ pub const StreamResult = union(Tag) {
}
}
pub const Err = enum {
Error,
JSValue,
pub const StreamError = union(enum) {
Error: Syscall.Error,
AbortReason: JSC.CommonAbortReason.Cacheable,
// TODO: use an explicit JSC.Strong here.
JSValue: JSC.JSValue,
WeakJSValue: JSC.JSValue,
const WasStrong = enum {
Strong,
Weak,
};
pub fn toJSWeak(this: *const @This(), globalObject: *JSC.JSGlobalObject) struct { JSC.JSValue, WasStrong } {
return switch (this.*) {
.Error => |err| {
return .{ err.toJSC(globalObject), WasStrong.Weak };
},
.JSValue => .{ this.JSValue, WasStrong.Strong },
.WeakJSValue => .{ this.WeakJSValue, WasStrong.Weak },
.AbortReason => |reason| {
const value = reason.toJS(globalObject);
return .{ value, WasStrong.Weak };
},
};
}
};
pub const Tag = enum {
@@ -943,13 +966,12 @@ pub const StreamResult = union(Tag) {
defer loop.exit();
switch (result.*) {
.err => |err| {
.err => |*err| {
const value = brk: {
if (err == .Error) break :brk err.Error.toJSC(globalThis);
const js_err = err.JSValue;
const js_err, const was_strong = err.toJSWeak(globalThis);
js_err.ensureStillAlive();
js_err.unprotect();
if (was_strong == .Strong)
js_err.unprotect();
break :brk js_err;
};
@@ -1010,12 +1032,11 @@ pub const StreamResult = union(Tag) {
},
.err => |err| {
if (err == .Error) {
return JSC.JSPromise.rejectedPromise(globalThis, JSValue.c(err.Error.toJS(globalThis))).asValue(globalThis);
const js_err, const was_strong = err.toJSWeak(globalThis);
if (was_strong == .Strong) {
js_err.unprotect();
}
const js_err = err.JSValue;
js_err.ensureStillAlive();
js_err.unprotect();
return JSC.JSPromise.rejectedPromise(globalThis, js_err).asValue(globalThis);
},
@@ -4332,13 +4353,9 @@ pub const ByteStream = struct {
if (to_copy.len == 0) {
if (stream == .err) {
if (stream.err == .Error) {
this.pending.result = .{ .err = .{ .Error = stream.err.Error } };
}
const js_err = stream.err.JSValue;
js_err.ensureStillAlive();
js_err.protect();
this.pending.result = .{ .err = .{ .JSValue = js_err } };
this.pending.result = .{
.err = stream.err,
};
} else {
this.pending.result = .{
.done = {},