aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-08-24 19:39:00 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-24 19:39:00 -0700
commit8a48e8bb0b7de985a96b3a4cae389e3294a2c0e3 (patch)
tree20d8dc382512313061c20296d1e33e8d18fbbde3
parent097ae4e982a9cbcae6b4886c4efb82d452629b99 (diff)
downloadbun-8a48e8bb0b7de985a96b3a4cae389e3294a2c0e3.tar.gz
bun-8a48e8bb0b7de985a96b3a4cae389e3294a2c0e3.tar.zst
bun-8a48e8bb0b7de985a96b3a4cae389e3294a2c0e3.zip
Report extra memory more (#4289)
* Report memory allocated in fetch * Memory size reporting to `Headers` * Fixup memory reporting allocator * Make these tests do more * cleanup some of this --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--src/bun.js/base.zig83
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp9
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp1
-rw-r--r--src/bun.js/bindings/bindings.cpp21
-rw-r--r--src/bun.js/bindings/bindings.zig5
-rw-r--r--src/bun.js/bindings/generated_classes.zig5
-rw-r--r--src/bun.js/bindings/webcore/FetchHeaders.cpp5
-rw-r--r--src/bun.js/bindings/webcore/FetchHeaders.h2
-rw-r--r--src/bun.js/bindings/webcore/HTTPHeaderMap.cpp19
-rw-r--r--src/bun.js/bindings/webcore/HTTPHeaderMap.h2
-rw-r--r--src/bun.js/bindings/webcore/JSFetchHeaders.cpp17
-rw-r--r--src/bun.js/bindings/webcore/JSFetchHeaders.h4
-rw-r--r--src/bun.js/javascript.zig4
-rw-r--r--src/bun.js/webcore/blob.zig28
-rw-r--r--src/bun.js/webcore/response.classes.ts1
-rw-r--r--src/bun.js/webcore/response.zig190
-rw-r--r--src/http_client_async.zig10
-rw-r--r--test/js/bun/http/serve.test.ts18
18 files changed, 320 insertions, 104 deletions
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index e72e196a3..0ed780a5d 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -2484,3 +2484,86 @@ pub const AsyncTaskTracker = struct {
bun.JSC.Debugger.didDispatchAsyncCall(globalObject, bun.JSC.Debugger.AsyncCallType.EventListener, this.id);
}
};
+
+pub const MemoryReportingAllocator = struct {
+ child_allocator: std.mem.Allocator,
+ memory_cost: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0),
+ const log = Output.scoped(.MEM, false);
+
+ fn alloc(this: *MemoryReportingAllocator, n: usize, log2_ptr_align: u8, return_address: usize) ?[*]u8 {
+ var result = this.child_allocator.rawAlloc(n, log2_ptr_align, return_address) orelse return null;
+ _ = this.memory_cost.fetchAdd(n, .Monotonic);
+ if (comptime Environment.allow_assert)
+ log("malloc({d}) = {d}", .{ n, this.memory_cost.loadUnchecked() });
+ return result;
+ }
+
+ pub fn discard(this: *MemoryReportingAllocator, buf: []const u8) void {
+ _ = this.memory_cost.fetchSub(buf.len, .Monotonic);
+ if (comptime Environment.allow_assert)
+ log("discard({d}) = {d}", .{ buf.len, this.memory_cost.loadUnchecked() });
+ }
+
+ fn resize(this: *MemoryReportingAllocator, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
+ if (this.child_allocator.rawResize(buf, buf_align, new_len, ret_addr)) {
+ _ = this.memory_cost.fetchAdd(new_len -| buf.len, .Monotonic);
+ if (comptime Environment.allow_assert)
+ log("resize() = {d}", .{this.memory_cost.loadUnchecked()});
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ fn free(this: *MemoryReportingAllocator, buf: []u8, buf_align: u8, ret_addr: usize) void {
+ this.child_allocator.rawFree(buf, buf_align, ret_addr);
+
+ const prev = this.memory_cost.fetchSub(buf.len, .Monotonic);
+ if (comptime Environment.allow_assert) {
+ // check for overflow, racily
+ std.debug.assert(prev > this.memory_cost.load(.Monotonic));
+ log("free({d}) = {d}", .{ buf.len, this.memory_cost.loadUnchecked() });
+ }
+ }
+
+ pub fn wrap(this: *MemoryReportingAllocator, allocator_: std.mem.Allocator) std.mem.Allocator {
+ this.* = .{
+ .child_allocator = allocator_,
+ };
+
+ return this.allocator();
+ }
+
+ pub fn allocator(this: *MemoryReportingAllocator) std.mem.Allocator {
+ return std.mem.Allocator{
+ .ptr = this,
+ .vtable = &MemoryReportingAllocator.VTable,
+ };
+ }
+
+ pub fn report(this: *MemoryReportingAllocator, vm: *JSC.VM) void {
+ const mem = this.memory_cost.load(.Monotonic);
+ if (mem > 0) {
+ vm.reportExtraMemory(mem);
+ if (comptime Environment.allow_assert)
+ log("report({d})", .{mem});
+ }
+ }
+
+ pub inline fn assert(this: *const MemoryReportingAllocator) void {
+ if (comptime !Environment.allow_assert) {
+ return;
+ }
+
+ const memory_cost = this.memory_cost.load(.Monotonic);
+ if (memory_cost > 0) {
+ Output.panic("MemoryReportingAllocator still has {d} bytes allocated", .{memory_cost});
+ }
+ }
+
+ pub const VTable = std.mem.Allocator.VTable{
+ .alloc = @ptrCast(&MemoryReportingAllocator.alloc),
+ .resize = @ptrCast(&MemoryReportingAllocator.resize),
+ .free = @ptrCast(&MemoryReportingAllocator.free),
+ };
+};
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index 3f8a92e3d..0ab7a1b5d 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -1728,6 +1728,8 @@ void JSBlobPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObj
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
+extern "C" size_t Blob__estimatedSize(void* ptr);
+
void JSBlobConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSBlobPrototype* prototype)
{
Base::finishCreation(vm, 0, "Blob"_s, PropertyAdditionMode::WithoutStructureTransition);
@@ -1775,6 +1777,7 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSBlobConstructor::construct(JSC::J
}
JSBlob* instance = JSBlob::create(vm, globalObject, structure, ptr);
+ vm.heap.reportExtraMemoryAllocated(Blob__estimatedSize(instance->wrapped()));
return JSValue::encode(instance);
}
@@ -1868,7 +1871,7 @@ extern "C" EncodedJSValue Blob__create(Zig::GlobalObject* globalObject, void* pt
auto& vm = globalObject->vm();
JSC::Structure* structure = globalObject->JSBlobStructure();
JSBlob* instance = JSBlob::create(vm, globalObject, structure, ptr);
-
+ vm.heap.reportExtraMemoryAllocated(Blob__estimatedSize(ptr));
return JSValue::encode(instance);
}
@@ -1878,7 +1881,9 @@ void JSBlob::visitChildrenImpl(JSCell* cell, Visitor& visitor)
JSBlob* thisObject = jsCast<JSBlob*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
-
+ if (auto* ptr = thisObject->wrapped()) {
+ visitor.reportExtraMemoryVisited(Blob__estimatedSize(ptr));
+ }
thisObject->visitAdditionalChildren<Visitor>(visitor);
}
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 5aee64451..467667953 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -289,7 +289,6 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
}
}
}
-
JSC::Options::assertOptionsAreCoherent();
}
}
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index badfd3437..674957218 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -1087,8 +1087,16 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject*
JSC__JSValue WebCore__FetchHeaders__toJS(WebCore__FetchHeaders* headers, JSC__JSGlobalObject* lexicalGlobalObject)
{
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ bool needsMemoryCost = headers->hasOneRef();
- return JSC::JSValue::encode(WebCore::toJS(lexicalGlobalObject, globalObject, headers));
+ JSValue value = WebCore::toJS(lexicalGlobalObject, globalObject, headers);
+
+ if (needsMemoryCost) {
+ JSFetchHeaders* jsHeaders = jsCast<JSFetchHeaders*>(value);
+ jsHeaders->computeMemoryCost();
+ }
+
+ return JSC::JSValue::encode(value);
}
JSC__JSValue WebCore__FetchHeaders__clone(WebCore__FetchHeaders* headers, JSC__JSGlobalObject* arg1)
{
@@ -1263,7 +1271,11 @@ JSC__JSValue WebCore__FetchHeaders__createValue(JSC__JSGlobalObject* arg0, Strin
WebCore::propagateException(*arg0, throwScope,
headers->fill(WebCore::FetchHeaders::Init(WTFMove(pairs))));
pairs.releaseBuffer();
- return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), WTFMove(headers)));
+ JSValue value = WebCore::toJSNewlyCreated(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), WTFMove(headers));
+
+ JSFetchHeaders* fetchHeaders = jsCast<JSFetchHeaders*>(value);
+ fetchHeaders->computeMemoryCost();
+ return JSC::JSValue::encode(value);
}
void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* headers, const ZigString* arg1, ZigString* arg2, JSC__JSGlobalObject* global)
{
@@ -3968,6 +3980,11 @@ void JSC__VM__deleteAllCode(JSC__VM* arg1, JSC__JSGlobalObject* globalObject)
arg1->heap.reportAbandonedObjectGraph();
}
+void JSC__VM__reportExtraMemory(JSC__VM* arg0, size_t arg1)
+{
+ arg0->heap.deprecatedReportExtraMemory(arg1);
+}
+
void JSC__VM__deinit(JSC__VM* arg1, JSC__JSGlobalObject* globalObject) {}
void JSC__VM__drainMicrotasks(JSC__VM* arg0) { arg0->drainMicrotasks(); }
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 5e4c6f5b9..c8c87648a 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -5007,6 +5007,11 @@ pub const VM = extern struct {
pub fn deferGC(this: *VM, ctx: ?*anyopaque, callback: *const fn (ctx: ?*anyopaque) callconv(.C) void) void {
cppFn("deferGC", .{ this, ctx, callback });
}
+ extern fn JSC__VM__reportExtraMemory(*VM, usize) void;
+ pub fn reportExtraMemory(this: *VM, size: usize) void {
+ JSC.markBinding(@src());
+ JSC__VM__reportExtraMemory(this, size);
+ }
pub fn deleteAllCode(
vm: *VM,
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index 50e1a620b..e98b8f973 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -467,6 +467,10 @@ pub const JSBlob = struct {
extern fn Blob__dangerouslySetPtr(JSC.JSValue, ?*Blob) bool;
comptime {
+ if (@TypeOf(Blob.estimatedSize) != (fn (*Blob) callconv(.C) usize)) {
+ @compileLog("Blob.estimatedSize is not a size function");
+ }
+
if (@TypeOf(Blob.onStructuredCloneSerialize) != (fn (*Blob, globalThis: *JSC.JSGlobalObject, ctx: *anyopaque, writeBytes: *const fn (*anyopaque, ptr: [*]const u8, len: u32) callconv(.C) void) callconv(.C) void)) {
@compileLog("Blob.onStructuredCloneSerialize is not a structured clone serialize function");
}
@@ -513,6 +517,7 @@ pub const JSBlob = struct {
@compileLog("Expected Blob.getWriter to be a callback but received " ++ @typeName(@TypeOf(Blob.getWriter)));
if (!JSC.is_bindgen) {
@export(Blob.constructor, .{ .name = "BlobClass__construct" });
+ @export(Blob.estimatedSize, .{ .name = "Blob__estimatedSize" });
@export(Blob.finalize, .{ .name = "BlobClass__finalize" });
@export(Blob.getArrayBuffer, .{ .name = "BlobPrototype__getArrayBuffer" });
@export(Blob.getExists, .{ .name = "BlobPrototype__getExists" });
diff --git a/src/bun.js/bindings/webcore/FetchHeaders.cpp b/src/bun.js/bindings/webcore/FetchHeaders.cpp
index f54214733..02ddbac45 100644
--- a/src/bun.js/bindings/webcore/FetchHeaders.cpp
+++ b/src/bun.js/bindings/webcore/FetchHeaders.cpp
@@ -214,6 +214,11 @@ ExceptionOr<void> FetchHeaders::remove(const String& name)
return {};
}
+size_t FetchHeaders::memoryCost() const
+{
+ return m_headers.memoryCost() + sizeof(*this);
+}
+
ExceptionOr<String> FetchHeaders::get(const String& name) const
{
if (!isValidHTTPToken(name))
diff --git a/src/bun.js/bindings/webcore/FetchHeaders.h b/src/bun.js/bindings/webcore/FetchHeaders.h
index 81f8e89c2..515f01227 100644
--- a/src/bun.js/bindings/webcore/FetchHeaders.h
+++ b/src/bun.js/bindings/webcore/FetchHeaders.h
@@ -62,6 +62,8 @@ public:
ExceptionOr<void> fill(const FetchHeaders&);
void filterAndFill(const HTTPHeaderMap&, Guard);
+ size_t memoryCost() const;
+
inline uint32_t size()
{
return m_headers.size();
diff --git a/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp b/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp
index 99fc9cf13..5021c699d 100644
--- a/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp
+++ b/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp
@@ -77,6 +77,25 @@ String HTTPHeaderMap::get(const String& name) const
return getUncommonHeader(name);
}
+size_t HTTPHeaderMap::memoryCost() const
+{
+ size_t cost = m_commonHeaders.size() * sizeof(CommonHeader);
+ cost += m_uncommonHeaders.size() * sizeof(UncommonHeader);
+ cost += m_setCookieHeaders.size() * sizeof(String);
+ for (auto& header : m_commonHeaders)
+ cost += header.value.sizeInBytes();
+
+ for (auto& header : m_uncommonHeaders) {
+ cost += header.key.sizeInBytes();
+ cost += header.value.sizeInBytes();
+ }
+
+ for (auto& header : m_setCookieHeaders)
+ cost += header.sizeInBytes();
+
+ return cost;
+}
+
String HTTPHeaderMap::getUncommonHeader(const String& name) const
{
auto index = m_uncommonHeaders.findIf([&](auto& header) {
diff --git a/src/bun.js/bindings/webcore/HTTPHeaderMap.h b/src/bun.js/bindings/webcore/HTTPHeaderMap.h
index 42fdbf41a..8522efdce 100644
--- a/src/bun.js/bindings/webcore/HTTPHeaderMap.h
+++ b/src/bun.js/bindings/webcore/HTTPHeaderMap.h
@@ -191,6 +191,8 @@ public:
WEBCORE_EXPORT bool contains(HTTPHeaderName) const;
WEBCORE_EXPORT bool remove(HTTPHeaderName);
+ size_t memoryCost() const;
+
// Instead of passing a string literal to any of these functions, just use a HTTPHeaderName instead.
template<size_t length> String get(const char (&)[length]) const = delete;
template<size_t length> void set(const char (&)[length], const String &) = delete;
diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
index 11e9157c1..eec128373 100644
--- a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
+++ b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
@@ -317,6 +317,12 @@ void JSFetchHeaders::finishCreation(VM& vm)
// static_assert(!std::is_base_of<ActiveDOMObject, FetchHeaders>::value, "Interface is not marked as [ActiveDOMObject] even though implementation class subclasses ActiveDOMObject.");
}
+void JSFetchHeaders::computeMemoryCost()
+{
+ m_memoryCost = wrapped().memoryCost();
+ globalObject()->vm().heap.reportExtraMemoryAllocated(m_memoryCost);
+}
+
JSObject* JSFetchHeaders::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return JSFetchHeadersPrototype::create(vm, &globalObject, JSFetchHeadersPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype()));
@@ -645,6 +651,17 @@ bool JSFetchHeadersOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> h
return false;
}
+template<typename Visitor>
+void JSFetchHeaders::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ auto* thisObject = jsCast<JSFetchHeaders*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.reportExtraMemoryVisited(thisObject->m_memoryCost);
+}
+
+DEFINE_VISIT_CHILDREN(JSFetchHeaders);
+
void JSFetchHeadersOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
{
auto* jsFetchHeaders = static_cast<JSFetchHeaders*>(handle.slot()->asCell());
diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.h b/src/bun.js/bindings/webcore/JSFetchHeaders.h
index 1611772ca..4efcc5f5f 100644
--- a/src/bun.js/bindings/webcore/JSFetchHeaders.h
+++ b/src/bun.js/bindings/webcore/JSFetchHeaders.h
@@ -42,6 +42,7 @@ public:
static void destroy(JSC::JSCell*);
DECLARE_INFO;
+ DECLARE_VISIT_CHILDREN;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
@@ -58,6 +59,9 @@ public:
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ size_t m_memoryCost { 0 };
+ void computeMemoryCost();
+
protected:
JSFetchHeaders(JSC::Structure*, JSDOMGlobalObject&, Ref<FetchHeaders>&&);
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 03cdab205..e5d7626d5 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -387,6 +387,7 @@ pub const VirtualMachine = struct {
standalone_module_graph: ?*bun.StandaloneModuleGraph = null,
hot_reload: bun.CLI.Command.HotReload = .none,
+ jsc: *JSC.VM = undefined,
/// hide bun:wrap from stack traces
/// bun:wrap is very noisy
@@ -1020,6 +1021,7 @@ pub const VirtualMachine = struct {
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
+ vm.jsc = vm.global.vm();
if (source_code_printer == null) {
var writer = try js_printer.BufferWriter.init(allocator);
@@ -1122,6 +1124,7 @@ pub const VirtualMachine = struct {
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
+ vm.jsc = vm.global.vm();
if (source_code_printer == null) {
var writer = try js_printer.BufferWriter.init(allocator);
@@ -1237,6 +1240,7 @@ pub const VirtualMachine = struct {
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
+ vm.jsc = vm.global.vm();
if (source_code_printer == null) {
var writer = try js_printer.BufferWriter.init(allocator);
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index c5e893a5a..604726c1e 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -1233,6 +1233,34 @@ pub const Blob = struct {
return blob_;
}
+ fn estimatedByteSize(this: *Blob) usize {
+ // in-memory size. not the size on disk.
+ if (this.size != Blob.max_size) {
+ return this.size;
+ }
+
+ var store = this.store orelse return 0;
+ if (store.data == .bytes) {
+ return store.data.bytes.len;
+ }
+
+ return 0;
+ }
+
+ pub fn estimatedSize(this: *Blob) callconv(.C) usize {
+ var size = this.estimatedByteSize() + @sizeOf(Blob);
+
+ if (this.store) |store| {
+ size += @sizeOf(Blob.Store);
+ size += switch (store.data) {
+ .bytes => store.data.bytes.stored_name.len,
+ .file => store.data.file.pathlike.path.slice().len,
+ };
+ }
+
+ return size + (this.content_type.len * @as(usize, @intFromBool(this.content_type_allocated)));
+ }
+
comptime {
if (!JSC.is_bindgen) {
_ = JSDOMFile__hasInstance;
diff --git a/src/bun.js/webcore/response.classes.ts b/src/bun.js/webcore/response.classes.ts
index abd29c654..6f3c05aa4 100644
--- a/src/bun.js/webcore/response.classes.ts
+++ b/src/bun.js/webcore/response.classes.ts
@@ -126,6 +126,7 @@ export default [
klass: {},
configurable: false,
structuredClone: { transferable: false, tag: 254 },
+ estimatedSize: true,
proto: {
text: { fn: "getText" },
json: { fn: "getJSON" },
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index d947a7d4e..0838a4580 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -631,6 +631,7 @@ pub const Fetch = struct {
promise: JSC.JSPromise.Strong,
concurrent_task: JSC.ConcurrentTask = .{},
poll_ref: JSC.PollRef = .{},
+ memory_reporter: *JSC.MemoryReportingAllocator,
/// For Http Client requests
/// when Content-Length is provided this represents the whole size of the request
/// If chunked encoded this will represent the total received size (ignoring the chunk headers)
@@ -691,18 +692,19 @@ pub const Fetch = struct {
}
fn clearData(this: *FetchTasklet) void {
+ const allocator = this.memory_reporter.allocator();
if (this.url_proxy_buffer.len > 0) {
- bun.default_allocator.free(this.url_proxy_buffer);
+ allocator.free(this.url_proxy_buffer);
this.url_proxy_buffer.len = 0;
}
if (this.hostname) |hostname| {
- bun.default_allocator.free(hostname);
+ allocator.free(hostname);
this.hostname = null;
}
- this.request_headers.entries.deinit(bun.default_allocator);
- this.request_headers.buf.deinit(bun.default_allocator);
+ this.request_headers.entries.deinit(allocator);
+ this.request_headers.buf.deinit(allocator);
this.request_headers = Headers{ .allocator = undefined };
if (this.http != null) {
@@ -710,7 +712,7 @@ pub const Fetch = struct {
}
if (this.metadata != null) {
- this.metadata.?.deinit();
+ this.metadata.?.deinit(allocator);
this.metadata = null;
}
@@ -730,8 +732,11 @@ pub const Fetch = struct {
}
pub fn deinit(this: *FetchTasklet) void {
- if (this.http) |http| this.javascript_vm.allocator.destroy(http);
- this.javascript_vm.allocator.destroy(this);
+ var reporter = this.memory_reporter;
+ if (this.http) |http| reporter.allocator().destroy(http);
+ reporter.allocator().destroy(this);
+ reporter.assert();
+ bun.default_allocator.destroy(reporter);
}
pub fn onBodyReceived(this: *FetchTasklet) void {
@@ -816,6 +821,7 @@ pub const Fetch = struct {
// we will reach here when not streaming
if (!this.result.has_more) {
var scheduled_response_buffer = this.scheduled_response_buffer.list;
+ this.memory_reporter.discard(scheduled_response_buffer.allocatedSlice());
// done resolve body
var old = body.value;
@@ -1006,6 +1012,7 @@ pub const Fetch = struct {
}
var scheduled_response_buffer = this.scheduled_response_buffer.list;
+ this.memory_reporter.discard(scheduled_response_buffer.allocatedSlice());
const response = Body.Value{
.InternalBlob = .{
.bytes = scheduled_response_buffer.toManaged(bun.default_allocator),
@@ -1024,26 +1031,22 @@ pub const Fetch = struct {
fn toResponse(this: *FetchTasklet, allocator: std.mem.Allocator) Response {
// at this point we always should have metadata
- std.debug.assert(this.metadata != null);
- if (this.metadata) |metadata| {
- const http_response = metadata.response;
- this.is_waiting_body = this.result.has_more;
- return Response{
- .allocator = allocator,
- .url = bun.String.createAtomIfPossible(metadata.href),
- .status_text = bun.String.createAtomIfPossible(http_response.status),
- .redirected = this.result.redirected,
- .body = .{
- .init = .{
- .headers = FetchHeaders.createFromPicoHeaders(http_response.headers),
- .status_code = @as(u16, @truncate(http_response.status_code)),
- },
- .value = this.toBodyValue(),
+ var metadata = this.metadata.?;
+ const http_response = metadata.response;
+ this.is_waiting_body = this.result.has_more;
+ return Response{
+ .allocator = allocator,
+ .url = bun.String.createAtomIfPossible(metadata.href),
+ .status_text = bun.String.createAtomIfPossible(http_response.status),
+ .redirected = this.result.redirected,
+ .body = .{
+ .init = .{
+ .headers = FetchHeaders.createFromPicoHeaders(http_response.headers),
+ .status_code = @as(u16, @truncate(http_response.status_code)),
},
- };
- }
-
- @panic("fetch metadata should be provided");
+ .value = this.toBodyValue(),
+ },
+ };
}
pub fn onResolve(this: *FetchTasklet) JSValue {
@@ -1063,25 +1066,25 @@ pub const Fetch = struct {
fetch_options: FetchOptions,
) !*FetchTasklet {
var jsc_vm = globalThis.bunVM();
- var fetch_tasklet = try jsc_vm.allocator.create(FetchTasklet);
+ var fetch_tasklet = try allocator.create(FetchTasklet);
fetch_tasklet.* = .{
.mutex = Mutex.init(),
.scheduled_response_buffer = .{
- .allocator = bun.default_allocator,
+ .allocator = fetch_options.memory_reporter.allocator(),
.list = .{
.items = &.{},
.capacity = 0,
},
},
.response_buffer = MutableString{
- .allocator = bun.default_allocator,
+ .allocator = fetch_options.memory_reporter.allocator(),
.list = .{
.items = &.{},
.capacity = 0,
},
},
- .http = try jsc_vm.allocator.create(HTTPClient.AsyncHTTP),
+ .http = try allocator.create(HTTPClient.AsyncHTTP),
.javascript_vm = jsc_vm,
.request_body = fetch_options.body,
.global_this = globalThis,
@@ -1091,6 +1094,7 @@ pub const Fetch = struct {
.signal = fetch_options.signal,
.hostname = fetch_options.hostname,
.tracker = JSC.AsyncTaskTracker.init(jsc_vm),
+ .memory_reporter = fetch_options.memory_reporter,
};
fetch_tasklet.signals = fetch_tasklet.signal_store.to();
@@ -1114,7 +1118,7 @@ pub const Fetch = struct {
}
fetch_tasklet.http.?.* = HTTPClient.AsyncHTTP.init(
- allocator,
+ fetch_options.memory_reporter.allocator(),
fetch_options.method,
fetch_options.url,
fetch_options.headers.entries,
@@ -1186,6 +1190,8 @@ pub const Fetch = struct {
globalThis: ?*JSGlobalObject,
// Custom Hostname
hostname: ?[]u8 = null,
+
+ memory_reporter: *JSC.MemoryReportingAllocator,
};
pub fn queue(
@@ -1283,17 +1289,24 @@ pub const Fetch = struct {
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
JSC.markBinding(@src());
+ const globalThis = ctx.ptr();
+ const arguments = callframe.arguments(2);
var exception_val = [_]JSC.C.JSValueRef{null};
var exception: JSC.C.ExceptionRef = &exception_val;
+ var memory_reporter = bun.default_allocator.create(JSC.MemoryReportingAllocator) catch @panic("out of memory");
+ var free_memory_reporter = false;
+ var allocator = memory_reporter.wrap(bun.default_allocator);
defer {
if (exception.* != null) {
+ free_memory_reporter = true;
ctx.throwValue(JSC.JSValue.c(exception.*));
}
- }
- const globalThis = ctx.ptr();
- const arguments = callframe.arguments(2);
+ memory_reporter.report(globalThis.vm());
+
+ if (free_memory_reporter) bun.default_allocator.destroy(memory_reporter);
+ }
if (arguments.len == 0) {
const err = JSC.toTypeError(.ERR_MISSING_ARGS, fetch_error_no_args, .{}, ctx);
@@ -1340,13 +1353,14 @@ pub const Fetch = struct {
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_blank_url, .{}, ctx);
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
+ free_memory_reporter = true;
return JSPromise.rejectedPromiseValue(globalThis, err);
}
if (request.url.hasPrefixComptime("data:")) {
- var url_slice = request.url.toUTF8WithoutRef(bun.default_allocator);
+ var url_slice = request.url.toUTF8WithoutRef(allocator);
defer url_slice.deinit();
var data_url = DataURL.parseWithoutCheck(url_slice.slice()) catch {
@@ -1355,16 +1369,16 @@ pub const Fetch = struct {
};
data_url.url = request.url;
- return dataURLResponse(data_url, globalThis, bun.default_allocator);
+ return dataURLResponse(data_url, globalThis, allocator);
}
- url = ZigURL.fromString(bun.default_allocator, request.url) catch {
+ url = ZigURL.fromString(allocator, request.url) catch {
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "fetch() URL is invalid", .{}, ctx);
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
-
+ free_memory_reporter = true;
return JSPromise.rejectedPromiseValue(
globalThis,
err,
@@ -1376,7 +1390,7 @@ pub const Fetch = struct {
if (args.nextEat()) |options| {
if (options.isObject() or options.jsType() == .DOMWrapper) {
if (options.fastGet(ctx.ptr(), .method)) |method_| {
- var slice_ = method_.toSlice(ctx.ptr(), getAllocator(ctx));
+ var slice_ = method_.toSlice(ctx.ptr(), allocator);
defer slice_.deinit();
method = Method.which(slice_.slice()) orelse .GET;
} else {
@@ -1392,7 +1406,7 @@ pub const Fetch = struct {
} else {
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
// an error was thrown
return JSC.JSValue.jsUndefined();
@@ -1404,24 +1418,24 @@ pub const Fetch = struct {
if (options.fastGet(ctx.ptr(), .headers)) |headers_| {
if (headers_.as(FetchHeaders)) |headers__| {
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
- hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
+ hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable;
// TODO: make this one pass
} else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| {
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
- hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
+ hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable;
headers__.deref();
} else if (request.headers) |head| {
if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
- hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
+ hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable;
}
- headers = Headers.from(head, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable;
}
} else if (request.headers) |head| {
- headers = Headers.from(head, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable;
}
if (options.get(ctx, "timeout")) |timeout_value| {
@@ -1462,14 +1476,14 @@ pub const Fetch = struct {
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "fetch() proxy URL is invalid", .{}, ctx);
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
- bun.default_allocator.free(url_proxy_buffer);
+ allocator.free(url_proxy_buffer);
return JSPromise.rejectedPromiseValue(globalThis, err);
}
defer href.deref();
- var buffer = std.fmt.allocPrint(bun.default_allocator, "{s}{}", .{ url_proxy_buffer, href }) catch {
+ var buffer = std.fmt.allocPrint(allocator, "{s}{}", .{ url_proxy_buffer, href }) catch {
globalThis.throwOutOfMemory();
return .zero;
};
@@ -1477,7 +1491,7 @@ pub const Fetch = struct {
is_file_url = url.isFile();
proxy = ZigURL.parse(buffer[url.href.len..]);
- bun.default_allocator.free(url_proxy_buffer);
+ allocator.free(url_proxy_buffer);
url_proxy_buffer = buffer;
}
}
@@ -1487,9 +1501,9 @@ pub const Fetch = struct {
body = request.body.value.useAsAnyBlob();
if (request.headers) |head| {
if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
- hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
+ hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable;
}
- headers = Headers.from(head, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable;
}
if (request.signal) |signal_| {
_ = signal_.ref();
@@ -1502,13 +1516,13 @@ pub const Fetch = struct {
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_blank_url, .{}, ctx);
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
return JSPromise.rejectedPromiseValue(globalThis, err);
}
if (str.hasPrefixComptime("data:")) {
- var url_slice = str.toUTF8WithoutRef(bun.default_allocator);
+ var url_slice = str.toUTF8WithoutRef(allocator);
defer url_slice.deinit();
var data_url = DataURL.parseWithoutCheck(url_slice.slice()) catch {
@@ -1517,13 +1531,13 @@ pub const Fetch = struct {
};
data_url.url = str;
- return dataURLResponse(data_url, globalThis, bun.default_allocator);
+ return dataURLResponse(data_url, globalThis, allocator);
}
- url = ZigURL.fromString(bun.default_allocator, str) catch {
+ url = ZigURL.fromString(allocator, str) catch {
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "fetch() URL is invalid", .{}, ctx);
return JSPromise.rejectedPromiseValue(globalThis, err);
@@ -1535,7 +1549,7 @@ pub const Fetch = struct {
if (args.nextEat()) |options| {
if (options.isObject() or options.jsType() == .DOMWrapper) {
if (options.fastGet(ctx.ptr(), .method)) |method_| {
- var slice_ = method_.toSlice(ctx.ptr(), getAllocator(ctx));
+ var slice_ = method_.toSlice(ctx.ptr(), allocator);
defer slice_.deinit();
method = Method.which(slice_.slice()) orelse .GET;
}
@@ -1549,7 +1563,7 @@ pub const Fetch = struct {
} else {
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
// an error was thrown
return JSC.JSValue.jsUndefined();
@@ -1559,16 +1573,16 @@ pub const Fetch = struct {
if (options.fastGet(ctx.ptr(), .headers)) |headers_| {
if (headers_.as(FetchHeaders)) |headers__| {
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
- hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
+ hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable;
// TODO: make this one pass
} else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| {
defer headers__.deref();
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
- hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
+ hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
+ headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable;
} else {
// Converting the headers failed; return null and
// let the set exception get thrown
@@ -1615,20 +1629,20 @@ pub const Fetch = struct {
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "fetch() proxy URL is invalid", .{}, ctx);
// clean hostname if any
if (hostname) |host| {
- bun.default_allocator.free(host);
+ allocator.free(host);
}
- bun.default_allocator.free(url_proxy_buffer);
-
+ allocator.free(url_proxy_buffer);
+ free_memory_reporter = true;
return JSPromise.rejectedPromiseValue(globalThis, err);
}
defer href.deref();
- var buffer = std.fmt.allocPrint(bun.default_allocator, "{s}{}", .{ url_proxy_buffer, href }) catch {
+ var buffer = std.fmt.allocPrint(allocator, "{s}{}", .{ url_proxy_buffer, href }) catch {
globalThis.throwOutOfMemory();
return .zero;
};
url = ZigURL.parse(buffer[0..url.href.len]);
proxy = ZigURL.parse(buffer[url.href.len..]);
- bun.default_allocator.free(url_proxy_buffer);
+ allocator.free(url_proxy_buffer);
url_proxy_buffer = buffer;
}
}
@@ -1643,6 +1657,7 @@ pub const Fetch = struct {
}
if (url.isEmpty()) {
+ free_memory_reporter = true;
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_blank_url, .{}, ctx);
return JSPromise.rejectedPromiseValue(globalThis, err);
}
@@ -1651,7 +1666,7 @@ pub const Fetch = struct {
// We don't pass along headers, we ignore method, we ignore status code...
// But it's better than status quo.
if (is_file_url) {
- defer bun.default_allocator.free(url_proxy_buffer);
+ defer allocator.free(url_proxy_buffer);
var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
const PercentEncoding = @import("../../url.zig").PercentEncoding;
var path_buf2: [bun.MAX_PATH_BYTES]u8 = undefined;
@@ -1706,22 +1721,24 @@ pub const Fetch = struct {
if (url.protocol.len > 0) {
if (!(url.isHTTP() or url.isHTTPS())) {
- defer bun.default_allocator.free(url_proxy_buffer);
+ defer allocator.free(url_proxy_buffer);
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "protocol must be http: or https:", .{}, ctx);
+ free_memory_reporter = true;
return JSPromise.rejectedPromiseValue(globalThis, err);
}
}
if (!method.hasRequestBody() and body.size() > 0) {
- defer bun.default_allocator.free(url_proxy_buffer);
+ defer allocator.free(url_proxy_buffer);
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_unexpected_body, .{}, ctx);
+ free_memory_reporter = true;
return JSPromise.rejectedPromiseValue(globalThis, err);
}
if (headers == null and body.size() > 0 and body.hasContentTypeFromUser()) {
headers = Headers.from(
null,
- bun.default_allocator,
+ allocator,
.{ .body = &body },
) catch unreachable;
}
@@ -1739,15 +1756,15 @@ pub const Fetch = struct {
const opened_fd = switch (opened_fd_res) {
.err => |err| {
- bun.default_allocator.free(url_proxy_buffer);
+ allocator.free(url_proxy_buffer);
const rejected_value = JSPromise.rejectedPromiseValue(globalThis, err.toJSC(globalThis));
body.detach();
if (headers) |*headers_| {
- headers_.buf.deinit(bun.default_allocator);
- headers_.entries.deinit(bun.default_allocator);
+ headers_.buf.deinit(allocator);
+ headers_.entries.deinit(allocator);
}
-
+ free_memory_reporter = true;
return rejected_value;
},
.result => |fd| fd,
@@ -1817,20 +1834,20 @@ pub const Fetch = struct {
switch (res) {
.err => |err| {
- bun.default_allocator.free(url_proxy_buffer);
-
+ allocator.free(url_proxy_buffer);
+ free_memory_reporter = true;
const rejected_value = JSPromise.rejectedPromiseValue(globalThis, err.toJSC(globalThis));
body.detach();
if (headers) |*headers_| {
- headers_.buf.deinit(bun.default_allocator);
- headers_.entries.deinit(bun.default_allocator);
+ headers_.buf.deinit(allocator);
+ headers_.entries.deinit(allocator);
}
return rejected_value;
},
.result => |result| {
body.detach();
- body.from(std.ArrayList(u8).fromOwnedSlice(bun.default_allocator, @constCast(result.slice())));
+ body.from(std.ArrayList(u8).fromOwnedSlice(allocator, @constCast(result.slice())));
http_body = .{ .AnyBlob = body };
},
}
@@ -1845,13 +1862,13 @@ pub const Fetch = struct {
// var resolve = FetchTasklet.FetchResolver.Class.make(ctx: js.JSContextRef, ptr: *ZigType)
_ = FetchTasklet.queue(
- default_allocator,
+ allocator,
globalThis,
.{
.method = method,
.url = url,
.headers = headers orelse Headers{
- .allocator = bun.default_allocator,
+ .allocator = allocator,
},
.body = http_body,
.timeout = std.time.ns_per_hour,
@@ -1864,6 +1881,7 @@ pub const Fetch = struct {
.signal = signal,
.globalThis = globalThis,
.hostname = hostname,
+ .memory_reporter = memory_reporter,
},
// Pass the Strong value instead of creating a new one, or else we
// will leak it
diff --git a/src/http_client_async.zig b/src/http_client_async.zig
index 26978db22..09319966e 100644
--- a/src/http_client_async.zig
+++ b/src/http_client_async.zig
@@ -2571,8 +2571,8 @@ fn cloneMetadata(this: *HTTPClient) void {
var builder = &builder_;
this.state.pending_response.count(builder);
builder.count(this.url.href);
- builder.allocate(bun.default_allocator) catch unreachable;
- var headers_buf = bun.default_allocator.alloc(picohttp.Header, this.state.pending_response.headers.len) catch unreachable;
+ builder.allocate(this.allocator) catch unreachable;
+ var headers_buf = this.allocator.alloc(picohttp.Header, this.state.pending_response.headers.len) catch unreachable;
const response = this.state.pending_response.clone(headers_buf, builder);
this.state.pending_response = response;
@@ -2665,9 +2665,9 @@ pub const HTTPClientResult = struct {
href: []const u8 = "",
headers_buf: []picohttp.Header = &.{},
- pub fn deinit(this: *ResultMetadata) void {
- if (this.metadata_buf.len > 0) bun.default_allocator.free(this.metadata_buf);
- if (this.headers_buf.len > 0) bun.default_allocator.free(this.headers_buf);
+ pub fn deinit(this: *ResultMetadata, allocator: std.mem.Allocator) void {
+ if (this.metadata_buf.len > 0) allocator.free(this.metadata_buf);
+ if (this.headers_buf.len > 0) allocator.free(this.headers_buf);
this.headers_buf = &.{};
this.metadata_buf = &.{};
this.href = "";
diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts
index 4c55e779a..3066b4b37 100644
--- a/test/js/bun/http/serve.test.ts
+++ b/test/js/bun/http/serve.test.ts
@@ -449,11 +449,12 @@ describe("streaming", () => {
const textToExpect = readFileSync(fixture, "utf-8");
await runTest(
{
- fetch(req) {
+ async fetch(req) {
return new Response(
new ReadableStream({
- start(controller) {
+ async start(controller) {
controller.enqueue(textToExpect.substring(0, 100));
+ await Bun.sleep(0);
queueMicrotask(() => {
controller.enqueue(textToExpect.substring(100));
controller.close();
@@ -502,8 +503,9 @@ describe("streaming", () => {
fetch(req) {
return new Response(
new ReadableStream({
- pull(controller) {
+ async pull(controller) {
controller.enqueue(textToExpect.substring(0, 100));
+ await Bun.sleep(0);
queueMicrotask(() => {
controller.enqueue(textToExpect.substring(100));
controller.close();
@@ -540,9 +542,9 @@ describe("streaming", () => {
async pull(controller) {
for (let chunk of chunks) {
controller.enqueue(Buffer.from(chunk));
- await 1;
+ await Bun.sleep(0);
}
- await 1;
+ await Bun.sleep(0);
controller.close();
},
}),
@@ -569,9 +571,9 @@ describe("streaming", () => {
new ReadableStream({
async pull(controller) {
controller.enqueue(textToExpect.substring(0, 100));
- await Promise.resolve();
+ await Bun.sleep(0);
controller.enqueue(textToExpect.substring(100));
- await Promise.resolve();
+ await Bun.sleep(0);
controller.close();
},
}),
@@ -598,7 +600,7 @@ describe("streaming", () => {
for (let i = 0; i < 10 && remain.length > 0; i++) {
controller.enqueue(remain.substring(0, 100));
remain = remain.substring(100);
- await new Promise(resolve => queueMicrotask(resolve));
+ await Bun.sleep(0);
}
controller.enqueue(remain);