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
24 changes: 14 additions & 10 deletions src/bun_core/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2318,6 +2318,10 @@ unsafe extern "C" {
/// directly.
#[cfg(unix)]
safe fn clock_gettime(clk_id: libc::clockid_t, tp: &mut libc::timespec) -> core::ffi::c_int;
/// Bun C++ shim over `QueryPerformanceCounter` (c-bindings.cpp). Infallible
/// on Windows XP+; out-params are `&mut i64` so pointer validity is typed.
#[cfg(windows)]
safe fn clock_gettime_monotonic(sec: &mut i64, nsec: &mut i64);
}
impl Default for StackCheck {
/// `cached_stack_end` defaults to `0`, so
Expand Down Expand Up @@ -5265,10 +5269,9 @@ impl Timespec {
}
}

/// `bun.timespec.now(.allow_mocked_time)` — monotonic-ish "rough tick".
/// Real impl routes through `getRoughTickCount` (jsc); tier-0 reads the
/// monotonic clock directly. Test-runner fake-timers write the mocked
/// nanosecond value via `mock_time::set` / `mock_time::clear`.
/// Monotonic clock (`CLOCK_MONOTONIC` / QPC). Boot-relative on every
/// platform; never compare against wall-clock epoch. Fake-timers override
/// via `mock_time::set` / `mock_time::clear`.
#[inline]
pub fn now(mode: TimespecMockMode) -> Timespec {
if matches!(mode, TimespecMockMode::AllowMockedTime) {
Expand Down Expand Up @@ -5297,13 +5300,14 @@ impl Timespec {
nsec: ts.tv_nsec,
}
}
#[cfg(not(unix))]
#[cfg(windows)]
{
let n = crate::time::nano_timestamp();
Timespec {
sec: (n / 1_000_000_000) as i64,
nsec: (n % 1_000_000_000) as i64,
}
// QPC via the c-bindings.cpp shim: the same monotonic clock libuv
// (uv_hrtime), uSockets' sweep and WTF::MonotonicTime::now use.
let mut sec: i64 = 0;
let mut nsec: i64 = 0;
clock_gettime_monotonic(&mut sec, &mut nsec);
Timespec { sec, nsec }
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
}
}

Expand Down
12 changes: 4 additions & 8 deletions src/io/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,14 +511,11 @@ bun_core::define_scoped_log!(log, io_loop); // hand-declared static above (tagna
#[cfg(windows)]
mod windows_ffi {
// Bun C++ shim over `QueryPerformanceCounter` (src/bun.js/bindings/
// c-bindings.cpp).
// c-bindings.cpp). Infallible on Windows XP+.
unsafe extern "C" {
// safe: out-params are `&mut i64` (non-null, valid for write); C++ side
// only writes the slots and returns a status code — no preconditions.
pub(super) safe fn clock_gettime_monotonic(
sec: &mut i64,
nsec: &mut i64,
) -> core::ffi::c_int;
// only writes the slots — no preconditions.
pub(super) safe fn clock_gettime_monotonic(sec: &mut i64, nsec: &mut i64);
}
}

Expand Down Expand Up @@ -1098,8 +1095,7 @@ impl IoRequestLoop {
// scope in `windows_ffi` since `extern` blocks can't live in `impl`.
let mut sec: i64 = 0;
let mut nsec: i64 = 0;
let rc = windows_ffi::clock_gettime_monotonic(&mut sec, &mut nsec);
debug_assert!(rc == 0);
windows_ffi::clock_gettime_monotonic(&mut sec, &mut nsec);
timespec.tv_sec = sec.try_into().expect("infallible: size matches");
timespec.tv_nsec = nsec.try_into().expect("infallible: size matches");
}
Expand Down
22 changes: 9 additions & 13 deletions src/jsc/bindings/c-bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,25 +233,21 @@ extern "C" size_t Bun__memoryFootprint()
#define NS_PER_HNS (100ULL) // NS = nanoseconds
#define NS_PER_SEC (MS_PER_SEC * US_PER_MS * NS_PER_US)

extern "C" int clock_gettime_monotonic(int64_t* tv_sec, int64_t* tv_nsec)
extern "C" void clock_gettime_monotonic(int64_t* tv_sec, int64_t* tv_nsec)
{
static LARGE_INTEGER ticksPerSec;
LARGE_INTEGER ticks;

if (!ticksPerSec.QuadPart) {
QueryPerformanceFrequency(&ticksPerSec);
if (!ticksPerSec.QuadPart) {
errno = ENOTSUP;
return -1;
}
}
// C++11 thread-safe static init: Timespec::now() runs on multiple threads.
// QueryPerformanceFrequency is documented to always succeed on Windows XP+.
static const LARGE_INTEGER ticksPerSec = [] {
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
return f;
}();

LARGE_INTEGER ticks;
QueryPerformanceCounter(&ticks);

*tv_sec = (int64_t)(ticks.QuadPart / ticksPerSec.QuadPart);
*tv_nsec = (int64_t)(((ticks.QuadPart % ticksPerSec.QuadPart) * NS_PER_SEC) / ticksPerSec.QuadPart);

return 0;
}

extern "C" void windows_enable_stdio_inheritance()
Expand Down
56 changes: 0 additions & 56 deletions src/perf/hw_timer.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/perf/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use core::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;

pub mod generated_perf_trace_events;
pub mod hw_timer;
pub mod system_timer;
pub mod tracy;

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3961,7 +3961,7 @@ pub mod http_server_agent {
this.next_server_id,
(*instance.vm()).hot_reload_counter as i32,
&url,
bun_core::Timespec::now_allow_mocked_time().ms() as f64,
bun_core::time::milli_timestamp() as f64,
instance.ptr.cast(),
);
}
Expand Down
13 changes: 13 additions & 0 deletions test/js/web/timers/setTimeout.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawnSync } from "bun";
import { timerInternals } from "bun:internal-for-testing";
import { heapStats } from "bun:jsc";
import { expect, it } from "bun:test";
import { bunEnv, bunExe, isWindows } from "harness";
Expand Down Expand Up @@ -529,3 +530,15 @@ it("clearTimeout with a numeric id is a no-op after a timeout promoted to an int
expect(stdout).toBe("converted: ok\nsurvived\n");
expect(exitCode).toBe(0);
});

it("timer heap clock is monotonic, not wall-clock", () => {
// The clock that schedules setTimeout/setInterval deadlines must be monotonic
// (boot-relative) on every platform so NTP steps / user clock changes can't
// stall or mass-fire timers. A wall-clock reading here would be ~= Date.now().
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const t0 = timerInternals.timerClockMs();
const t1 = timerInternals.timerClockMs();
const wallNow = Date.now();
expect(t0).toBeGreaterThan(0);
expect(t1).toBeGreaterThanOrEqual(t0);
expect(t1).toBeLessThan(wallNow / 2);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading