diff options
Diffstat (limited to 'src')
m--------- | src/bun.js/WebKit | 0 | ||||
-rw-r--r-- | src/bun.js/api/bun/socket.zig | 3 | ||||
-rw-r--r-- | src/bun.js/api/server.zig | 11 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 17 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 13 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 4 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 2 | ||||
-rw-r--r-- | src/bun.js/event_loop.zig | 128 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 2 |
10 files changed, 174 insertions, 8 deletions
diff --git a/src/bun.js/WebKit b/src/bun.js/WebKit -Subproject 6e5bbee4a3f5fe81a095067e5d0f8d8f891f79d +Subproject 7e5db543dbeb0f5510c18b83b94c79ca391514f diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index 78829f428..fa4411cc0 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -409,6 +409,7 @@ pub const Listener = struct { ctx_opts.ssl_prefer_low_memory_usage = @boolToInt(ssl_config.low_memory_mode); } + globalObject.bunVM().eventLoop().ensureWaker(); socket.socket_context = uws.us_create_socket_context(@boolToInt(ssl_enabled), uws.Loop.get().?, @sizeOf(usize), ctx_opts); if (ssl) |ssl_config| { @@ -661,6 +662,8 @@ pub const Listener = struct { ctx_opts.ssl_prefer_low_memory_usage = @boolToInt(ssl_config.low_memory_mode); } + globalObject.bunVM().eventLoop().ensureWaker(); + var socket_context = uws.us_create_socket_context(@boolToInt(ssl_enabled), uws.Loop.get().?, @sizeOf(usize), ctx_opts).?; var connection: Listener.UnixOrHost = if (port) |port_| .{ .host = .{ .host = (hostname_or_unix.cloneIfNeeded(bun.default_allocator) catch unreachable).slice(), .port = port_ }, diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index fb991481f..e2892f88f 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -4169,6 +4169,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } pub fn onRequestComplete(this: *ThisServer) void { + this.vm.eventLoop().processGCTimer(); + this.pending_requests -= 1; this.deinitIfWeCan(); } @@ -4567,7 +4569,14 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { this.config.hostname; this.ref(); - this.vm.autoGarbageCollect(); + + // Starting up an HTTP server is a good time to GC + if (this.vm.aggressive_garbage_collection == .aggressive) { + this.vm.autoGarbageCollect(); + } else { + this.vm.eventLoop().performGC(); + } + this.app.listenWithConfig(*ThisServer, this, onListen, .{ .port = this.config.port, .host = host, diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index e2e42f38c..3af4b8421 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -3432,3 +3432,20 @@ restart: return; } } + +extern "C" size_t JSC__VM__blockBytesAllocated(JSC__VM* vm) +{ +#if ENABLE(RESOURCE_USAGE) + return vm->heap.blockBytesAllocated(); +#else + return 0; +#endif +} +extern "C" size_t JSC__VM__externalMemorySize(JSC__VM* vm) +{ +#if ENABLE(RESOURCE_USAGE) + return vm->heap.externalMemorySize(); +#else + return 0; +#endif +}
\ No newline at end of file diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 15e15b032..d17cc5382 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3385,7 +3385,18 @@ pub const VM = extern struct { vm, }); } - pub const Extern = [_][]const u8{ "collectAsync", "heapSize", "releaseWeakRefs", "throwError", "doWork", "deferGC", "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" }; + + pub fn externalMemorySize(vm: *VM) usize { + return cppFn("externalMemorySize", .{vm}); + } + + /// `RESOURCE_USAGE` build option in JavaScriptCore is required for this function + /// This is faster than checking the heap size + pub fn blockBytesAllocated(vm: *VM) usize { + return cppFn("blockBytesAllocated", .{vm}); + } + + pub const Extern = [_][]const u8{ "collectAsync", "externalMemorySize", "blockBytesAllocated", "heapSize", "releaseWeakRefs", "throwError", "doWork", "deferGC", "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" }; }; pub const ThrowScope = extern struct { diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index e35afe0f0..34fdaba99 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1669793662 +//-- AUTOGENERATED FILE -- 1669880224 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 399fc54a6..75701502d 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1669793662 +//-- AUTOGENERATED FILE -- 1669880224 #pragma once #include <stddef.h> @@ -337,6 +337,7 @@ CPP_DECL JSC__JSValue JSC__Exception__value(JSC__Exception* arg0); #pragma mark - JSC::VM +CPP_DECL size_t JSC__VM__blockBytesAllocated(JSC__VM* arg0); CPP_DECL void JSC__VM__clearExecutionTimeLimit(JSC__VM* arg0); CPP_DECL void JSC__VM__collectAsync(JSC__VM* arg0); CPP_DECL JSC__VM* JSC__VM__create(unsigned char HeapType0); @@ -346,6 +347,7 @@ 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 size_t JSC__VM__externalMemorySize(JSC__VM* arg0); CPP_DECL size_t JSC__VM__heapSize(JSC__VM* arg0); CPP_DECL void JSC__VM__holdAPILock(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0)); CPP_DECL bool JSC__VM__isEntered(JSC__VM* arg0); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index e99db7ef0..b0bfadae9 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -254,6 +254,7 @@ pub extern fn JSC__JSValue__toZigString(JSValue0: JSC__JSValue, arg1: [*c]ZigStr pub extern fn JSC__Exception__create(arg0: ?*JSC__JSGlobalObject, arg1: [*c]JSC__JSObject, StackCaptureAction2: u8) [*c]JSC__Exception; pub extern fn JSC__Exception__getStackTrace(arg0: [*c]JSC__Exception, arg1: [*c]ZigStackTrace) void; pub extern fn JSC__Exception__value(arg0: [*c]JSC__Exception) JSC__JSValue; +pub extern fn JSC__VM__blockBytesAllocated(arg0: [*c]JSC__VM) usize; pub extern fn JSC__VM__clearExecutionTimeLimit(arg0: [*c]JSC__VM) void; pub extern fn JSC__VM__collectAsync(arg0: [*c]JSC__VM) void; pub extern fn JSC__VM__create(HeapType0: u8) [*c]JSC__VM; @@ -263,6 +264,7 @@ pub extern fn JSC__VM__deleteAllCode(arg0: [*c]JSC__VM, arg1: ?*JSC__JSGlobalObj 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__externalMemorySize(arg0: [*c]JSC__VM) usize; pub extern fn JSC__VM__heapSize(arg0: [*c]JSC__VM) usize; pub extern fn JSC__VM__holdAPILock(arg0: [*c]JSC__VM, arg1: ?*anyopaque, ArgFn2: ?fn (?*anyopaque) callconv(.C) void) void; pub extern fn JSC__VM__isEntered(arg0: [*c]JSC__VM) bool; diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index f9f11c943..aac1e3dcd 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -220,6 +220,21 @@ pub const EventLoop = struct { start_server_on_next_tick: bool = false, defer_count: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0), + gc_timer: *uws.Timer = undefined, + gc_last_heap_size: usize = 0, + gc_last_heap_size_on_repeating_timer: usize = 0, + heap_size_didnt_change_for_repeating_timer_ticks_count: u8 = 0, + gc_timer_state: GCTimerState = GCTimerState.pending, + gc_repeating_timer: *uws.Timer = undefined, + gc_timer_interval: i32 = 0, + gc_repeating_timer_fast: bool = true, + + pub const GCTimerState = enum { + pending, + scheduled, + run_on_next_tick, + }; + pub const Queue = std.fifo.LinearFifo(Task, .Dynamic); pub fn tickWithCount(this: *EventLoop) u32 { @@ -333,14 +348,59 @@ pub const EventLoop = struct { var loop = this.virtual_machine.uws_event_loop.?; if (loop.num_polls > 0 or loop.active > 0) { loop.tick(); + this.processGCTimer(); // this.afterUSocketsTick(); } } + pub fn scheduleGCTimer(this: *EventLoop) void { + this.gc_timer_state = .scheduled; + this.gc_timer.set(this, onGCTimer, 16, 0); + } + + pub fn processGCTimer(this: *EventLoop) void { + var vm = this.virtual_machine.global.vm(); + const this_heap_size = vm.blockBytesAllocated(); + const prev = this.gc_last_heap_size; + + switch (this.gc_timer_state) { + .run_on_next_tick => { + if (this_heap_size > prev) { + this.scheduleGCTimer(); + this.updateGCRepeatTimer(.fast); + } else { + this.gc_timer_state = .pending; + } + vm.collectAsync(); + this.gc_last_heap_size = this_heap_size; + }, + .pending => { + if (this_heap_size > prev) { + this.updateGCRepeatTimer(.fast); + + if (this_heap_size > prev * 2) { + this.performGC(); + } else { + this.scheduleGCTimer(); + } + } + }, + .scheduled => { + if (this_heap_size > prev * 2) { + this.updateGCRepeatTimer(.fast); + this.performGC(); + } + }, + } + } + // TODO: fix this technical debt pub fn tick(this: *EventLoop) void { var ctx = this.virtual_machine; this.tickConcurrent(); + + this.processGCTimer(); + var global_vm = ctx.global.vm(); while (true) { while (this.tickWithCount() > 0) { @@ -373,10 +433,6 @@ pub const EventLoop = struct { if (loop.active > 0 or (ctx.us_loop_reference_count > 0 and !ctx.is_us_loop_entered and (loop.num_polls > 0 or this.start_server_on_next_tick))) { if (this.tickConcurrentWithCount() > 0) { this.tick(); - } else { - if ((@intCast(c_ulonglong, ctx.uws_event_loop.?.internal_loop_data.iteration_nr) % 1_000) == 1) { - _ = ctx.global.vm().runGC(true); - } } ctx.is_us_loop_entered = true; @@ -427,11 +483,75 @@ pub const EventLoop = struct { if (this.virtual_machine.uws_event_loop == null) { var actual = uws.Loop.get().?; this.virtual_machine.uws_event_loop = actual; + this.gc_timer = uws.Timer.create(actual, this); + this.gc_repeating_timer = uws.Timer.create(actual, this); + + var gc_timer_interval: i32 = 1000; + if (this.virtual_machine.bundler.env.map.get("BUN_GC_TIMER_INTERVAL")) |timer| { + if (std.fmt.parseInt(i32, timer, 10)) |parsed| { + if (parsed > 0) { + gc_timer_interval = parsed; + } + } else |_| {} + } + this.gc_repeating_timer.set(this, onGCRepeatingTimer, gc_timer_interval, gc_timer_interval); + this.gc_timer_interval = gc_timer_interval; // _ = actual.addPostHandler(*JSC.EventLoop, this, JSC.EventLoop.afterUSocketsTick); // _ = actual.addPreHandler(*JSC.VM, this.virtual_machine.global.vm(), JSC.VM.drainMicrotasks); } } + pub fn onGCTimer(timer: *uws.Timer) callconv(.C) void { + var this = timer.as(*EventLoop); + this.gc_timer_state = .run_on_next_tick; + } + + // We want to always run GC once in awhile + // But if you have a long-running instance of Bun, you don't want the + // program constantly using CPU doing GC for no reason + // + // So we have two settings for this GC timer: + // + // - Fast: GC runs every 1 second + // - Slow: GC runs every 30 seconds + // + // When the heap size is increasing, we always switch to fast mode + // When the heap size has been the same or less for 30 seconds, we switch to slow mode + pub fn updateGCRepeatTimer(this: *EventLoop, comptime setting: @Type(.EnumLiteral)) void { + if (setting == .fast and !this.gc_repeating_timer_fast) { + this.gc_repeating_timer_fast = true; + this.gc_repeating_timer.set(this, onGCRepeatingTimer, this.gc_timer_interval, this.gc_timer_interval); + this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0; + } else if (setting == .slow and this.gc_repeating_timer_fast) { + this.gc_repeating_timer_fast = false; + this.gc_repeating_timer.set(this, onGCRepeatingTimer, 30_000, 30_000); + this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0; + } + } + + pub fn onGCRepeatingTimer(timer: *uws.Timer) callconv(.C) void { + var this = timer.as(*EventLoop); + const prev_heap_size = this.gc_last_heap_size_on_repeating_timer; + this.performGC(); + this.gc_last_heap_size_on_repeating_timer = this.gc_last_heap_size; + if (prev_heap_size == this.gc_last_heap_size_on_repeating_timer) { + this.heap_size_didnt_change_for_repeating_timer_ticks_count +|= 1; + if (this.heap_size_didnt_change_for_repeating_timer_ticks_count >= 30) { + // make the timer interval longer + this.updateGCRepeatTimer(.slow); + } + } else { + this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0; + this.updateGCRepeatTimer(.fast); + } + } + + /// Asynchronously run the garbage collector and track how much memory is now allocated + pub fn performGC(this: *EventLoop) void { + this.global.vm().collectAsync(); + this.gc_last_heap_size = this.global.vm().blockBytesAllocated(); + } + pub fn enqueueTaskConcurrent(this: *EventLoop, task: *ConcurrentTask) void { JSC.markBinding(@src()); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 6f966df2b..54fec7a96 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -1333,6 +1333,7 @@ pub const VirtualMachine = struct { // pending_internal_promise can change if hot module reloading is enabled if (this.bun_watcher != null) { + this.eventLoop().performGC(); switch (this.pending_internal_promise.status(this.global.vm())) { JSC.JSPromise.Status.Pending => { while (this.pending_internal_promise.status(this.global.vm()) == .Pending) { @@ -1346,6 +1347,7 @@ pub const VirtualMachine = struct { else => {}, } } else { + this.eventLoop().performGC(); this.waitForPromise(promise); } |