diff options
author | 2022-03-14 23:43:20 -0700 | |
---|---|---|
committer | 2022-03-14 23:43:20 -0700 | |
commit | a168c513951a6b15fcb098131cd49dc30c5d8a62 (patch) | |
tree | fa021fc6e411bcada246f9b5f4358aa45d185e30 | |
parent | 5aae8726ef3de4b4be025796831abe2b37c2e032 (diff) | |
download | bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.tar.gz bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.tar.zst bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.zip |
Fix a couple memory leaks in `bun dev`
-rw-r--r-- | src/http.zig | 113 | ||||
-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 |
11 files changed, 301 insertions, 144 deletions
diff --git a/src/http.zig b/src/http.zig index 9258af461..64503986f 100644 --- a/src/http.zig +++ b/src/http.zig @@ -34,13 +34,16 @@ const DotEnv = @import("./env_loader.zig"); const mimalloc = @import("./allocators/mimalloc.zig"); const MacroMap = @import("./resolver/package_json.zig").MacroMap; const Analytics = @import("./analytics/analytics_thread.zig"); -const MiArena = @import("./mimalloc_arena.zig").Arena; -const Arena = MiArena; -const ArenaType = Arena; +const Arena = std.heap.ArenaAllocator; +const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena; const JSON = @import("./json_parser.zig"); const DateTime = @import("datetime"); const ThreadPool = @import("thread_pool"); const SourceMap = @import("./sourcemap/sourcemap.zig"); +const ObjectPool = @import("./pool.zig").ObjectPool; +const Lock = @import("./lock.zig").Lock; +const RequestDataPool = ObjectPool([32_000]u8, null, false, 1); + pub fn constStrToU8(s: string) []u8 { return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len]; } @@ -99,7 +102,8 @@ pub const RequestContext = struct { url: URLPath, conn: *tcp.Connection, allocator: std.mem.Allocator, - arena: *ArenaType, + arena: ThreadlocalArena, + req_body_node: *RequestDataPool.Node = undefined, log: logger.Log, bundler: *Bundler, keep_alive: bool = true, @@ -703,7 +707,7 @@ pub const RequestContext = struct { pub fn init( req: Request, - arena: *ArenaType, + arena: ThreadlocalArena, conn: *tcp.Connection, bundler_: *Bundler, watcher_: *Watcher, @@ -1163,6 +1167,11 @@ pub const RequestContext = struct { conn: tcp.Connection, params: Router.Param.List, + pub fn deinit(this: *JavaScriptHandler) void { + this.params.deinit(bun.default_allocator); + bun.default_allocator.destroy(this); + } + pub var javascript_vm: ?*JavaScript.VirtualMachine = null; pub const HandlerThread = struct { @@ -1195,7 +1204,7 @@ pub const RequestContext = struct { } pub fn handleJSErrorFmt(this: *HandlerThread, comptime step: Api.FallbackStep, err: anyerror, comptime fmt: string, args: anytype) !void { - var arena = ArenaType.init() catch unreachable; + var arena = ThreadlocalArena.init() catch unreachable; var allocator = arena.allocator(); defer arena.deinit(); @@ -1233,7 +1242,7 @@ pub const RequestContext = struct { } pub fn handleRuntimeJSError(this: *HandlerThread, js_value: JavaScript.JSValue, comptime step: Api.FallbackStep, comptime fmt: string, args: anytype) !void { - var arena = ArenaType.init() catch unreachable; + var arena = ThreadlocalArena.init() catch unreachable; var allocator = arena.allocator(); defer arena.deinit(); defer this.log.msgs.clearRetainingCapacity(); @@ -1279,7 +1288,7 @@ pub const RequestContext = struct { } pub fn handleFetchEventError(this: *HandlerThread, err: anyerror, js_value: JavaScript.JSValue, ctx: *RequestContext) !void { - var arena = MiArena.init() catch unreachable; + var arena = ThreadlocalArena.init() catch unreachable; var allocator = arena.allocator(); defer arena.deinit(); @@ -1425,11 +1434,11 @@ pub const RequestContext = struct { Output.Source.configureThread(); @import("javascript/jsc/javascript_core_c_api.zig").JSCInitialize(); - js_ast.Stmt.Data.Store.create(std.heap.c_allocator); - js_ast.Expr.Data.Store.create(std.heap.c_allocator); + js_ast.Stmt.Data.Store.create(bun.default_allocator); + js_ast.Expr.Data.Store.create(bun.default_allocator); var vm: *JavaScript.VirtualMachine = JavaScript.VirtualMachine.init( - std.heap.c_allocator, + bun.default_allocator, handler.args, null, handler.log, @@ -1439,7 +1448,7 @@ pub const RequestContext = struct { javascript_disabled = true; return; }; - vm.bundler.options.macro_remap = try handler.client_bundler.options.macro_remap.clone(std.heap.c_allocator); + vm.bundler.options.macro_remap = try handler.client_bundler.options.macro_remap.clone(bun.default_allocator); vm.bundler.macro_context = js_ast.Macro.MacroContext.init(&vm.bundler); vm.is_from_devserver = true; @@ -1497,7 +1506,7 @@ pub const RequestContext = struct { vm.global.vm().holdAPILock(handler, JavaScript.OpaqueWrap(HandlerThread, startJavaScript)); } - var __arena: MiArena = undefined; + var __arena: ThreadlocalArena = undefined; pub fn runLoop(vm: *JavaScript.VirtualMachine, thread: *HandlerThread) !void { var module_map = JavaScript.ZigGlobalObject.getModuleRegistryMap(vm.global); @@ -1510,7 +1519,7 @@ pub const RequestContext = struct { } while (true) { - __arena = MiArena.init() catch unreachable; + __arena = ThreadlocalArena.init() catch unreachable; JavaScript.VirtualMachine.vm.arena = &__arena; JavaScript.VirtualMachine.vm.has_loaded = true; JavaScript.VirtualMachine.vm.tick(); @@ -1533,6 +1542,9 @@ pub const RequestContext = struct { const original_origin = vm.origin; vm.origin = handler.ctx.origin; defer vm.origin = original_origin; + handler.ctx.arena = __arena; + handler.ctx.allocator = __arena.allocator(); + var req_body = handler.ctx.req_body_node; JavaScript.EventListenerMixin.emitFetchEvent( vm, &handler.ctx, @@ -1540,7 +1552,9 @@ pub const RequestContext = struct { thread, HandlerThread.handleFetchEventError, ) catch {}; + Server.current.releaseRequestDataPoolNode(req_body); JavaScript.VirtualMachine.vm.tick(); + handler.deinit(); } } @@ -1559,20 +1573,24 @@ pub const RequestContext = struct { ); return; } - var clone = try ctx.allocator.create(JavaScriptHandler); + + var clone = try server.allocator.create(JavaScriptHandler); clone.* = JavaScriptHandler{ .ctx = ctx.*, .conn = ctx.conn.*, .params = if (params.len > 0) - try params.clone(ctx.allocator) + try params.clone(server.allocator) else Router.Param.List{}, }; clone.ctx.conn = &clone.conn; - clone.ctx.matched_route.?.params = &clone.params; + // this is a threadlocal arena + clone.ctx.arena.deinit(); + clone.ctx.allocator = undefined; + if (!has_loaded_channel) { var handler_thread = try server.allocator.create(HandlerThread); @@ -1657,6 +1675,9 @@ pub const RequestContext = struct { clone.websocket = Websocket.Websocket.create(&clone.conn, SOCKET_FLAGS); clone.tombstone = false; + clone.ctx.allocator = undefined; + clone.ctx.arena.deinit(); + try open_websockets.append(clone); return clone; } @@ -1740,9 +1761,11 @@ pub const RequestContext = struct { threadlocal var websocket_handler_caches: CacheSet = undefined; threadlocal var websocket_printer: JSPrinter.BufferWriter = undefined; pub fn handle(self: *WebsocketHandler) void { + var req_body = self.ctx.req_body_node; defer { js_ast.Stmt.Data.Store.reset(); js_ast.Expr.Data.Store.reset(); + Server.current.releaseRequestDataPoolNode(req_body); } self.builder.printer = JSPrinter.BufferPrinter.init( @@ -1754,6 +1777,9 @@ pub const RequestContext = struct { fn _handle(handler: *WebsocketHandler) !void { var ctx = &handler.ctx; + ctx.arena = ThreadlocalArena.init() catch unreachable; + ctx.allocator = ctx.arena.allocator(); + var is_socket_closed = false; defer { websocket_handler_caches = handler.builder.bundler.resolver.caches; @@ -1921,7 +1947,7 @@ pub const RequestContext = struct { break :brk full_build.id; }; - var arena = MiArena.init() catch unreachable; + var arena = ThreadlocalArena.init() catch unreachable; defer arena.deinit(); var head = Websocket.WebsocketHeader{ @@ -2917,7 +2943,13 @@ pub const RequestContext = struct { if (input_path.len == 0) return ctx.sendNotFound(); const pathname = Fs.PathName.init(input_path); - const result = try ctx.buildFile(input_path, pathname.ext); + const result = ctx.buildFile(input_path, pathname.ext) catch |err| { + if (err == error.ModuleNotFound) { + return try ctx.sendNotFound(); + } + + return err; + }; switch (result.file.value) { .pending => |resolve_result| { @@ -3187,6 +3219,9 @@ pub const Server = struct { transform_options: Api.TransformOptions, javascript_enabled: bool = false, fallback_only: bool = false, + req_body_release_queue_mutex: Lock = Lock.init(), + req_body_release_queue: RequestDataPool.List = RequestDataPool.List{}, + websocket_threadpool: ThreadPool = ThreadPool.init(.{ // on macOS, the max stack size is 65520 bytes, // so we ask for 65519 @@ -3194,6 +3229,27 @@ pub const Server = struct { .max_threads = std.math.maxInt(u32), }), + pub var current: *Server = undefined; + + pub fn releaseRequestDataPoolNode(this: *Server, node: *RequestDataPool.Node) void { + this.req_body_release_queue_mutex.lock(); + defer this.req_body_release_queue_mutex.unlock(); + node.next = null; + + this.req_body_release_queue.prepend(node); + } + + pub fn cleanupRequestData(this: *Server) void { + this.req_body_release_queue_mutex.lock(); + defer this.req_body_release_queue_mutex.unlock(); + var any = false; + while (this.req_body_release_queue.popFirst()) |node| { + node.next = null; + node.release(); + any = true; + } + } + pub var editor: ?Editor = null; pub var editor_name: string = ""; pub var editor_path: string = ""; @@ -3660,6 +3716,9 @@ pub const Server = struct { server.handleConnection(&conn, comptime features); } + server.cleanupRequestData(); + var counter: usize = 0; + while (true) { defer Output.flush(); var conn = listener.accept(.{ .close_on_exec = true }) catch @@ -3668,11 +3727,11 @@ pub const Server = struct { disableSIGPIPESoClosingTheTabDoesntCrash(conn); server.handleConnection(&conn, comptime features); + counter +%= 1; + if (counter % 4 == 0) server.cleanupRequestData(); } } - threadlocal var req_buf: [32_000]u8 = undefined; - pub const ConnectionFeatures = struct { public_folder: PublicFolderPriority = PublicFolderPriority.none, filesystem_router: bool = false, @@ -3686,9 +3745,10 @@ pub const Server = struct { threadlocal var req_ctx_: RequestContext = undefined; pub fn handleConnection(server: *Server, conn: *tcp.Connection, comptime features: ConnectionFeatures) void { + var req_buf_node = RequestDataPool.get(server.allocator); // https://stackoverflow.com/questions/686217/maximum-on-http-header-values - var read_size = conn.client.read(&req_buf, SOCKET_FLAGS) catch { + var read_size = conn.client.read(&req_buf_node.data, SOCKET_FLAGS) catch { _ = conn.client.write(RequestContext.printStatusLine(400) ++ "\r\n\r\n", SOCKET_FLAGS) catch {}; return; }; @@ -3698,15 +3758,14 @@ pub const Server = struct { return; } - var req = picohttp.Request.parse(req_buf[0..read_size], &req_headers_buf) catch |err| { + var req = picohttp.Request.parse(req_buf_node.data[0..read_size], &req_headers_buf) catch |err| { _ = conn.client.write(RequestContext.printStatusLine(400) ++ "\r\n\r\n", SOCKET_FLAGS) catch {}; conn.client.deinit(); Output.printErrorln("ERR: {s}", .{@errorName(err)}); return; }; - var request_arena = server.allocator.create(Arena) catch unreachable; - request_arena.* = ArenaType.init() catch unreachable; + var request_arena = ThreadlocalArena.init() catch unreachable; req_ctx_ = RequestContext.init( req, @@ -3720,7 +3779,9 @@ pub const Server = struct { conn.client.deinit(); return; }; + var req_ctx = &req_ctx_; + req_ctx.req_body_node = req_buf_node; req_ctx.timer.reset(); const is_navigation_request = req_ctx_.isBrowserNavigation(); @@ -3748,6 +3809,7 @@ pub const Server = struct { defer { if (!req_ctx.controlled) { + req_ctx.req_body_node.release(); req_ctx.arena.deinit(); } } @@ -4015,6 +4077,7 @@ pub const Server = struct { server.bundler.* = try Bundler.init(allocator, &server.log, options, null, null); server.bundler.configureLinker(); try server.bundler.configureRouter(true); + Server.current = server; if (debug.dump_environment_variables) { server.bundler.dumpEnvironmentVariables(); 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; } } |