aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/event_loop.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/event_loop.zig')
-rw-r--r--src/bun.js/event_loop.zig128
1 files changed, 124 insertions, 4 deletions
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());