aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/api/bun/socket.zig4
-rw-r--r--src/bun.js/api/server.zig286
-rw-r--r--src/bun.js/webcore/blob.zig17
-rw-r--r--src/bun.js/webcore/body.zig7
-rw-r--r--src/bun.js/webcore/request.zig99
-rw-r--r--src/bun.js/webcore/response.zig16
-rw-r--r--src/bun.zig68
-rw-r--r--src/hive_array.zig8
-rw-r--r--src/js_ast.zig2
9 files changed, 288 insertions, 219 deletions
diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig
index 5980de6ec..8b78cf035 100644
--- a/src/bun.js/api/bun/socket.zig
+++ b/src/bun.js/api/bun/socket.zig
@@ -1476,8 +1476,8 @@ fn NewSocket(comptime ssl: bool) type {
globalObject.throw("Only Blob/buffered bodies are supported for now", .{});
return .{ .fail = {} };
} else if (args.ptr[0].as(JSC.WebCore.Request)) |request| {
- request.body.toBlobIfPossible();
- if (request.body.tryUseAsAnyBlob()) |blob| {
+ request.body.value.toBlobIfPossible();
+ if (request.body.value.tryUseAsAnyBlob()) |blob| {
break :getter blob;
}
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig
index 4662ad0bc..f8be42e79 100644
--- a/src/bun.js/api/server.zig
+++ b/src/bun.js/api/server.zig
@@ -941,7 +941,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
needs_content_length: bool = false,
needs_content_range: bool = false,
sendfile: SendfileContext = undefined,
- request_js_object: JSC.C.JSObjectRef = null,
+ request_body: ?*JSC.WebCore.BodyValueRef = null,
request_body_buf: std.ArrayListUnmanaged(u8) = .{},
request_body_content_len: usize = 0,
@@ -983,17 +983,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
const result = arguments.ptr[0];
result.ensureStillAlive();
- if (ctx.request_js_object != null and ctx.signal == null) {
- var request_js = ctx.request_js_object.?.value();
- request_js.ensureStillAlive();
- if (request_js.as(Request)) |request_object| {
- if (request_object.signal) |signal| {
- ctx.signal = signal;
- _ = signal.ref();
- }
- }
- }
-
ctx.pending_promises_for_abort -|= 1;
if (ctx.aborted) {
ctx.finalizeForAbort();
@@ -1042,17 +1031,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
var ctx = arguments.ptr[1].asPromisePtr(@This());
const err = arguments.ptr[0];
- if (ctx.request_js_object != null and ctx.signal == null) {
- var request_js = ctx.request_js_object.?.value();
- request_js.ensureStillAlive();
- if (request_js.as(Request)) |request_object| {
- if (request_object.signal) |signal| {
- ctx.signal = signal;
- _ = signal.ref();
- }
- }
- }
-
ctx.pending_promises_for_abort -|= 1;
if (ctx.aborted) {
@@ -1273,11 +1251,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return false;
}
- if (this.request_js_object) |obj| {
- if (obj.value().as(Request)) |req| {
- if (req.body == .Locked) {
- return false;
- }
+ if (this.request_body) |body| {
+ if (body.value == .Locked) {
+ return false;
}
}
@@ -1317,27 +1293,20 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
// if we cannot, we have to reject pending promises
// first, we reject the request body promise
- if (this.request_js_object != null) {
- var request_js = this.request_js_object.?.value();
- request_js.ensureStillAlive();
-
- this.request_js_object = null;
- defer request_js.ensureStillAlive();
- defer JSC.C.JSValueUnprotect(this.server.globalThis, request_js.asObjectRef());
+ 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 (request_js.as(Request)) |req| {
+ if (body.value == .Locked) {
// the promise is pending
- if (req.body == .Locked and (req.body.Locked.action != .none or req.body.Locked.promise != null)) {
+ if (body.value.Locked.action != .none or body.value.Locked.promise != null) {
this.pending_promises_for_abort += 1;
- req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
- } else if (req.body == .Locked and (req.body.Locked.readable != null)) {
- req.body.Locked.readable.?.abort(this.server.globalThis);
- req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
- req.body.Locked.readable = null;
+ body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
+ } else if (body.value.Locked.readable != null) {
+ body.value.Locked.readable.?.abort(this.server.globalThis);
+ body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
+ body.value.Locked.readable = null;
}
- req.uws_request = null;
}
}
@@ -1399,23 +1368,13 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
_ = signal.unref();
}
- if (this.request_js_object != null) {
- ctxLog("finalizeWithoutDeinit: request_js_object != null", .{});
-
- var request_js = this.request_js_object.?.value();
- request_js.ensureStillAlive();
-
- this.request_js_object = null;
- defer request_js.ensureStillAlive();
- defer JSC.C.JSValueUnprotect(this.server.globalThis, request_js.asObjectRef());
+ if (this.request_body) |body| {
+ ctxLog("finalizeWithoutDeinit: request_body != null", .{});
// User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
// but we received nothing or the connection was aborted
- if (request_js.as(Request)) |req| {
- // the promise is pending
- if (req.body == .Locked and req.body.Locked.action != .none and req.body.Locked.promise != null) {
- req.body.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
- }
- req.uws_request = null;
+ // the promise is pending
+ if (body.value == .Locked and body.value.Locked.action != .none and body.value.Locked.promise != null) {
+ body.value.toErrorInstance(JSC.toTypeError(.ABORT_ERR, "Request aborted", .{}, this.server.globalThis), this.server.globalThis);
}
}
@@ -1461,6 +1420,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
this.request_body_buf.clearAndFree(this.allocator);
this.response_buf_owned.clearAndFree(this.allocator);
+ if (this.request_body) |body| {
+ _ = body.unref();
+ this.request_body = null;
+ }
server.request_pool_allocator.destroy(this);
}
@@ -2037,12 +2000,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
switch (promise.status(vm.global.vm())) {
.Pending => {},
.Fulfilled => {
- if (ctx.signal == null) {
- if (request_object.signal) |signal| {
- ctx.signal = signal;
- _ = signal.ref();
- }
- }
const fulfilled_value = promise.result(vm.global.vm());
// if you return a Response object or a Promise<Response>
@@ -2085,12 +2042,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
return;
},
.Rejected => {
- if (ctx.signal == null) {
- if (request_object.signal) |signal| {
- ctx.signal = signal;
- _ = signal.ref();
- }
- }
ctx.handleReject(promise.result(vm.global.vm()));
return;
},
@@ -2661,84 +2612,80 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
if (this.aborted) return;
- const request = JSC.JSValue.fromRef(this.request_js_object);
- var req = request.as(Request) orelse {
- this.request_body_buf.clearAndFree(this.allocator);
- return;
- };
+ if (this.request_body != null) {
+ var body = this.request_body.?;
- if (req.body == .Locked) {
- if (req.body.Locked.readable) |readable| {
- if (readable.ptr == .Bytes) {
- std.debug.assert(this.request_body_buf.items.len == 0);
+ if (body.value == .Locked) {
+ if (body.value.Locked.readable) |readable| {
+ if (readable.ptr == .Bytes) {
+ std.debug.assert(this.request_body_buf.items.len == 0);
- if (!last) {
- readable.ptr.Bytes.onData(
- .{
- .temporary = bun.ByteList.initConst(chunk),
- },
- bun.default_allocator,
- );
- } else {
- readable.ptr.Bytes.onData(
- .{
- .temporary_and_done = bun.ByteList.initConst(chunk),
- },
- bun.default_allocator,
- );
- }
+ if (!last) {
+ readable.ptr.Bytes.onData(
+ .{
+ .temporary = bun.ByteList.initConst(chunk),
+ },
+ bun.default_allocator,
+ );
+ } else {
+ readable.ptr.Bytes.onData(
+ .{
+ .temporary_and_done = bun.ByteList.initConst(chunk),
+ },
+ bun.default_allocator,
+ );
+ }
- return;
+ return;
+ }
}
}
- }
- if (last) {
- request.ensureStillAlive();
- var bytes = this.request_body_buf;
- defer this.request_body_buf = .{};
- var old = req.body;
+ if (last) {
+ var bytes = this.request_body_buf;
+ defer this.request_body_buf = .{};
+ var old = body.value;
+
+ const total = bytes.items.len + chunk.len;
+ getter: {
+ // if (total <= JSC.WebCore.InlineBlob.available_bytes) {
+ // if (total == 0) {
+ // body.value = .{ .Empty = {} };
+ // break :getter;
+ // }
+
+ // body.value = .{ .InlineBlob = JSC.WebCore.InlineBlob.concat(bytes.items, chunk) };
+ // this.request_body_buf.clearAndFree(this.allocator);
+ // } else {
+ bytes.ensureTotalCapacityPrecise(this.allocator, total) catch |err| {
+ this.request_body_buf.clearAndFree(this.allocator);
+ body.value.toError(err, this.server.globalThis);
+ break :getter;
+ };
- const total = bytes.items.len + chunk.len;
- getter: {
- // if (total <= JSC.WebCore.InlineBlob.available_bytes) {
- // if (total == 0) {
- // req.body = .{ .Empty = {} };
- // break :getter;
- // }
-
- // req.body = .{ .InlineBlob = JSC.WebCore.InlineBlob.concat(bytes.items, chunk) };
- // this.request_body_buf.clearAndFree(this.allocator);
- // } else {
- bytes.ensureTotalCapacityPrecise(this.allocator, total) catch |err| {
- this.request_body_buf.clearAndFree(this.allocator);
- req.body.toError(err, this.server.globalThis);
- break :getter;
- };
+ const prev_len = bytes.items.len;
+ bytes.items.len = total;
+ var slice = bytes.items[prev_len..];
+ @memcpy(slice.ptr, chunk.ptr, chunk.len);
+ body.value = .{
+ .InternalBlob = .{
+ .bytes = bytes.toManaged(this.allocator),
+ },
+ };
+ // }
+ }
- const prev_len = bytes.items.len;
- bytes.items.len = total;
- var slice = bytes.items[prev_len..];
- @memcpy(slice.ptr, chunk.ptr, chunk.len);
- req.body = .{
- .InternalBlob = .{
- .bytes = bytes.toManaged(this.allocator),
- },
- };
- // }
+ if (old == .Locked) {
+ old.resolve(&body.value, this.server.globalThis);
+ }
+ return;
}
- if (old == .Locked) {
- old.resolve(&req.body, this.server.globalThis);
+ if (this.request_body_buf.capacity == 0) {
+ this.request_body_buf.ensureTotalCapacityPrecise(this.allocator, @min(this.request_body_content_len, max_request_body_preallocate_length)) catch @panic("Out of memory while allocating request body buffer");
}
- request.unprotect();
- return;
+ this.request_body_buf.appendSlice(this.allocator, chunk) catch @panic("Out of memory while allocating request body");
}
-
- if (this.request_body_buf.capacity == 0) {
- this.request_body_buf.ensureTotalCapacityPrecise(this.allocator, @min(this.request_body_content_len, max_request_body_preallocate_length)) catch @panic("Out of memory while allocating request body buffer");
- }
- this.request_body_buf.appendSlice(this.allocator, chunk) catch @panic("Out of memory while allocating request body");
}
pub fn onStartStreamingRequestBody(this: *RequestContext) JSC.WebCore.DrainResult {
@@ -2771,22 +2718,20 @@ 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 {
ctxLog("onStartBuffering", .{});
- const request = JSC.JSValue.c(this.request_js_object);
- request.ensureStillAlive();
// TODO: check if is someone calling onStartBuffering other than onStartBufferingCallback
// if is not, this should be removed and only keep protect + setAbortHandler
if (this.is_transfer_encoding == false and this.request_body_content_len == 0) {
// no content-length or 0 content-length
// no transfer-encoding
- if (request.as(Request)) |req| {
- var old = req.body;
+ if (this.request_body != null) {
+ var body = this.request_body.?;
+ var old = body.value;
old.Locked.onReceiveValue = null;
- req.body = .{ .Null = {} };
- old.resolve(&req.body, this.server.globalThis);
+ var new_body = .{ .Null = {} };
+ old.resolve(&new_body, this.server.globalThis);
+ body.value = new_body;
}
} else {
- // we need to buffer the request body
- request.protect();
this.setAbortHandler();
}
}
@@ -4201,6 +4146,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
config: ServerConfig = ServerConfig{},
pending_requests: usize = 0,
request_pool_allocator: std.mem.Allocator = undefined,
+
listen_callback: JSC.AnyTask = undefined,
allocator: std.mem.Allocator,
poll_ref: JSC.PollRef = .{},
@@ -4596,7 +4542,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
existing_request = Request{
.url = url.href,
.headers = headers,
- .body = body,
+ .body = JSC.WebCore.InitRequestBodyValue(body) catch unreachable,
.method = method,
};
}
@@ -4948,15 +4894,22 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory");
ctx.create(this, req, resp);
var request_object = this.allocator.create(JSC.WebCore.Request) catch unreachable;
+ var body = JSC.WebCore.InitRequestBodyValue(.{ .Null = {} }) catch unreachable;
+
+ ctx.request_body = body;
+ const js_signal = JSC.WebCore.AbortSignal.create(this.globalThis);
+ js_signal.ensureStillAlive();
+ if (JSC.WebCore.AbortSignal.fromJS(js_signal)) |signal| {
+ ctx.signal = signal.ref().ref(); // +2 refs 1 for the request and 1 for the request context
+ }
request_object.* = .{
.url = "",
.method = ctx.method,
.uws_request = req,
.https = ssl_enabled,
- .body = .{
- .Null = {},
- },
+ .signal = ctx.signal,
+ .body = body.ref(),
};
if (comptime debug_mode) {
@@ -4996,7 +4949,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
// we defer pre-allocating the body until we receive the first chunk
// that way if the client is lying about how big the body is or the client aborts
// we don't waste memory
- request_object.body = .{
+ ctx.request_body.?.value = .{
.Locked = .{
.task = ctx,
.global = this.globalThis,
@@ -5005,14 +4958,6 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
},
};
resp.onData(*RequestContext, RequestContext.onBufferedBodyChunk, ctx);
- } else if (request_object.body == .Locked) {
- // This branch should never be taken, but we are handling it anyway.
- var old = request_object.body;
- old.Locked.onReceiveValue = null;
- request_object.body = .{ .Null = {} };
- old.resolve(&request_object.body, this.globalThis);
- } else {
- request_object.body = .{ .Null = {} };
}
}
@@ -5021,15 +4966,12 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
request_object.toJS(this.globalThis),
this.thisObject,
};
- ctx.request_js_object = args[0].asObjectRef();
+
const request_value = args[0];
request_value.ensureStillAlive();
const response_value = this.config.onRequest.callWithThis(this.globalThis, this.thisObject, &args);
- if (request_object.signal) |signal| {
- ctx.signal = signal;
- _ = signal.ref();
- }
+
ctx.onResponse(
this,
req,
@@ -5037,6 +4979,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
request_value,
response_value,
);
+ // uWS request will not live longer than this function
+ request_object.uws_request = null;
}
pub fn onWebSocketUpgrade(
@@ -5052,15 +4996,23 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory");
ctx.create(this, req, resp);
var request_object = this.allocator.create(JSC.WebCore.Request) catch unreachable;
+ var body = JSC.WebCore.InitRequestBodyValue(.{ .Null = {} }) catch unreachable;
+
+ ctx.request_body = body;
+ const js_signal = JSC.WebCore.AbortSignal.create(this.globalThis);
+ js_signal.ensureStillAlive();
+ if (JSC.WebCore.AbortSignal.fromJS(js_signal)) |signal| {
+ ctx.signal = signal.ref().ref(); // +2 refs 1 for the request and 1 for the request context
+ }
+
request_object.* = .{
.url = "",
.method = ctx.method,
.uws_request = req,
.upgrader = ctx,
.https = ssl_enabled,
- .body = .{
- .Null = {},
- },
+ .signal = ctx.signal,
+ .body = body.ref(),
};
ctx.upgrade_context = upgrade_ctx;
@@ -5069,7 +5021,6 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
request_object.toJS(this.globalThis),
this.thisObject,
};
- ctx.request_js_object = args[0].asObjectRef();
const request_value = args[0];
request_value.ensureStillAlive();
const response_value = this.config.onRequest.callWithThis(this.globalThis, this.thisObject, &args);
@@ -5081,6 +5032,9 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
request_value,
response_value,
);
+
+ // uWS request will not live longer than this function
+ request_object.uws_request = null;
}
pub fn listen(this: *ThisServer) void {
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index c5e07b13c..5723a36b4 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -711,7 +711,7 @@ pub const Blob = struct {
}
if (data.as(Request)) |request| {
- switch (request.body) {
+ switch (request.body.value) {
// .InlineBlob,
.InternalBlob,
.Used,
@@ -719,13 +719,13 @@ pub const Blob = struct {
.Blob,
.Null,
=> {
- break :brk request.body.use();
+ break :brk request.body.value.use();
},
.Error => {
destination_blob.detach();
- const err = request.body.Error;
+ const err = request.body.value.Error;
JSC.C.JSValueUnprotect(ctx, err.asObjectRef());
- _ = request.body.use();
+ _ = request.body.value.use();
return JSC.JSPromise.rejectedPromiseValue(ctx.ptr(), err).asObjectRef();
},
.Locked => {
@@ -737,8 +737,8 @@ pub const Blob = struct {
.promise = promise,
};
- request.body.Locked.task = task;
- request.body.Locked.onReceiveValue = WriteFileWaitFromLockedValueTask.thenWrap;
+ request.body.value.Locked.task = task;
+ request.body.value.Locked.onReceiveValue = WriteFileWaitFromLockedValueTask.thenWrap;
return promise.asValue(ctx.ptr()).asObjectRef();
},
@@ -3436,6 +3436,9 @@ pub const AnyBlob = union(enum) {
switch (this.*) {
.Blob => return this.Blob.toString(global, lifetime),
// .InlineBlob => {
+ // if (this.InlineBlob.len == 0) {
+ // return ZigString.Empty.toValue(global);
+ // }
// const owned = this.InlineBlob.toStringOwned(global);
// this.* = .{ .InlineBlob = .{ .len = 0 } };
// return owned;
@@ -3457,7 +3460,7 @@ pub const AnyBlob = union(enum) {
.Blob => return this.Blob.toArrayBuffer(global, lifetime),
// .InlineBlob => {
// if (this.InlineBlob.len == 0) {
- // return JSC.ArrayBuffer.empty.toJS(global, null);
+ // return JSC.ArrayBuffer.create(global, "", .ArrayBuffer);
// }
// var bytes = this.InlineBlob.sliceConst();
// this.InlineBlob.len = 0;
diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig
index ae873fa88..a4446446b 100644
--- a/src/bun.js/webcore/body.zig
+++ b/src/bun.js/webcore/body.zig
@@ -45,7 +45,7 @@ const StringJoiner = @import("../../string_joiner.zig");
const uws = @import("root").bun.uws;
const Blob = JSC.WebCore.Blob;
-const InlineBlob = JSC.WebCore.InlineBlob;
+// const InlineBlob = JSC.WebCore.InlineBlob;
const AnyBlob = JSC.WebCore.AnyBlob;
const InternalBlob = JSC.WebCore.InternalBlob;
const Response = JSC.WebCore.Response;
@@ -100,6 +100,7 @@ pub const Body = struct {
try formatter.writeIndent(Writer, writer);
try this.value.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else if (this.value == .InternalBlob) {
+ // } else if (this.value == .InternalBlob or this.value == .InlineBlob) {
try formatter.printComma(Writer, writer, enable_ansi_colors);
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
@@ -737,11 +738,12 @@ pub const Body = struct {
},
// .InlineBlob => {
// const cloned = this.InlineBlob.bytes;
+ // // keep same behavior as InternalBlob but clone the data
// const new_blob = Blob.create(
// cloned[0..this.InlineBlob.len],
// bun.default_allocator,
// JSC.VirtualMachine.get().global,
- // this.InlineBlob.was_string,
+ // false,
// );
// this.* = .{ .Used = {} };
@@ -855,7 +857,6 @@ pub const Body = struct {
JSC.C.JSValueUnprotect(VirtualMachine.get().global, this.Error.asObjectRef());
}
}
-
pub fn clone(this: *Value, globalThis: *JSC.JSGlobalObject) Value {
if (this.* == .InternalBlob) {
var internal_blob = this.InternalBlob;
diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig
index 854f7ff43..a3df7caeb 100644
--- a/src/bun.js/webcore/request.zig
+++ b/src/bun.js/webcore/request.zig
@@ -52,6 +52,15 @@ const BodyMixin = JSC.WebCore.BodyMixin;
const Body = JSC.WebCore.Body;
const Blob = JSC.WebCore.Blob;
+const body_value_pool_size: u16 = 256;
+pub const BodyValueRef = bun.HiveRef(Body.Value, body_value_pool_size);
+const BodyValueHiveAllocator = bun.HiveArray(BodyValueRef, body_value_pool_size).Fallback;
+
+var body_value_hive_allocator = BodyValueHiveAllocator.init(bun.default_allocator);
+
+pub fn InitRequestBodyValue(value: Body.Value) !*BodyValueRef {
+ return try BodyValueRef.init(value, &body_value_hive_allocator);
+}
// https://developer.mozilla.org/en-US/docs/Web/API/Request
pub const Request = struct {
url: []const u8 = "",
@@ -59,7 +68,7 @@ pub const Request = struct {
headers: ?*FetchHeaders = null,
signal: ?*AbortSignal = null,
- body: Body.Value = Body.Value{ .Null = {} },
+ body: *BodyValueRef,
method: Method = Method.GET,
uws_request: ?*uws.Request = null,
https: bool = false,
@@ -94,9 +103,9 @@ pub const Request = struct {
}
}
- if (this.body == .Blob) {
- if (this.body.Blob.content_type.len > 0)
- return ZigString.Slice.fromUTF8NeverFree(this.body.Blob.content_type);
+ if (this.body.value == .Blob) {
+ if (this.body.value.Blob.content_type.len > 0)
+ return ZigString.Slice.fromUTF8NeverFree(this.body.value.Blob.content_type);
}
return null;
@@ -111,14 +120,14 @@ pub const Request = struct {
pub fn estimatedSize(this: *Request) callconv(.C) usize {
return this.reported_estimated_size orelse brk: {
- this.reported_estimated_size = @truncate(u63, this.body.estimatedSize() + this.sizeOfURL() + @sizeOf(Request));
+ this.reported_estimated_size = @truncate(u63, this.body.value.estimatedSize() + this.sizeOfURL() + @sizeOf(Request));
break :brk this.reported_estimated_size.?;
};
}
pub fn writeFormat(this: *Request, comptime Formatter: type, formatter: *Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void {
const Writer = @TypeOf(writer);
- try writer.print("Request ({}) {{\n", .{bun.fmt.size(this.body.slice().len)});
+ try writer.print("Request ({}) {{\n", .{bun.fmt.size(this.body.value.slice().len)});
{
formatter.indent += 1;
defer formatter.indent -|= 1;
@@ -136,20 +145,20 @@ pub const Request = struct {
try writer.print(comptime Output.prettyFmt("<r><b>{s}<r>", enable_ansi_colors), .{this.url});
try writer.writeAll("\"");
- if (this.body == .Blob) {
+ if (this.body.value == .Blob) {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- try this.body.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
- } else if (this.body == .InternalBlob) {
+ try this.body.value.Blob.writeFormat(Formatter, formatter, writer, enable_ansi_colors);
+ } else if (this.body.value == .InternalBlob) {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
- if (this.body.size() == 0) {
+ if (this.body.value.size() == 0) {
try Blob.initEmpty(undefined).writeFormat(Formatter, formatter, writer, enable_ansi_colors);
} else {
- try Blob.writeFormatForSize(this.body.size(), writer, enable_ansi_colors);
+ try Blob.writeFormatForSize(this.body.value.size(), writer, enable_ansi_colors);
}
- } else if (this.body == .Locked) {
- if (this.body.Locked.readable) |stream| {
+ } else if (this.body.value == .Locked) {
+ if (this.body.value.Locked.readable) |stream| {
try writer.writeAll("\n");
try formatter.writeIndent(Writer, writer);
formatter.printAs(.Object, Writer, writer, stream.value, stream.value.jsType(), enable_ansi_colors);
@@ -164,7 +173,7 @@ pub const Request = struct {
pub fn fromRequestContext(ctx: *RequestContext) !Request {
var req = Request{
.url = bun.asByteSlice(ctx.getFullURL()),
- .body = .{ .Null = {} },
+ .body = try InitRequestBodyValue(.{ .Null = {} }),
.method = ctx.method,
.headers = FetchHeaders.createFromPicoHeaders(ctx.request.headers),
.url_was_allocated = true,
@@ -179,7 +188,7 @@ pub const Request = struct {
}
}
- switch (this.body) {
+ switch (this.body.value) {
.Blob => |blob| {
if (blob.content_type.len > 0) {
return blob.content_type;
@@ -187,8 +196,8 @@ pub const Request = struct {
return MimeType.other.value;
},
- .InternalBlob => return this.body.InternalBlob.contentType(),
- // .InlineBlob => return this.body.InlineBlob.contentType(),
+ .InternalBlob => return this.body.value.InternalBlob.contentType(),
+ // .InlineBlob => return this.body.value.InlineBlob.contentType(),
.Null, .Error, .Used, .Locked, .Empty => return MimeType.other.value,
}
}
@@ -270,7 +279,6 @@ pub const Request = struct {
bun.default_allocator.free(bun.constStrToU8(this.url));
}
- this.body.deinit();
if (this.signal) |signal| {
_ = signal.unref();
this.signal = null;
@@ -279,6 +287,7 @@ pub const Request = struct {
pub fn finalize(this: *Request) callconv(.C) void {
this.finalizeWithoutDeinit();
+ _ = this.body.unref();
bun.default_allocator.destroy(this);
}
@@ -400,13 +409,19 @@ pub const Request = struct {
globalThis: *JSC.JSGlobalObject,
arguments: []const JSC.JSValue,
) ?Request {
- var req = Request{};
+ var req = Request{
+ .body = InitRequestBodyValue(.{ .Null = {} }) catch {
+ return null;
+ },
+ };
if (arguments.len == 0) {
globalThis.throw("Failed to construct 'Request': 1 argument required, but only 0 present.", .{});
+ _ = req.body.unref();
return null;
} else if (arguments[0].isEmptyOrUndefinedOrNull() or !arguments[0].isCell()) {
globalThis.throw("Failed to construct 'Request': expected non-empty string or object, got undefined", .{});
+ _ = req.body.unref();
return null;
}
@@ -423,10 +438,12 @@ pub const Request = struct {
if (is_first_argument_a_url) {
const slice = arguments[0].toSliceOrNull(globalThis) orelse {
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
};
req.url = (slice.cloneIfNeeded(globalThis.allocator()) catch {
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
}).slice();
req.url_was_allocated = req.url.len > 0;
@@ -434,6 +451,7 @@ pub const Request = struct {
fields.insert(.url);
} else if (!url_or_object_type.isObject()) {
globalThis.throw("Failed to construct 'Request': expected non-empty string or object", .{});
+ _ = req.body.unref();
return null;
}
@@ -476,10 +494,10 @@ pub const Request = struct {
}
if (!fields.contains(.body)) {
- switch (request.body) {
+ switch (request.body.value) {
.Null, .Empty, .Used => {},
else => {
- req.body = request.body.clone(globalThis);
+ req.body.value = request.body.value.clone(globalThis);
fields.insert(.body);
},
}
@@ -511,7 +529,7 @@ pub const Request = struct {
switch (response.body.value) {
.Null, .Empty, .Used => {},
else => {
- req.body = response.body.value.clone(globalThis);
+ req.body.value = response.body.value.clone(globalThis);
fields.insert(.body);
},
}
@@ -523,9 +541,10 @@ pub const Request = struct {
if (value.fastGet(globalThis, .body)) |body_| {
fields.insert(.body);
if (Body.Value.fromJS(globalThis, body_)) |body| {
- req.body = body;
+ req.body.value = body;
} else {
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
}
}
@@ -546,10 +565,12 @@ pub const Request = struct {
{
const slice = value.toSliceOrNull(globalThis) orelse {
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
};
req.url = (slice.cloneIfNeeded(globalThis.allocator()) catch {
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
}).slice();
req.url_was_allocated = req.url.len > 0;
@@ -569,6 +590,7 @@ pub const Request = struct {
} else {
globalThis.throw("Failed to construct 'Request': signal is not of type AbortSignal.", .{});
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
}
}
@@ -596,6 +618,7 @@ pub const Request = struct {
if (req.url.len == 0) {
globalThis.throw("Failed to construct 'Request': url is required.", .{});
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
}
@@ -603,20 +626,20 @@ pub const Request = struct {
if (parsed_url.hostname.len == 0) {
globalThis.throw("Failed to construct 'Request': Invalid URL (missing a hostname)", .{});
req.finalizeWithoutDeinit();
+ _ = req.body.unref();
return null;
}
- if (req.body == .Blob and
+ if (req.body.value == .Blob and
req.headers != null and
- req.body.Blob.content_type.len > 0 and
+ req.body.value.Blob.content_type.len > 0 and
!req.headers.?.fastHas(.ContentType))
{
- req.headers.?.put("content-type", req.body.Blob.content_type, globalThis);
+ req.headers.?.put("content-type", req.body.value.Blob.content_type, globalThis);
}
return req;
}
-
pub fn constructor(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
@@ -624,8 +647,12 @@ pub const Request = struct {
const arguments_ = callframe.arguments(2);
const arguments = arguments_.ptr[0..arguments_.len];
- const request = constructInto(globalThis, arguments) orelse return null;
- var request_ = getAllocator(globalThis).create(Request) catch return null;
+ var request = constructInto(globalThis, arguments) orelse {
+ return null;
+ };
+ var request_ = getAllocator(globalThis).create(Request) catch {
+ return null;
+ };
request_.* = request;
return request_;
}
@@ -633,7 +660,7 @@ pub const Request = struct {
pub fn getBodyValue(
this: *Request,
) *Body.Value {
- return &this.body;
+ return &this.body.value;
}
pub fn getFetchHeaders(
@@ -661,8 +688,8 @@ pub const Request = struct {
} else {
this.headers = FetchHeaders.createEmpty();
- if (this.body == .Blob) {
- const content_type = this.body.Blob.content_type;
+ if (this.body.value == .Blob) {
+ const content_type = this.body.value.Blob.content_type;
if (content_type.len > 0) {
this.headers.?.put("content-type", content_type, globalThis);
}
@@ -699,9 +726,15 @@ pub const Request = struct {
) void {
this.ensureURL() catch {};
+ var body = InitRequestBodyValue(this.body.value.clone(globalThis)) catch {
+ globalThis.throw("Failed to clone request", .{});
+ return;
+ };
+
req.* = Request{
- .body = this.body.clone(globalThis),
+ .body = body,
.url = allocator.dupe(u8, this.url) catch {
+ _ = body.unref();
globalThis.throw("Failed to clone request", .{});
return;
},
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 7517395da..9c262252a 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -760,11 +760,6 @@ pub const Fetch = struct {
fn toBodyValue(this: *FetchTasklet) Body.Value {
var response_buffer = this.response_buffer.list;
- const response = Body.Value{
- .InternalBlob = .{
- .bytes = response_buffer.toManaged(bun.default_allocator),
- },
- };
this.response_buffer = .{
.allocator = default_allocator,
.list = .{
@@ -778,6 +773,13 @@ pub const Fetch = struct {
// defer response_buffer.deinit(bun.default_allocator);
// return .{ .InlineBlob = inline_blob };
// }
+
+ const response = Body.Value{
+ .InternalBlob = .{
+ .bytes = response_buffer.toManaged(bun.default_allocator),
+ },
+ };
+
return response;
}
@@ -1016,7 +1018,7 @@ pub const Fetch = struct {
return JSC.JSValue.jsUndefined().asObjectRef();
}
} else {
- body = request.body.useAsAnyBlob();
+ body = request.body.value.useAsAnyBlob();
}
if (options.get(ctx, "timeout")) |timeout_value| {
@@ -1095,7 +1097,7 @@ pub const Fetch = struct {
}
headers = Headers.from(head, bun.default_allocator) catch unreachable;
}
- body = request.body.useAsAnyBlob();
+ body = request.body.value.useAsAnyBlob();
// no proxy only url
url = ZigURL.parse(getAllocator(ctx).dupe(u8, request.url) catch unreachable);
url_proxy_buffer = url.href;
diff --git a/src/bun.zig b/src/bun.zig
index d640b4a77..9fd6102e8 100644
--- a/src/bun.zig
+++ b/src/bun.zig
@@ -1361,4 +1361,72 @@ pub fn threadlocalAllocator() std.mem.Allocator {
return default_allocator;
}
+pub fn Ref(comptime T: type) type {
+ return struct {
+ ref_count: u32,
+ allocator: std.mem.Allocator,
+ value: T,
+
+ pub fn init(value: T, allocator: std.mem.Allocator) !*@This() {
+ var this = try allocator.create(@This());
+ this.allocator = allocator;
+ this.ref_count = 1;
+ this.value = value;
+ return this;
+ }
+
+ pub fn ref(this: *@This()) *@This() {
+ this.ref_count += 1;
+ return this;
+ }
+
+ pub fn unref(this: *@This()) ?*@This() {
+ this.ref_count -= 1;
+ if (this.ref_count == 0) {
+ if (@hasDecl(T, "deinit")) {
+ this.value.deinit();
+ }
+ this.allocator.destroy(this);
+ return null;
+ }
+ return this;
+ }
+ };
+}
+
+pub fn HiveRef(comptime T: type, comptime capacity: u16) type {
+ return struct {
+ const HiveAllocator = HiveArray(@This(), capacity).Fallback;
+
+ ref_count: u32,
+ allocator: *HiveAllocator,
+ value: T,
+
+ pub fn init(value: T, allocator: *HiveAllocator) !*@This() {
+ var this = try allocator.tryGet();
+ this.allocator = allocator;
+ this.ref_count = 1;
+ this.value = value;
+ return this;
+ }
+
+ pub fn ref(this: *@This()) *@This() {
+ this.ref_count += 1;
+ return this;
+ }
+
+ pub fn unref(this: *@This()) ?*@This() {
+ this.ref_count -= 1;
+ if (this.ref_count == 0) {
+ if (@hasDecl(T, "deinit")) {
+ this.value.deinit();
+ }
+ this.allocator.put(this);
+ return null;
+ }
+ return this;
+ }
+ };
+}
+
pub const MaxHeapAllocator = @import("./max_heap_allocator.zig").MaxHeapAllocator;
diff --git a/src/hive_array.zig b/src/hive_array.zig
index 39f10d324..cc032c44f 100644
--- a/src/hive_array.zig
+++ b/src/hive_array.zig
@@ -86,6 +86,14 @@ pub fn HiveArray(comptime T: type, comptime capacity: u16) type {
return self.allocator.create(T) catch unreachable;
}
+ pub fn tryGet(self: *This) !*T {
+ if (self.hive.get()) |value| {
+ return value;
+ }
+
+ return try self.allocator.create(T);
+ }
+
pub fn put(self: *This, value: *T) void {
if (self.hive.put(value)) return;
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 3cf99211b..d2cb467d1 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -9326,7 +9326,7 @@ pub const Macro = struct {
blob_ = resp.body.use();
} else if (value.as(JSC.WebCore.Request)) |resp| {
mime_type = HTTP.MimeType.init(resp.mimeType());
- blob_ = resp.body.use();
+ blob_ = resp.body.value.use();
} else if (value.as(JSC.WebCore.Blob)) |resp| {
blob_ = resp.*;
blob_.?.allocator = null;