aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-14 23:43:20 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-14 23:43:20 -0700
commita168c513951a6b15fcb098131cd49dc30c5d8a62 (patch)
treefa021fc6e411bcada246f9b5f4358aa45d185e30
parent5aae8726ef3de4b4be025796831abe2b37c2e032 (diff)
downloadbun-a168c513951a6b15fcb098131cd49dc30c5d8a62.tar.gz
bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.tar.zst
bun-a168c513951a6b15fcb098131cd49dc30c5d8a62.zip
Fix a couple memory leaks in `bun dev`
-rw-r--r--src/http.zig113
-rw-r--r--src/javascript/jsc/api/router.zig46
-rw-r--r--src/javascript/jsc/base.zig68
-rw-r--r--src/javascript/jsc/bindings/ZigGlobalObject.cpp61
-rw-r--r--src/javascript/jsc/bindings/ZigSourceProvider.cpp14
-rw-r--r--src/javascript/jsc/bindings/ZigSourceProvider.h4
-rw-r--r--src/javascript/jsc/bindings/exports.zig6
-rw-r--r--src/javascript/jsc/bindings/headers-cpp.h2
-rw-r--r--src/javascript/jsc/bindings/headers.h3
-rw-r--r--src/javascript/jsc/bindings/headers.zig1
-rw-r--r--src/javascript/jsc/javascript.zig127
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;
}
}