diff options
author | 2022-03-14 23:43:20 -0700 | |
---|---|---|
committer | 2022-03-14 23:43:20 -0700 | |
commit | a168c513951a6b15fcb098131cd49dc30c5d8a62 (patch) | |
tree | fa021fc6e411bcada246f9b5f4358aa45d185e30 /src/javascript | |
parent | 5aae8726ef3de4b4be025796831abe2b37c2e032 (diff) | |
download | bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.tar.gz bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.tar.zst bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.zip |
Fix a couple memory leaks in `bun dev`
Diffstat (limited to 'src/javascript')
-rw-r--r-- | src/javascript/jsc/api/router.zig | 46 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 68 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigGlobalObject.cpp | 61 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigSourceProvider.cpp | 14 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigSourceProvider.h | 4 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/exports.zig | 6 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.h | 3 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.zig | 1 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 127 |
10 files changed, 213 insertions, 119 deletions
diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig index ed2e0a9af..fb980fed0 100644 --- a/src/javascript/jsc/api/router.zig +++ b/src/javascript/jsc/api/router.zig @@ -37,11 +37,6 @@ query_string_map: ?QueryStringMap = null, param_map: ?QueryStringMap = null, params_list_holder: FilesystemRouter.Param.List = .{}, -script_src: ?string = null, -script_src_buf: [1024]u8 = undefined, - -script_src_buf_writer: ScriptSrcStream = undefined, - pub fn importRoute( this: *Router, ctx: js.JSContextRef, @@ -70,15 +65,25 @@ pub fn match( return null; } + var arg: JSC.JSValue = undefined; + if (FetchEvent.Class.loaded and js.JSValueIsObjectOfClass(ctx, arguments[0], FetchEvent.Class.get().*)) { - return matchFetchEvent(ctx, To.Zig.ptr(FetchEvent, arguments[0]), exception); + var fetch_event = To.Zig.ptr(FetchEvent, arguments[0]); + if (fetch_event.request_context != null) { + return matchFetchEvent(ctx, fetch_event, exception); + } + + // When disconencted, we still have a copy of the request data in here + arg = JSC.JSValue.fromRef(fetch_event.getRequest(ctx, null, null, null)); + } else { + arg = JSC.JSValue.fromRef(arguments[0]); } var router = JavaScript.VirtualMachine.vm.bundler.router orelse { JSError(getAllocator(ctx), "Bun.match needs a framework configured with routes", .{}, ctx, exception); return null; }; - var arg = JSC.JSValue.fromRef(arguments[0]); + var path_: ?ZigString.Slice = null; var pathname: string = ""; defer { @@ -128,7 +133,6 @@ pub fn match( instance.params_list_holder = params_list; instance.route = &instance.route_holder; instance.route_holder.params = &instance.params_list_holder; - instance.script_src_buf_writer = ScriptSrcStream{ .pos = 0, .buffer = std.mem.span(&instance.script_src_buf) }; return Instance.make(ctx, instance); } @@ -150,7 +154,7 @@ fn matchFetchEvent( fetch_event: *const FetchEvent, _: js.ExceptionRef, ) js.JSObjectRef { - return createRouteObject(ctx, fetch_event.request_context); + return createRouteObject(ctx, fetch_event.request_context.?); } fn createRouteObject(ctx: js.JSContextRef, req: *const http.RequestContext) js.JSValueRef { @@ -169,7 +173,6 @@ fn createRouteObjectFromMatch( router.* = Router{ .route = route, }; - router.script_src_buf_writer = ScriptSrcStream{ .pos = 0, .buffer = std.mem.span(&router.script_src_buf) }; return Instance.make(ctx, router); } @@ -221,9 +224,7 @@ pub const Instance = NewClass( }, }, .{ - .finalize = .{ - .rfn = finalize, - }, + .finalize = finalize, .import = .{ .rfn = importRoute, .ts = d.ts{ @@ -345,8 +346,9 @@ pub fn finalize( this.params_list_holder.deinit(bun.default_allocator); this.params_list_holder = .{}; this.needs_deinit = false; - bun.default_allocator.destroy(this); } + + bun.default_allocator.destroy(this); } pub fn getPathname( @@ -437,13 +439,13 @@ pub fn createQueryObject(ctx: js.JSContextRef, map: *QueryStringMap, _: js.Excep return value.asRef(); } -threadlocal var entry_point_tempbuf: [bun.MAX_PATH_BYTES]u8 = undefined; pub fn getScriptSrcString( comptime Writer: type, writer: Writer, file_path: string, client_framework_enabled: bool, ) void { + var entry_point_tempbuf: [bun.MAX_PATH_BYTES]u8 = undefined; // We don't store the framework config including the client parts in the server // instead, we just store a boolean saying whether we should generate this whenever the script is requested // this is kind of bad. we should consider instead a way to inline the contents of the script. @@ -454,11 +456,11 @@ pub fn getScriptSrcString( Fs.PathName.init(file_path), ), VirtualMachine.vm.origin, - ScriptSrcStream.Writer, + Writer, writer, ); } else { - JavaScript.Bun.getPublicPath(file_path, VirtualMachine.vm.origin, ScriptSrcStream.Writer, writer); + JavaScript.Bun.getPublicPath(file_path, VirtualMachine.vm.origin, Writer, writer); } } @@ -469,12 +471,12 @@ pub fn getScriptSrc( _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - const src = this.script_src orelse brk: { - getScriptSrcString(ScriptSrcStream.Writer, this.script_src_buf_writer.writer(), this.route.file_path, this.route.client_framework_enabled); - break :brk this.script_src_buf[0..this.script_src_buf_writer.pos]; - }; + var script_src_buffer = std.ArrayList(u8).init(bun.default_allocator); + + var writer = script_src_buffer.writer(); + getScriptSrcString(@TypeOf(&writer), &writer, this.route.file_path, this.route.client_framework_enabled); - return ZigString.init(src).toValueGC(ctx.ptr()).asObjectRef(); + return ZigString.init(script_src_buffer.toOwnedSlice()).toExternalValue(ctx.ptr()).asObjectRef(); } pub fn getParams( diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 6fa5bc511..cf9af5e4c 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -22,6 +22,7 @@ const Request = WebCore.Request; const Router = @import("./api/router.zig"); const FetchEvent = WebCore.FetchEvent; const Headers = WebCore.Headers.RefCountedHeaders; +const IdentityContext = @import("../../identity_context.zig").IdentityContext; const Body = WebCore.Body; const TaggedPointerTypes = @import("../../tagged_pointer.zig"); @@ -1869,13 +1870,78 @@ pub const MarkedArrayBuffer = struct { pub const toJS = toJSObjectRef; }; +// expensive heap reference-counted string type +// only use this for big strings +// like source code +// not little ones +pub const RefString = struct { + ptr: [*]const u8 = undefined, + len: usize = 0, + hash: Hash = 0, + + count: u32 = 0, + allocator: std.mem.Allocator, + + ctx: ?*anyopaque = null, + onBeforeDeinit: ?Callback = null, + + pub const Hash = u32; + pub const Map = std.HashMap(Hash, *JSC.RefString, IdentityContext(Hash), 80); + + pub const Callback = fn (ctx: *anyopaque, str: *RefString) void; + + pub fn computeHash(input: []const u8) u32 { + return @truncate(u32, std.hash.Wyhash.hash(0, input)); + } + + pub fn ref(this: *RefString) void { + this.count += 1; + } + + pub fn slice(this: *RefString) []const u8 { + this.ref(); + + return this.leak(); + } + + pub fn leak(this: RefString) []const u8 { + @setRuntimeSafety(false); + return this.ptr[0..this.len]; + } + + pub fn deref(this: *RefString) void { + this.count -|= 1; + + if (this.count == 0) { + this.deinit(); + } + } + + pub export fn RefString__free(this: *RefString, _: [*]const u8, _: usize) void { + this.deref(); + } + + pub fn deinit(this: *RefString) void { + if (this.onBeforeDeinit) |onBeforeDeinit| { + onBeforeDeinit(this.ctx.?, this); + } + + this.allocator.free(this.leak()); + this.allocator.destroy(this); + } +}; + +comptime { + std.testing.refAllDecls(RefString); +} + export fn MarkedArrayBuffer_deallocator(bytes_: *anyopaque, _: *anyopaque) void { const mimalloc = @import("../../allocators/mimalloc.zig"); // zig's memory allocator interface won't work here // mimalloc knows the size of things // but we don't mimalloc.mi_free(bytes_); - +} pub export fn BlobArrayBuffer_deallocator(_: *anyopaque, blob: *anyopaque) void { // zig's memory allocator interface won't work here // mimalloc knows the size of things diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index 6c2847596..ab35ff4d9 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -29,6 +29,7 @@ #include <JavaScriptCore/JSMicrotask.h> // #include <JavaScriptCore/JSContextInternal.h> #include <JavaScriptCore/CatchScope.h> +#include <JavaScriptCore/DeferredWorkTimer.h> #include <JavaScriptCore/JSInternalPromise.h> #include <JavaScriptCore/JSLock.h> #include <JavaScriptCore/JSMap.h> @@ -160,15 +161,16 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* g if (JSC::JSMap* oldMap = JSC::jsDynamicCast<JSC::JSMap*>( globalObject->vm(), obj->getDirect(globalObject->vm(), identifier))) { - oldMap->clear(globalObject); - // vm.finalizeSynchronousJSExecution(); + + vm.finalizeSynchronousJSExecution(); obj->putDirect(globalObject->vm(), identifier, map->clone(globalObject, globalObject->vm(), globalObject->mapStructure())); // vm.deleteAllLinkedCode(JSC::DeleteAllCodeEffort::DeleteAllCodeIfNotCollecting); // JSC::Heap::PreventCollectionScope(vm.heap); - + oldMap->clear(globalObject); + JSC::gcUnprotect(oldMap); // vm.heap.completeAllJITPlans(); // vm.forEachScriptExecutableSpace([&](auto &spaceAndSet) { @@ -181,48 +183,13 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* g // } // }); // }); + + // globalObject->vm().heap.deleteAllUnlinkedCodeBlocks( + // JSC::DeleteAllCodeEffort::PreventCollectionAndDeleteAllCode); } - // globalObject->vm().heap.deleteAllUnlinkedCodeBlocks( - // JSC::DeleteAllCodeEffort::PreventCollectionAndDeleteAllCode); - // vm.whenIdle([globalObject, oldMap, map]() { - // auto recordIdentifier = JSC::Identifier::fromString(globalObject->vm(), "module"); - - // JSC::JSModuleRecord *record; - // JSC::JSValue key; - // JSC::JSValue value; - // JSC::JSObject *mod; - // JSC::JSObject *nextObject; - // JSC::forEachInIterable( - // globalObject, oldMap, - // [&](JSC::VM &vm, JSC::JSGlobalObject *globalObject, JSC::JSValue nextValue) { - // nextObject = JSC::jsDynamicCast<JSC::JSObject *>(vm, nextValue); - // key = nextObject->getIndex(globalObject, static_cast<unsigned>(0)); - - // if (!map->has(globalObject, key)) { - // value = nextObject->getIndex(globalObject, static_cast<unsigned>(1)); - // mod = JSC::jsDynamicCast<JSC::JSObject *>(vm, value); - // if (mod) { - // record = JSC::jsDynamicCast<JSC::JSModuleRecord *>( - // vm, mod->getDirect(vm, recordIdentifier)); - // if (record) { - // auto code = &record->sourceCode(); - // if (code) { - - // Zig::SourceProvider *provider = - // reinterpret_cast<Zig::SourceProvider *>(code->provider()); - // // code->~SourceCode(); - // if (provider) { provider->freeSourceCode(); } - // } - // } - // } - // } - // }); - - // oldMap->clear(globalObject); - // } - // } - // map } + // map + // } return true; } @@ -317,7 +284,11 @@ JSC_DEFINE_CUSTOM_GETTER(property_lazyProcessGetter, globalObject->m_process = Zig::Process::create( vm, Zig::Process::createStructure(vm, globalObject, globalObject->objectPrototype())); - + // We must either: + // - GC it and re-create it + // - keep it alive forever + // I think it is more correct to keep it alive forever + JSC::gcProtect(globalObject->m_process); { auto jsClass = dot_env_class_ref; @@ -329,6 +300,8 @@ JSC_DEFINE_CUSTOM_GETTER(property_lazyProcessGetter, globalObject->m_process->putDirect(vm, JSC::Identifier::fromString(vm, "env"), JSC::JSValue(object), JSC::PropertyAttribute::DontDelete | 0); + + JSC::gcProtect(JSC::JSValue(object)); } return JSC::JSValue::encode(JSC::JSValue(globalObject->m_process)); diff --git a/src/javascript/jsc/bindings/ZigSourceProvider.cpp b/src/javascript/jsc/bindings/ZigSourceProvider.cpp index de46bde77..14976dbde 100644 --- a/src/javascript/jsc/bindings/ZigSourceProvider.cpp +++ b/src/javascript/jsc/bindings/ZigSourceProvider.cpp @@ -10,6 +10,8 @@ #include <wtf/Scope.h> #include <wtf/text/StringHash.h> +extern "C" void RefString__free(void*, void*, unsigned); + namespace Zig { using Base = JSC::SourceProvider; @@ -25,22 +27,20 @@ using SourceProviderSourceType = JSC::SourceProviderSourceType; static char* wasmSourceName = "[WebAssembly Source]"; static size_t wasmSourceName_len = 20; + Ref<SourceProvider> SourceProvider::create(ResolvedSource resolvedSource) { void* allocator = resolvedSource.allocator; JSC::SourceProviderSourceType sourceType = JSC::SourceProviderSourceType::Module; - WTF::StringImpl* stringImpl = nullptr; if (allocator) { Ref<WTF::ExternalStringImpl> stringImpl_ = WTF::ExternalStringImpl::create( resolvedSource.source_code.ptr, resolvedSource.source_code.len, - nullptr, - [allocator](void* str, void* ptr, unsigned int len) { - ZigString__free((const unsigned char*)ptr, len, allocator); - }); + allocator, + RefString__free); return adoptRef(*new SourceProvider( - resolvedSource, reinterpret_cast<WTF::StringImpl*>(stringImpl_.ptr()), + resolvedSource, stringImpl_, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(toString(resolvedSource.source_url))), toStringNotConst(resolvedSource.source_url), TextPosition(), sourceType)); @@ -48,7 +48,7 @@ Ref<SourceProvider> SourceProvider::create(ResolvedSource resolvedSource) Ref<WTF::ExternalStringImpl> stringImpl_ = WTF::ExternalStringImpl::createStatic( resolvedSource.source_code.ptr, resolvedSource.source_code.len); return adoptRef(*new SourceProvider( - resolvedSource, reinterpret_cast<WTF::StringImpl*>(stringImpl_.ptr()), + resolvedSource, stringImpl_, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(toString(resolvedSource.source_url))), toStringNotConst(resolvedSource.source_url), TextPosition(), sourceType)); diff --git a/src/javascript/jsc/bindings/ZigSourceProvider.h b/src/javascript/jsc/bindings/ZigSourceProvider.h index fc584db6f..37d21f5bc 100644 --- a/src/javascript/jsc/bindings/ZigSourceProvider.h +++ b/src/javascript/jsc/bindings/ZigSourceProvider.h @@ -63,11 +63,11 @@ public: void freeSourceCode(); private: - SourceProvider(ResolvedSource resolvedSource, WTF::StringImpl* sourceImpl, + SourceProvider(ResolvedSource resolvedSource, WTF::StringImpl& sourceImpl, const SourceOrigin& sourceOrigin, WTF::String&& sourceURL, const TextPosition& startPosition, JSC::SourceProviderSourceType sourceType) : Base(sourceOrigin, WTFMove(sourceURL), startPosition, sourceType) - , m_source(*sourceImpl) + , m_source(sourceImpl) { m_resolvedSource = resolvedSource; diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index 85ae5ac9e..dc9c94d9b 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -246,13 +246,11 @@ export fn ZigString__free(ptr: [*]const u8, len: usize, allocator_: ?*anyopaque) allocator.free(str); } -export fn ZigString__free_global(ptr: [*]const u8, len: usize) void { - var str = ptr[0..len]; +export fn ZigString__free_global(ptr: [*]const u8, _: usize) void { if (comptime Environment.allow_assert) { std.debug.assert(Mimalloc.mi_check_owned(ptr)); } - - default_allocator.free(str); + Mimalloc.mi_free(@intToPtr(*anyopaque, @ptrToInt(ptr))); } export fn Zig__getAPIGlobals(count: *usize) [*]JSC.C.JSClassRef { diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index dccb07f35..ee99ba75c 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1647230769 +//-- AUTOGENERATED FILE -- 1647318604 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index afe6eb575..fcdf4ad5e 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1647230769 +//-- AUTOGENERATED FILE -- 1647318604 #pragma once #include <stddef.h> @@ -527,6 +527,7 @@ CPP_DECL JSC__VM* JSC__VM__create(unsigned char HeapType0); CPP_DECL void JSC__VM__deferGC(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0)); CPP_DECL void JSC__VM__deinit(JSC__VM* arg0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__VM__deleteAllCode(JSC__VM* arg0, JSC__JSGlobalObject* arg1); +CPP_DECL void JSC__VM__doWork(JSC__VM* arg0); CPP_DECL void JSC__VM__drainMicrotasks(JSC__VM* arg0); CPP_DECL bool JSC__VM__executionForbidden(JSC__VM* arg0); CPP_DECL void JSC__VM__holdAPILock(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0)); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 3729be6b9..992313209 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -365,6 +365,7 @@ pub extern fn JSC__VM__create(HeapType0: u8) [*c]JSC__VM; pub extern fn JSC__VM__deferGC(arg0: [*c]JSC__VM, arg1: ?*anyopaque, ArgFn2: ?fn (?*anyopaque) callconv(.C) void) void; pub extern fn JSC__VM__deinit(arg0: [*c]JSC__VM, arg1: [*c]JSC__JSGlobalObject) void; pub extern fn JSC__VM__deleteAllCode(arg0: [*c]JSC__VM, arg1: [*c]JSC__JSGlobalObject) void; +pub extern fn JSC__VM__doWork(arg0: [*c]JSC__VM) void; pub extern fn JSC__VM__drainMicrotasks(arg0: [*c]JSC__VM) void; pub extern fn JSC__VM__executionForbidden(arg0: [*c]JSC__VM) bool; pub extern fn JSC__VM__holdAPILock(arg0: [*c]JSC__VM, arg1: ?*anyopaque, ArgFn2: ?fn (?*anyopaque) callconv(.C) void) void; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 0eedd8266..cb82c7400 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -688,7 +688,10 @@ pub const Bun = struct { arguments: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { - Global.mimalloc_cleanup(true); + // it should only force cleanup on thread exit + + Global.mimalloc_cleanup(false); + return ctx.ptr().vm().runGC(arguments.len > 0 and JSValue.fromRef(arguments[0]).toBoolean()).asRef(); } @@ -1809,6 +1812,8 @@ pub const VirtualMachine = struct { regular_event_loop: EventLoop = EventLoop{}, event_loop: *EventLoop = undefined, + ref_strings: JSC.RefString.Map = undefined, + source_mappings: SavedSourceMap = undefined, pub inline fn eventLoop(this: *VirtualMachine) *EventLoop { @@ -1889,7 +1894,9 @@ pub const VirtualMachine = struct { pub fn tick(this: *EventLoop) void { while (true) { this.tickConcurrent(); + while (this.tickWithCount() > 0) {} + this.tickConcurrent(); if (this.tickWithCount() == 0) break; @@ -1897,10 +1904,9 @@ pub const VirtualMachine = struct { } pub fn waitForPromise(this: *EventLoop, promise: *JSC.JSInternalPromise) void { - var _vm = this.global.vm(); - switch (promise.status(_vm)) { + switch (promise.status(this.global.vm())) { JSC.JSPromise.Status.Pending => { - while (promise.status(_vm) == .Pending) { + while (promise.status(this.global.vm()) == .Pending) { this.tick(); } }, @@ -1937,9 +1943,11 @@ pub const VirtualMachine = struct { } pub fn tick(this: *VirtualMachine) void { - this.eventLoop().tickConcurrent(); + this.eventLoop().tick(); + } - while (this.eventLoop().tickWithCount() > 0) {} + pub fn waitForPromise(this: *VirtualMachine, promise: *JSC.JSInternalPromise) void { + this.eventLoop().waitForPromise(promise); } pub fn waitForTasks(this: *VirtualMachine) void { @@ -2027,6 +2035,7 @@ pub const VirtualMachine = struct { .macros = MacroMap.init(allocator), .macro_entry_points = @TypeOf(VirtualMachine.vm.macro_entry_points).init(allocator), .origin_timer = std.time.Timer.start() catch @panic("Please don't mess with timers."), + .ref_strings = JSC.RefString.Map.init(allocator), }; VirtualMachine.vm.regular_event_loop.tasks = EventLoop.Queue.init( @@ -2076,6 +2085,47 @@ pub const VirtualMachine = struct { threadlocal var source_code_printer: ?*js_printer.BufferPrinter = null; + pub fn clearRefString(_: *anyopaque, ref_string: *JSC.RefString) void { + _ = VirtualMachine.vm.ref_strings.remove(ref_string.hash); + } + + pub fn refCountedResolvedSource(this: *VirtualMachine, code: []const u8, specifier: []const u8, source_url: []const u8, hash_: ?u32) ResolvedSource { + var source = this.refCountedString(code, hash_, true); + + return ResolvedSource{ + .source_code = ZigString.init(source.slice()), + .specifier = ZigString.init(specifier), + .source_url = ZigString.init(source_url), + .hash = source.hash, + .allocator = source, + }; + } + + pub fn refCountedString(this: *VirtualMachine, input_: []const u8, hash_: ?u32, comptime dupe: bool) *JSC.RefString { + const hash = hash_ orelse JSC.RefString.computeHash(input_); + + var entry = this.ref_strings.getOrPut(hash) catch unreachable; + if (!entry.found_existing) { + const input = if (comptime dupe) + (this.allocator.dupe(u8, input_) catch unreachable) + else + input_; + + var ref = this.allocator.create(JSC.RefString) catch unreachable; + ref.* = JSC.RefString{ + .allocator = this.allocator, + .ptr = input.ptr, + .len = input.len, + .hash = hash, + .ctx = this, + .onBeforeDeinit = VirtualMachine.clearRefString, + }; + entry.value_ptr.* = ref; + } + + return entry.value_ptr.*; + } + pub fn preflush(this: *VirtualMachine) void { // We flush on the next tick so that if there were any errors you can still see them this.blobs.?.temporary.reset() catch {}; @@ -2350,8 +2400,12 @@ pub const VirtualMachine = struct { return error.PrintingErrorWriteFailed; } + if (jsc_vm.has_loaded) { + return jsc_vm.refCountedResolvedSource(printer.ctx.written, specifier, path.text, null); + } + return ResolvedSource{ - .allocator = if (jsc_vm.has_loaded) &jsc_vm.allocator else null, + .allocator = null, .source_code = ZigString.init(jsc_vm.allocator.dupe(u8, printer.ctx.written) catch unreachable), .specifier = ZigString.init(specifier), .source_url = ZigString.init(path.text), @@ -2590,11 +2644,15 @@ pub const VirtualMachine = struct { return slice; } - // This double prints - pub fn promiseRejectionTracker(_: *JSGlobalObject, _: *JSPromise, _: JSPromiseRejectionOperation) callconv(.C) JSValue { - // VirtualMachine.vm.defaultErrorHandler(promise.result(global.vm()), null); - return JSValue.jsUndefined(); - } + // // This double prints + // pub fn promiseRejectionTracker(global: *JSGlobalObject, promise: *JSPromise, _: JSPromiseRejectionOperation) callconv(.C) JSValue { + // const result = promise.result(global.vm()); + // if (@enumToInt(VirtualMachine.vm.last_error_jsvalue) != @enumToInt(result)) { + // VirtualMachine.vm.defaultErrorHandler(result, null); + // } + + // return JSValue.jsUndefined(); + // } const main_file_name: string = "bun:main"; pub threadlocal var errors_stack: [256]*anyopaque = undefined; @@ -2725,6 +2783,8 @@ pub const VirtualMachine = struct { } pub fn defaultErrorHandler(this: *VirtualMachine, result: JSValue, exception_list: ?*ExceptionList) void { + this.last_error_jsvalue = result; + if (result.isException(this.global.vm())) { var exception = @ptrCast(*Exception, result.asVoid()); @@ -2763,26 +2823,14 @@ pub const VirtualMachine = struct { this.has_loaded_node_modules = true; promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(std.mem.span(bun_file_import_path))); - this.tick(); - - while (promise.status(this.global.vm()) == JSPromise.Status.Pending) { - this.tick(); - } - - if (promise.status(this.global.vm()) == JSPromise.Status.Rejected) { + this.waitForPromise(promise); + if (promise.status(this.global.vm()) == .Rejected) return promise; - } - - _ = promise.result(this.global.vm()); } promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(std.mem.span(main_file_name))); - this.tick(); - - while (promise.status(this.global.vm()) == JSPromise.Status.Pending) { - this.tick(); - } + this.waitForPromise(promise); return promise; } @@ -2990,6 +3038,11 @@ pub const VirtualMachine = struct { } } + pub fn reportUncaughtExceptio(_: *JSGlobalObject, exception: *JSC.Exception) JSValue { + VirtualMachine.vm.defaultErrorHandler(exception.value(), null); + return JSC.JSValue.jsUndefined(); + } + pub fn printStackTrace(comptime Writer: type, writer: Writer, trace: ZigStackTrace, comptime allow_ansi_colors: bool) !void { const stack = trace.frames(); if (stack.len > 0) { @@ -3359,9 +3412,7 @@ pub const EventListenerMixin = struct { comptime onError: fn (ctx: *CtxType, err: anyerror, value: JSValue, request_ctx: *http.RequestContext) anyerror!void, ) !void { if (comptime JSC.is_bindgen) unreachable; - defer { - if (request_context.has_called_done) request_context.arena.deinit(); - } + var listeners = vm.event_listeners.get(EventType.fetch) orelse (return onError(ctx, error.NoListeners, JSValue.jsUndefined(), request_context) catch {}); if (listeners.items.len == 0) return onError(ctx, error.NoListeners, JSValue.jsUndefined(), request_context) catch {}; const FetchEventRejectionHandler = struct { @@ -3370,7 +3421,7 @@ pub const EventListenerMixin = struct { @intToPtr(*CtxType, @ptrToInt(_ctx)), err, value, - fetch_event.request_context, + fetch_event.request_context.?, ) catch {}; } }; @@ -3386,9 +3437,11 @@ pub const EventListenerMixin = struct { }; var fetch_args: [1]js.JSObjectRef = undefined; - for (listeners.items) |listener_ref| { - fetch_args[0] = FetchEvent.Class.make(vm.global.ref(), fetch_event); + fetch_args[0] = FetchEvent.Class.make(vm.global.ref(), fetch_event); + JSC.C.JSValueProtect(vm.global.ref(), fetch_args[0]); + defer JSC.C.JSValueUnprotect(vm.global.ref(), fetch_args[0]); + for (listeners.items) |listener_ref| { vm.tick(); var result = js.JSObjectCallAsFunctionReturnValue(vm.global.ref(), listener_ref, null, 1, &fetch_args); vm.tick(); @@ -3399,7 +3452,7 @@ pub const EventListenerMixin = struct { if (fetch_event.rejected) return; if (promise.status(vm.global.vm()) == .Rejected) { - onError(ctx, error.JSError, promise.result(vm.global.vm()), fetch_event.request_context) catch {}; + onError(ctx, error.JSError, promise.result(vm.global.vm()), request_context) catch {}; return; } else { _ = promise.result(vm.global.vm()); @@ -3407,13 +3460,13 @@ pub const EventListenerMixin = struct { vm.waitForTasks(); - if (fetch_event.request_context.has_called_done) { + if (request_context.has_called_done) { break; } } - if (!fetch_event.request_context.has_called_done) { - onError(ctx, error.FetchHandlerRespondWithNeverCalled, JSValue.jsUndefined(), fetch_event.request_context) catch {}; + if (!request_context.has_called_done) { + onError(ctx, error.FetchHandlerRespondWithNeverCalled, JSValue.jsUndefined(), request_context) catch {}; return; } } |