Compare commits

...

4 Commits

Author SHA1 Message Date
autofix-ci[bot]
6ee8c182a5 [autofix.ci] apply automated fixes 2025-09-29 09:31:04 +00:00
Claude Bot
3b18c92c61 refactor: improve GC timer implementation robustness
- Add named constants for GC timer intervals (GC_SINGLE_SHOT_DELAY_MS, GC_SLOW_INTERVAL_MS)
- Fix pointer-to-local issue in timer updates by using timer's own next field
- Clear timer state in deinit to avoid stale state reads post-teardown
- Ensure timer.next field is authoritative storage for next fire time

These changes improve code maintainability and prevent potential issues
with stack-local temporaries being passed to timer update functions.
2025-09-29 09:29:22 +00:00
Claude Bot
2e859de67b fix: properly handle timer rearm in EventLoopTimer system
- Fix timer rearm handling in Timer.All.drainTimers()
- Set timer state to PENDING before reinserting on rearm
- Update GC timer to use proper rearm return value
- Remove manual timer insertion workarounds

This fixes test failures caused by timer state management issues
when timers try to reschedule themselves.
2025-09-29 04:44:11 +00:00
Claude Bot
00bd64a32c refactor: migrate GarbageCollectionController to use EventLoopTimer
This refactors the GarbageCollectionController to use the centralized EventLoopTimer
system instead of uws.Timer, providing better integration with Bun's event loop.

Changes:
- Replace gc_timer and gc_repeating_timer from *uws.Timer to EventLoopTimer structs
- Add GCTimer and GCRepeatingTimer tags to EventLoopTimer.Tag enum
- Update timer callbacks to match EventLoopTimer's fire() pattern
- Fix timer scheduling to use vm.timer.insert() and vm.timer.update()
- Fix bug in Timer.All.drainTimers() where rearm case wasn't handled properly
- Properly manage timer state transitions (PENDING -> ACTIVE -> FIRED)

The refactoring maintains all existing GC timer functionality including:
- Adaptive timing based on heap growth patterns
- Configuration via BUN_GC_TIMER_INTERVAL and BUN_GC_TIMER_DISABLE
- Fast/slow mode switching for long-running processes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 11:15:11 +00:00
4 changed files with 95 additions and 19 deletions

View File

@@ -298,8 +298,15 @@ pub const All = struct {
while (this.next(&has_set_now, &now)) |t| {
switch (t.fire(&now, vm)) {
.disarm => {},
.rearm => {},
.disarm => {
// Timer is done, state should already be set to FIRED
},
.rearm => |next_time| {
// Reinsert the timer with the new time
t.next = next_time;
t.state = .PENDING; // Reset state before inserting
this.insert(t);
},
}
}
}

View File

@@ -69,6 +69,8 @@ pub const Tag = if (Environment.isWindows) enum {
DateHeaderTimer,
BunTest,
EventLoopDelayMonitor,
GCTimer,
GCRepeatingTimer,
pub fn Type(comptime T: Tag) type {
return switch (T) {
@@ -94,6 +96,7 @@ pub const Tag = if (Environment.isWindows) enum {
.DateHeaderTimer => jsc.API.Timer.DateHeaderTimer,
.BunTest => jsc.Jest.bun_test.BunTest,
.EventLoopDelayMonitor => jsc.API.Timer.EventLoopDelayMonitor,
.GCTimer, .GCRepeatingTimer => bun.jsc.GarbageCollectionController,
};
}
} else enum {
@@ -117,6 +120,8 @@ pub const Tag = if (Environment.isWindows) enum {
DateHeaderTimer,
BunTest,
EventLoopDelayMonitor,
GCTimer,
GCRepeatingTimer,
pub fn Type(comptime T: Tag) type {
return switch (T) {
@@ -141,6 +146,7 @@ pub const Tag = if (Environment.isWindows) enum {
.DateHeaderTimer => jsc.API.Timer.DateHeaderTimer,
.BunTest => jsc.Jest.bun_test.BunTest,
.EventLoopDelayMonitor => jsc.API.Timer.EventLoopDelayMonitor,
.GCTimer, .GCRepeatingTimer => bun.jsc.GarbageCollectionController,
};
}
};
@@ -227,6 +233,14 @@ pub fn fire(self: *Self, now: *const timespec, vm: *VirtualMachine) Arm {
monitor.onFire(vm, now);
return .disarm;
},
.GCTimer => {
const gc = @as(*bun.jsc.GarbageCollectionController, @fieldParentPtr("gc_timer", self));
return gc.onGCTimer(now, vm);
},
.GCRepeatingTimer => {
const gc = @as(*bun.jsc.GarbageCollectionController, @fieldParentPtr("gc_repeating_timer", self));
return gc.onGCRepeatingTimer(now, vm);
},
inline else => |t| {
if (@FieldType(t.Type(), "event_loop_timer") != Self) {
@compileError(@typeName(t.Type()) ++ " has wrong type for 'event_loop_timer'");

View File

@@ -20,20 +20,32 @@
const GarbageCollectionController = @This();
gc_timer: *uws.Timer = undefined,
// Timer interval constants
pub const GC_SINGLE_SHOT_DELAY_MS: i32 = 16;
pub const GC_SLOW_INTERVAL_MS: i32 = 30_000;
gc_timer: bun.api.Timer.EventLoopTimer = .{
.tag = .GCTimer,
.next = .epoch,
.state = .PENDING,
.heap = .{},
},
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_repeating_timer: bun.api.Timer.EventLoopTimer = .{
.tag = .GCRepeatingTimer,
.next = .epoch,
.state = .PENDING,
.heap = .{},
},
gc_timer_interval: i32 = 0,
gc_repeating_timer_fast: bool = true,
disabled: bool = false,
pub fn init(this: *GarbageCollectionController, vm: *VirtualMachine) void {
const actual = uws.Loop.get();
this.gc_timer = uws.Timer.createFallthrough(actual, this);
this.gc_repeating_timer = uws.Timer.createFallthrough(actual, this);
actual.internal_loop_data.jsc_vm = vm.jsc_vm;
if (comptime Environment.isDebug) {
@@ -54,28 +66,53 @@ pub fn init(this: *GarbageCollectionController, vm: *VirtualMachine) void {
this.disabled = vm.transpiler.env.has("BUN_GC_TIMER_DISABLE");
if (!this.disabled)
this.gc_repeating_timer.set(this, onGCRepeatingTimer, gc_timer_interval, gc_timer_interval);
if (!this.disabled) {
const now = bun.timespec.now();
this.gc_repeating_timer.next = now.addMs(@intCast(gc_timer_interval));
vm.timer.insert(&this.gc_repeating_timer);
}
}
pub fn deinit(this: *GarbageCollectionController) void {
this.gc_timer.deinit(true);
this.gc_repeating_timer.deinit(true);
const vm = this.bunVM();
if (this.gc_timer.state == .ACTIVE) {
vm.timer.remove(&this.gc_timer);
}
// Clear local state to avoid stale reads post-teardown
this.gc_timer.state = .PENDING;
this.gc_timer.next = .epoch;
if (this.gc_repeating_timer.state == .ACTIVE) {
vm.timer.remove(&this.gc_repeating_timer);
}
// Clear local state to avoid stale reads post-teardown
this.gc_repeating_timer.state = .PENDING;
this.gc_repeating_timer.next = .epoch;
}
pub fn scheduleGCTimer(this: *GarbageCollectionController) void {
this.gc_timer_state = .scheduled;
this.gc_timer.set(this, onGCTimer, 16, 0);
const vm = this.bunVM();
const now = bun.timespec.now();
this.gc_timer.next = now.addMs(GC_SINGLE_SHOT_DELAY_MS);
if (this.gc_timer.state == .ACTIVE) {
vm.timer.update(&this.gc_timer, &this.gc_timer.next);
} else {
vm.timer.insert(&this.gc_timer);
}
}
pub fn bunVM(this: *GarbageCollectionController) *VirtualMachine {
return @alignCast(@fieldParentPtr("gc_controller", this));
}
pub fn onGCTimer(timer: *uws.Timer) callconv(.C) void {
var this = timer.as(*GarbageCollectionController);
if (this.disabled) return;
pub fn onGCTimer(this: *GarbageCollectionController, now: *const bun.timespec, vm: *VirtualMachine) bun.api.Timer.EventLoopTimer.Arm {
_ = now;
_ = vm;
this.gc_timer.state = .FIRED;
if (this.disabled) return .disarm;
this.gc_timer_state = .run_on_next_tick;
return .disarm;
}
// We want to always run GC once in awhile
@@ -90,32 +127,50 @@ pub fn onGCTimer(timer: *uws.Timer) callconv(.C) void {
// 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: *GarbageCollectionController, comptime setting: @Type(.enum_literal)) void {
const vm = this.bunVM();
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);
const now = bun.timespec.now();
this.gc_repeating_timer.next = now.addMs(@intCast(this.gc_timer_interval));
if (this.gc_repeating_timer.state == .ACTIVE) {
vm.timer.update(&this.gc_repeating_timer, &this.gc_repeating_timer.next);
}
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);
const now = bun.timespec.now();
this.gc_repeating_timer.next = now.addMs(GC_SLOW_INTERVAL_MS);
if (this.gc_repeating_timer.state == .ACTIVE) {
vm.timer.update(&this.gc_repeating_timer, &this.gc_repeating_timer.next);
}
this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0;
}
}
pub fn onGCRepeatingTimer(timer: *uws.Timer) callconv(.C) void {
var this = timer.as(*GarbageCollectionController);
pub fn onGCRepeatingTimer(this: *GarbageCollectionController, now: *const bun.timespec, vm: *VirtualMachine) bun.api.Timer.EventLoopTimer.Arm {
_ = vm;
this.gc_repeating_timer.state = .FIRED;
if (this.disabled) return .disarm;
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);
return .{ .rearm = now.addMs(GC_SLOW_INTERVAL_MS) };
}
} else {
this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0;
this.updateGCRepeatTimer(.fast);
}
// Reschedule for the next interval
const interval: i32 = if (this.gc_repeating_timer_fast) this.gc_timer_interval else GC_SLOW_INTERVAL_MS;
return .{ .rearm = now.addMs(@intCast(interval)) };
}
pub fn processGCTimer(this: *GarbageCollectionController) void {

View File

@@ -10,7 +10,7 @@
".stdDir()": 41,
".stdFile()": 18,
"// autofix": 167,
": [^=]+= undefined,$": 256,
": [^=]+= undefined,$": 254,
"== alloc.ptr": 0,
"== allocator.ptr": 0,
"@import(\"bun\").": 0,