Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 15 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). Out-params
/// are `&mut i64` so the pointer-validity precondition is in the type.
#[cfg(windows)]
safe fn clock_gettime_monotonic(sec: &mut i64, nsec: &mut i64) -> core::ffi::c_int;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
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,15 @@ 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;
let rc = clock_gettime_monotonic(&mut sec, &mut nsec);
debug_assert!(rc == 0);
Timespec { sec, nsec }
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
}
}

Expand Down
18 changes: 10 additions & 8 deletions src/jsc/bindings/c-bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,17 +235,19 @@ extern "C" size_t Bun__memoryFootprint()

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

// 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;
}();
if (!ticksPerSec.QuadPart) {
QueryPerformanceFrequency(&ticksPerSec);
if (!ticksPerSec.QuadPart) {
errno = ENOTSUP;
return -1;
}
errno = ENOTSUP;
return -1;
}

LARGE_INTEGER ticks;
QueryPerformanceCounter(&ticks);

*tv_sec = (int64_t)(ticks.QuadPart / ticksPerSec.QuadPart);
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 @@
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_allow_mocked_time(),

Check warning on line 3964 in src/runtime/server/mod.rs

View check run for this annotation

Claude / Claude Code Review

Inspector start/stop timestamps use different mocked-time variants

nit: `notify_server_started` now sends `milli_timestamp_allow_mocked_time()` (mocked-aware) while its sibling `notify_server_stopped` at :3978 still sends plain `milli_timestamp()` (never mocked), so under `jest.useFakeTimers()` DevTools receives a mocked start time and a real-wall-clock stop time. The mocked/real asymmetry pre-dates this PR (the old `Timespec::now_allow_mocked_time()` was also mocked-aware), but since the earlier review suggested plain `milli_timestamp()` precisely to match the
Comment thread
robobun marked this conversation as resolved.
Outdated
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