Compare commits

...

4 Commits

Author SHA1 Message Date
Jarred Sumner
baaff77651 Merge branch 'main' into claude/gc-very-slow-timer 2025-07-29 10:32:57 -07:00
autofix-ci[bot]
1fac21e315 [autofix.ci] apply automated fixes 2025-07-29 09:25:05 +00:00
Claude Bot
9660611977 Add very slow GC timer mode for long-running stable applications
This change adds a third tier to the GarbageCollectionController timer system
to minimize CPU usage for very long-running applications with stable heap sizes.

Changes:
- Add GCTimerMode enum with fast/slow/very_slow options
- Replace gc_repeating_timer_fast boolean with gc_repeating_timer_mode enum
- Update updateGCRepeatTimer() to handle three modes with proper transitions
- Modify onGCRepeatingTimer() to transition to very_slow after 255 stable ticks

Timer behavior:
- Fast mode: GC every 1 second (default, when heap is changing)
- Slow mode: GC every 30 seconds (after 30 stable ticks in fast mode)
- Very slow mode: GC every 10 minutes (after 255 stable ticks in slow mode)

This provides optimal GC scheduling:
- Active applications: frequent GC for responsiveness
- Stable applications: moderate GC for efficiency
- Idle applications: minimal GC for CPU conservation

The system immediately returns to fast mode when any heap size change is detected,
ensuring responsive garbage collection when needed while minimizing overhead
during periods of stability.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 09:11:04 +00:00
Claude Bot
176ef8c447 Optimize uSockets sweep timer to only run when connections exist
This change implements reference counting for active sockets to avoid running
the sweep timer continuously when no connections need timeout checking.

Changes:
- Add sweep_timer_count field to loop data structures (C and Zig)
- Implement us_internal_enable/disable_sweep_timer() helper functions
- Timer starts disabled and only enables when first socket is linked
- Timer disables when last socket is unlinked
- Covers both regular sockets and connecting sockets that need sweeping
- Listen sockets are excluded as they don't need timeout checking

This significantly reduces CPU usage when Bun applications are idle
with no active HTTP connections.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 08:49:47 +00:00
6 changed files with 79 additions and 18 deletions

View File

@@ -130,6 +130,7 @@ void us_internal_socket_context_unlink_socket(int ssl, struct us_socket_context_
next->prev = prev;
}
}
us_internal_disable_sweep_timer(context->loop);
us_socket_context_unref(ssl, context);
}
void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_socket_context_t *context, struct us_connecting_socket_t *c) {
@@ -147,6 +148,7 @@ void us_internal_socket_context_unlink_connecting_socket(int ssl, struct us_sock
next->prev_pending = prev;
}
}
us_internal_disable_sweep_timer(context->loop);
us_socket_context_unref(ssl, context);
}
@@ -172,6 +174,7 @@ void us_internal_socket_context_link_connecting_socket(int ssl, struct us_socket
}
context->head_connecting_sockets = c;
us_socket_context_ref(ssl, context);
us_internal_enable_sweep_timer(context->loop);
}
@@ -185,6 +188,7 @@ void us_internal_socket_context_link_socket(struct us_socket_context_t *context,
}
context->head_sockets = s;
us_socket_context_ref(0, context);
us_internal_enable_sweep_timer(context->loop);
}
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context) {

View File

@@ -116,6 +116,8 @@ extern struct addrinfo_result *Bun__addrinfo_getRequestResult(struct addrinfo_re
/* Loop related */
void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, int events);
void us_internal_timer_sweep(us_loop_r loop);
void us_internal_enable_sweep_timer(struct us_loop_t *loop);
void us_internal_disable_sweep_timer(struct us_loop_t *loop);
void us_internal_free_closed_sockets(us_loop_r loop);
void us_internal_loop_link(struct us_loop_t *loop,
struct us_socket_context_t *context);

View File

@@ -35,6 +35,7 @@ typedef void* zig_mutex_t;
// IMPORTANT: When changing this, don't forget to update the zig version in uws.zig as well!
struct us_internal_loop_data_t {
struct us_timer_t *sweep_timer;
int sweep_timer_count;
struct us_internal_async *wakeup_async;
int last_write_failed;
struct us_socket_context_t *head;

View File

@@ -18,6 +18,7 @@
#include "libusockets.h"
#include "internal/internal.h"
#include <stdlib.h>
#include <stdio.h>
#ifndef WIN32
#include <sys/ioctl.h>
#endif
@@ -29,11 +30,28 @@ extern void __attribute((__noreturn__)) Bun__panic(const char* message, size_t l
#define BUN_PANIC(message) Bun__panic(message, sizeof(message) - 1)
#endif
void sweep_timer_cb(struct us_internal_callback_t *cb);
void us_internal_enable_sweep_timer(struct us_loop_t *loop) {
if (loop->data.sweep_timer_count == 0) {
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
}
loop->data.sweep_timer_count++;
}
void us_internal_disable_sweep_timer(struct us_loop_t *loop) {
loop->data.sweep_timer_count--;
if (loop->data.sweep_timer_count == 0) {
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, 0, 0);
}
}
/* The loop has 2 fallthrough polls */
void us_internal_loop_data_init(struct us_loop_t *loop, void (*wakeup_cb)(struct us_loop_t *loop),
void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop)) {
// We allocate with calloc, so we only need to initialize the specific fields in use.
loop->data.sweep_timer = us_create_timer(loop, 1, 0);
loop->data.sweep_timer_count = 0;
loop->data.recv_buf = malloc(LIBUS_RECV_BUFFER_LENGTH + LIBUS_RECV_BUFFER_PADDING * 2);
loop->data.send_buf = malloc(LIBUS_SEND_BUFFER_LENGTH);
loop->data.pre_cb = pre_cb;
@@ -547,9 +565,9 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
}
}
/* Integration only requires the timer to be set up */
/* Integration only requires the timer to be set up, but not automatically enabled */
void us_loop_integrate(struct us_loop_t *loop) {
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
/* Timer is now controlled dynamically by socket count, not enabled automatically */
}
void *us_loop_ext(struct us_loop_t *loop) {

View File

@@ -27,7 +27,7 @@ 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,
gc_repeating_timer_mode: GCTimerMode = .fast,
disabled: bool = false,
pub fn init(this: *GarbageCollectionController, vm: *VirtualMachine) void {
@@ -77,22 +77,36 @@ pub fn onGCTimer(timer: *uws.Timer) callconv(.C) void {
// 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:
// So we have three settings for this GC timer:
//
// - Fast: GC runs every 1 second
// - Fast: GC runs every 1 second (default)
// - Slow: GC runs every 30 seconds
// - Very Slow: GC runs every 10 minutes (600 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: *GarbageCollectionController, comptime setting: @Type(.enum_literal)) 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;
// When the heap size has been the same for 30 ticks, we switch to slow mode
// When the heap size has been the same for a very large number of ticks (255), we switch to very slow mode
pub fn updateGCRepeatTimer(this: *GarbageCollectionController, mode: GCTimerMode) void {
if (this.gc_repeating_timer_mode == mode) return;
const old_mode = this.gc_repeating_timer_mode;
this.gc_repeating_timer_mode = mode;
switch (mode) {
.fast => {
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;
},
.slow => {
this.gc_repeating_timer.set(this, onGCRepeatingTimer, 30_000, 30_000);
if (old_mode == .fast) {
this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0;
}
},
.very_slow => {
this.gc_repeating_timer.set(this, onGCRepeatingTimer, 600_000, 600_000); // 10 minutes
this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0;
},
}
}
@@ -103,9 +117,24 @@ pub fn onGCRepeatingTimer(timer: *uws.Timer) callconv(.C) void {
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);
// Transition to progressively slower modes based on heap stability
switch (this.gc_repeating_timer_mode) {
.fast => {
if (this.heap_size_didnt_change_for_repeating_timer_ticks_count >= 30) {
this.updateGCRepeatTimer(.slow);
}
},
.slow => {
// After a very large number of ticks in slow mode, switch to very slow
// Use 255 as the threshold since that's the max value for u8
if (this.heap_size_didnt_change_for_repeating_timer_ticks_count == 255) {
this.updateGCRepeatTimer(.very_slow);
}
},
.very_slow => {
// Already at the slowest mode, stay here
},
}
} else {
this.heap_size_didnt_change_for_repeating_timer_ticks_count = 0;
@@ -167,6 +196,12 @@ pub const GCTimerState = enum {
run_on_next_tick,
};
pub const GCTimerMode = enum {
fast,
slow,
very_slow,
};
const std = @import("std");
const bun = @import("bun");

View File

@@ -2,6 +2,7 @@ pub const InternalLoopData = extern struct {
pub const us_internal_async = opaque {};
sweep_timer: ?*Timer,
sweep_timer_count: i32,
wakeup_async: ?*us_internal_async,
last_write_failed: i32,
head: ?*SocketContext,