Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/sources/ZigSources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ src/bun.js/api/server/StaticRoute.zig
src/bun.js/api/server/WebSocketServerContext.zig
src/bun.js/api/streams.classes.zig
src/bun.js/api/Timer.zig
src/bun.js/api/Timer/DateHeaderTimer.zig
src/bun.js/api/Timer/EventLoopTimer.zig
src/bun.js/api/Timer/ImmediateObject.zig
src/bun.js/api/Timer/TimeoutObject.zig
Expand Down
8 changes: 6 additions & 2 deletions packages/bun-usockets/src/loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ extern void __attribute((__noreturn__)) Bun__panic(const char* message, size_t l
#define BUN_PANIC(message) Bun__panic(message, sizeof(message) - 1)
#endif

extern void Bun__internal_ensureDateHeaderTimerIsEnabled(struct us_loop_t *loop);

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) {
loop->data.sweep_timer_count++;
if (loop->data.sweep_timer_count == 1) {
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
Bun__internal_ensureDateHeaderTimerIsEnabled(loop);
}
loop->data.sweep_timer_count++;

}

void us_internal_disable_sweep_timer(struct us_loop_t *loop) {
Expand Down
18 changes: 1 addition & 17 deletions packages/bun-uws/src/Loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,6 @@ struct Loop {

static Loop *create(void *hint) {
Loop *loop = ((Loop *) us_create_loop(hint, wakeupCb, preCb, postCb, sizeof(LoopData)))->init();

/* We also need some timers (should live off the one 4 second timer rather) */
LoopData *loopData = (LoopData *) us_loop_ext((struct us_loop_t *) loop);
loopData->dateTimer = us_create_timer((struct us_loop_t *) loop, 1, sizeof(LoopData *));
loopData->updateDate();

memcpy(us_timer_ext(loopData->dateTimer), &loopData, sizeof(LoopData *));
us_timer_set(loopData->dateTimer, [](struct us_timer_t *t) {
LoopData *loopData;
memcpy(&loopData, us_timer_ext(t), sizeof(LoopData *));
loopData->updateDate();
}, 1000, 1000);

return loop;
}

Expand Down Expand Up @@ -146,10 +133,7 @@ struct Loop {
/* Freeing the default loop should be done once */
void free() {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);

/* Stop and free dateTimer first */
us_timer_close(loopData->dateTimer, 1);


loopData->~LoopData();
/* uSockets will track whether this loop is owned by us or a borrowed alien loop */
us_loop_free((us_loop_t *) this);
Expand Down
2 changes: 0 additions & 2 deletions packages/bun-uws/src/LoopData.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ struct alignas(16) LoopData {
ZlibContext *zlibContext = nullptr;
InflationStream *inflationStream = nullptr;
DeflationStream *deflationStream = nullptr;

us_timer_t *dateTimer;
};

}
Expand Down
6 changes: 5 additions & 1 deletion src/bun.js/VirtualMachine.zig
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,11 @@ pub const VMHolder = struct {
};

pub inline fn get() *VirtualMachine {
return VMHolder.vm.?;
return getOrNull().?;
}

pub inline fn getOrNull() ?*VirtualMachine {
return VMHolder.vm;
}

pub fn getMainThreadVM() ?*VirtualMachine {
Expand Down
27 changes: 27 additions & 0 deletions src/bun.js/api/Timer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub const All = struct {
}
} = .{},

/// Updates the "Date" header.
date_header_timer: DateHeaderTimer = .{},

pub fn init() @This() {
return .{
.thread_id = std.Thread.getCurrentId(),
Expand Down Expand Up @@ -199,6 +202,27 @@ pub const All = struct {
return VirtualMachine.get().timer.last_id;
}

fn isDateTimerActive(this: *const All) bool {
return this.date_header_timer.event_loop_timer.state == .ACTIVE;
}

pub fn updateDateHeaderTimerIfNecessary(this: *All, loop: *const uws.Loop, vm: *VirtualMachine) void {
if (loop.shouldEnableDateHeaderTimer()) {
if (!this.isDateTimerActive()) {
this.date_header_timer.enable(
vm,
// Be careful to avoid adding extra calls to bun.timespec.now()
// when it's not needed.
&bun.timespec.now(),
);
}
} else {
// don't un-schedule it here.
// it's better to wake up an extra 1 time after a second idle
// than to have to check a date potentially on every single HTTP request.
}
}

pub fn getTimeout(this: *All, spec: *timespec, vm: *VirtualMachine) bool {
var maybe_now: ?timespec = null;
while (this.timers.peek()) |min| {
Expand Down Expand Up @@ -571,6 +595,8 @@ pub const ID = extern struct {
/// A timer created by WTF code and invoked by Bun's event loop
pub const WTFTimer = @import("./Timer/WTFTimer.zig");

pub const DateHeaderTimer = @import("./Timer/DateHeaderTimer.zig");

pub const internal_bindings = struct {
/// Node.js has some tests that check whether timers fire at the right time. They check this
/// with the internal binding `getLibuvNow()`, which returns an integer in milliseconds. This
Expand Down Expand Up @@ -598,6 +624,7 @@ const Environment = bun.Environment;
const JSError = bun.JSError;
const assert = bun.assert;
const timespec = bun.timespec;
const uws = bun.uws;
const heap = bun.io.heap;
const uv = bun.windows.libuv;

Expand Down
82 changes: 82 additions & 0 deletions src/bun.js/api/Timer/DateHeaderTimer.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/// DateHeaderTimer manages the periodic updating of the "Date" header in Bun.serve().
///
/// This timer ensures that HTTP responses include an up-to-date Date header by
/// updating the date every second when there are active connections.
///
/// Behavior:
/// - When sweep_timer_count > 0 (active connections), the timer should be running
/// - When sweep_timer_count = 0 (no connections), the timer doesn't get rescheduled.
/// - If the timer was already running, no changes are made.
/// - If the timer was not running and needs to start:
/// - If the last update was > 1 second ago, update the date immediately and schedule next update
/// - If the last update was < 1 second ago, just schedule the next update
///
/// Note that we only check for potential updates ot this timer once per event loop tick.
const DateHeaderTimer = @This();

event_loop_timer: jsc.API.Timer.EventLoopTimer = .{
.tag = .DateHeaderTimer,
.next = .epoch,
},

/// Schedule the "Date"" header timer.
///
/// The logic handles two scenarios:
/// 1. If the timer was recently updated (< 1 second ago), just reschedule it
/// 2. If the timer is stale (> 1 second since last update), update the date immediately and reschedule
pub fn enable(this: *DateHeaderTimer, vm: *VirtualMachine, now: *const bun.timespec) void {
bun.debugAssert(this.event_loop_timer.state != .ACTIVE);

const last_update = this.event_loop_timer.next;
const elapsed = now.duration(&last_update).ms();

// If the last update was more than 1 second ago, the date is stale
if (elapsed >= std.time.ms_per_s) {
// Update the date immediately since it's stale
log("updating stale timer & rescheduling for 1 second later", .{});

// updateDate() is an expensive function.
vm.uwsLoop().updateDate();

vm.timer.update(&this.event_loop_timer, &now.addMs(std.time.ms_per_s));
} else {
// The date was updated recently, just reschedule for the next second
log("rescheduling timer", .{});
vm.timer.insert(&this.event_loop_timer);
}
}

pub fn run(this: *DateHeaderTimer, vm: *VirtualMachine) void {
this.event_loop_timer.state = .FIRED;
const loop = vm.uwsLoop();
const now = bun.timespec.now();

// Record when we last ran it.
this.event_loop_timer.next = now;
log("run", .{});

// updateDate() is an expensive function.
loop.updateDate();

if (loop.internal_loop_data.sweep_timer_count > 0) {
// Reschedule it automatically for 1 second later.
this.event_loop_timer.next = now.addMs(std.time.ms_per_s);
vm.timer.insert(&this.event_loop_timer);
}
}

pub export fn Bun__internal_ensureDateHeaderTimerIsEnabled(loop: *uws.Loop) callconv(.C) void {
if (jsc.VirtualMachine.getOrNull()) |vm| {
vm.timer.updateDateHeaderTimerIfNecessary(loop, vm);
}
}

const log = bun.Output.scoped(.DateHeaderTimer, .visible);

const std = @import("std");

const bun = @import("bun");
const uws = bun.uws;

const jsc = bun.jsc;
const VirtualMachine = jsc.VirtualMachine;
9 changes: 9 additions & 0 deletions src/bun.js/api/Timer/EventLoopTimer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub const Tag = if (Environment.isWindows) enum {
DevServerSweepSourceMaps,
DevServerMemoryVisualizerTick,
AbortSignalTimeout,
DateHeaderTimer,

pub fn Type(comptime T: Tag) type {
return switch (T) {
Expand All @@ -86,6 +87,7 @@ pub const Tag = if (Environment.isWindows) enum {
.DevServerMemoryVisualizerTick,
=> bun.bake.DevServer,
.AbortSignalTimeout => jsc.WebCore.AbortSignal.Timeout,
.DateHeaderTimer => jsc.API.Timer.DateHeaderTimer,
};
}
} else enum {
Expand All @@ -105,6 +107,7 @@ pub const Tag = if (Environment.isWindows) enum {
DevServerSweepSourceMaps,
DevServerMemoryVisualizerTick,
AbortSignalTimeout,
DateHeaderTimer,

pub fn Type(comptime T: Tag) type {
return switch (T) {
Expand All @@ -125,6 +128,7 @@ pub const Tag = if (Environment.isWindows) enum {
.DevServerMemoryVisualizerTick,
=> bun.bake.DevServer,
.AbortSignalTimeout => jsc.WebCore.AbortSignal.Timeout,
.DateHeaderTimer => jsc.API.Timer.DateHeaderTimer,
};
}
};
Expand Down Expand Up @@ -194,6 +198,11 @@ pub fn fire(self: *Self, now: *const timespec, vm: *VirtualMachine) Arm {
timeout.run(vm);
return .disarm;
},
.DateHeaderTimer => {
const date_header_timer = @as(*jsc.API.Timer.DateHeaderTimer, @fieldParentPtr("event_loop_timer", self));
date_header_timer.run(vm);
return .disarm;
},
inline else => |t| {
if (@FieldType(t.Type(), "event_loop_timer") != Self) {
@compileError(@typeName(t.Type()) ++ " has wrong type for 'event_loop_timer'");
Expand Down
12 changes: 8 additions & 4 deletions src/bun.js/event_loop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,8 @@ pub fn usocketsLoop(this: *const EventLoop) *uws.Loop {
}

pub fn autoTick(this: *EventLoop) void {
var loop = this.usocketsLoop();
var ctx = this.virtual_machine;
const loop = this.usocketsLoop();
const ctx = this.virtual_machine;

this.tickImmediateTasks(ctx);
if (comptime Environment.isPosix) {
Expand All @@ -350,6 +350,8 @@ pub fn autoTick(this: *EventLoop) void {
}
}

ctx.timer.updateDateHeaderTimerIfNecessary(loop, ctx);

this.runImminentGCTimer();

if (loop.isActive()) {
Expand Down Expand Up @@ -378,8 +380,8 @@ pub fn autoTick(this: *EventLoop) void {
}

pub fn tickPossiblyForever(this: *EventLoop) void {
var ctx = this.virtual_machine;
var loop = this.usocketsLoop();
const ctx = this.virtual_machine;
const loop = this.usocketsLoop();

if (comptime Environment.isPosix) {
const pending_unref = ctx.pending_unref_counter;
Expand Down Expand Up @@ -429,6 +431,8 @@ pub fn autoTickActive(this: *EventLoop) void {
}
}

ctx.timer.updateDateHeaderTimerIfNecessary(loop, ctx);

if (loop.isActive()) {
this.processGCTimer();
var timespec: bun.timespec = undefined;
Expand Down
5 changes: 5 additions & 0 deletions src/deps/libuwsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ using TCPWebSocket = uWS::WebSocket<false, true, void *>;
extern "C"
{

void uws_loop_date_header_timer_update(us_loop_t *loop) {
uWS::LoopData *loopData = uWS::Loop::data(loop);
loopData->updateDate();
}

uws_app_t *uws_create_app(int ssl, struct us_bun_socket_context_options_t options)
{
if (ssl)
Expand Down
4 changes: 4 additions & 0 deletions src/deps/uws/InternalLoopData.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub const InternalLoopData = extern struct {
return this.recv_buf[0..LIBUS_RECV_BUFFER_LENGTH];
}

pub fn shouldEnableDateHeaderTimer(this: *const InternalLoopData) bool {
return this.sweep_timer_count > 0;
}

pub fn setParentEventLoop(this: *InternalLoopData, parent: jsc.EventLoopHandle) void {
switch (parent) {
.js => |ptr| {
Expand Down
17 changes: 17 additions & 0 deletions src/deps/uws/Loop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub const PosixLoop = extern struct {
c.uws_res_clear_corked_socket(this);
}

pub fn updateDate(this: *PosixLoop) void {
c.uws_loop_date_header_timer_update(this);
}

pub fn iterationNumber(this: *const PosixLoop) u64 {
return this.internal_loop_data.iteration_nr;
}
Expand Down Expand Up @@ -157,6 +161,10 @@ pub const PosixLoop = extern struct {
pub fn run(this: *PosixLoop) void {
c.us_loop_run(this);
}

pub fn shouldEnableDateHeaderTimer(this: *const PosixLoop) bool {
return this.internal_loop_data.shouldEnableDateHeaderTimer();
}
};

pub const WindowsLoop = extern struct {
Expand All @@ -169,6 +177,10 @@ pub const WindowsLoop = extern struct {
pre: *uv.uv_prepare_t,
check: *uv.uv_check_t,

pub fn shouldEnableDateHeaderTimer(this: *const WindowsLoop) bool {
return this.internal_loop_data.shouldEnableDateHeaderTimer();
}

pub fn uncork(this: *PosixLoop) void {
c.uws_res_clear_corked_socket(this);
}
Expand Down Expand Up @@ -245,6 +257,10 @@ pub const WindowsLoop = extern struct {
c.uws_loop_defer(this, user_data, Handler.callback);
}

pub fn updateDate(this: *Loop) void {
c.uws_loop_date_header_timer_update(this);
}

fn NewHandler(comptime UserType: type, comptime callback_fn: fn (UserType) void) type {
return struct {
loop: *Loop,
Expand Down Expand Up @@ -287,6 +303,7 @@ const c = struct {
pub extern fn uws_get_loop_with_native(*anyopaque) *WindowsLoop;
pub extern fn uws_loop_defer(loop: *Loop, ctx: *anyopaque, cb: *const (fn (ctx: *anyopaque) callconv(.C) void)) void;
pub extern fn uws_res_clear_corked_socket(loop: *Loop) void;
pub extern fn uws_loop_date_header_timer_update(loop: *Loop) void;
};

const log = bun.Output.scoped(.Loop, .visible);
Expand Down
Loading