Skip to content
Open
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
29 changes: 15 additions & 14 deletions src/bun_core/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5092,49 +5092,50 @@ pub mod timespec_mode {

/// Mocked-time storage. The data lives at T0 so `Timespec::now` reads it
/// directly; the test-runner (`useFakeTimers`) writes via `set`/`clear`
/// from `bun_runtime::test_runner::timers::FakeTimers::CurrentTime`.
/// Sentinel `i64::MIN` / `NaN` ⇒ not mocked.
/// from `bun_runtime::test_runner::timers::FakeTimers`. Thread-local so each
/// VM (main thread, each `Worker`) has its own fake clock, matching the
/// per-thread fake `TimerHeap` it drives.
pub mod mock_time {
use core::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use core::cell::Cell;

static MOCKED_TIME_NS: AtomicI64 = AtomicI64::new(i64::MIN);
// Mocked wall-clock `Date.now()` in ms, stored as f64 bits; NaN = unset.
static MOCKED_WALL_MS: AtomicU64 = AtomicU64::new(f64::NAN.to_bits());
std::thread_local! {
static MOCKED_TIME_NS: Cell<Option<i64>> = const { Cell::new(None) };
/// Mocked wall-clock `Date.now()` in ms.
static MOCKED_WALL_MS: Cell<Option<f64>> = const { Cell::new(None) };
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// Set the mocked monotonic time (nanoseconds). Called by fake-timers.
#[inline]
pub fn set(ns: i64) {
MOCKED_TIME_NS.store(ns, Ordering::Relaxed);
MOCKED_TIME_NS.set(Some(ns));
}
/// Clear the mocked time so `Timespec::now(AllowMockedTime)` reads the
/// real clock again.
#[inline]
pub fn clear() {
MOCKED_TIME_NS.store(i64::MIN, Ordering::Relaxed);
MOCKED_TIME_NS.set(None);
}
/// Current mocked time, or `None` if not mocked.
#[inline]
pub fn get() -> Option<i64> {
let v = MOCKED_TIME_NS.load(Ordering::Relaxed);
if v == i64::MIN { None } else { Some(v) }
MOCKED_TIME_NS.get()
}
/// Set the mocked wall-clock time (`Date.now()` in ms). Called by
/// fake-timers alongside `set` so calendar-based consumers and the
/// monotonic timer heap move through the same mock.
#[inline]
pub fn set_wall_ms(ms: f64) {
MOCKED_WALL_MS.store(ms.to_bits(), Ordering::Relaxed);
MOCKED_WALL_MS.set(Some(ms));
Comment thread
dylan-conway marked this conversation as resolved.
}
/// Clear the mocked wall-clock time.
#[inline]
pub fn clear_wall() {
MOCKED_WALL_MS.store(f64::NAN.to_bits(), Ordering::Relaxed);
MOCKED_WALL_MS.set(None);
}
/// Current mocked wall-clock time in ms, or `None` if not mocked.
#[inline]
pub(crate) fn wall_ms() -> Option<f64> {
let v = f64::from_bits(MOCKED_WALL_MS.load(Ordering::Relaxed));
if v.is_nan() { None } else { Some(v) }
MOCKED_WALL_MS.get()
}
}

Expand Down
19 changes: 18 additions & 1 deletion src/runtime/api/cron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use bun_resolver::fs::RealFS;
#[cfg(not(windows))]
use crate::api::bun::process::SpawnResultExt as _;
use crate::api::bun::process::{self as spawn, Process, Rusage, SpawnOptions, Status};
use crate::timer::{EventLoopTimer, EventLoopTimerState, EventLoopTimerTag};
use crate::timer::{EventLoopTimer, EventLoopTimerState, EventLoopTimerTag, InHeap};
use bun_core::ZStr;
use bun_core::strings;
use bun_io::pipe_reader::BufferedReaderParent;
Expand Down Expand Up @@ -1810,6 +1810,14 @@ impl CronJob {
// holds the raw pointer (not `&mut`) so re-entrant JS can re-borrow.
let _ev_guard = vm.enter_event_loop_scope();

// The fake clock this tick was popped from, if the job is a fake timer
// (`in_heap` still names the heap until it is re-inserted or removed).
let fake_clock_before_call = if this_ref.event_loop_timer.get().in_heap == InHeap::Fake {
timer_all().fake_timers.clock_id()
} else {
None
};

this_ref.in_fire.set(true);
// A top-level call: what the tick throws is reported here (before the
// job is re-armed, so an `uncaughtException` handler's `stop()` is
Expand All @@ -1818,6 +1826,15 @@ impl CronJob {
let result =
vm.event_loop_mut()
.run_callback_with_result(cb, &this_ref.global, js_this, &[]);
// The tick uninstalled (`useRealTimers()`) or replaced
// (`useFakeTimers()`) that clock. Already popped, this job was out of
// `FakeTimers::clear`'s reach; stop it like the rest of that clock's
// timers (deferred while `in_fire`, finished by `schedule_next`).
if fake_clock_before_call.is_some()
&& timer_all().fake_timers.clock_id() != fake_clock_before_call
{
Self::self_stop(this, vm);
}
this_ref.in_fire.set(false);

// terminate() may have arrived while the callback was running; bail out
Expand Down
5 changes: 4 additions & 1 deletion src/runtime/dns_jsc/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2264,8 +2264,11 @@ pub mod internal {
// To preserve memory, we use a 32 bit timestamp
// However, we're almost out of time to use 32 bit timestamps for anything
// So we set the epoch to January 1st, 2024 instead.
//
// Real time: the cache is process-global, shared by every VM's JS
// thread and the HTTP thread, so no single VM's fake clock applies.
fn get_cache_timestamp() -> u32 {
(bun::Timespec::now(bun::TimespecMockMode::AllowMockedTime).ms_unsigned() / 1000) as u32
(bun::Timespec::now(bun::TimespecMockMode::ForceRealTime).ms_unsigned() / 1000) as u32
}

fn is_nearly_full(&self) -> bool {
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ unsafe fn init_runtime_state(
RUNTIME_STATE.with(|c| c.set(state));

// `Timespec::now_allow_mocked_time` reads `bun_core::mock_time` directly;
// `FakeTimers::CurrentTime::{set,clear}` write that storage so timers
// `FakeTimers::{set_now,clear_now}` write that storage so timers
// scheduled under `jest.useFakeTimers()` use the mocked epoch.

// ── vm.transpiler ────────────────────────────────────────────────────
Expand Down Expand Up @@ -1773,8 +1773,8 @@ fn stop_active_handles(vm: &mut VirtualMachine, reason: StopReason) -> SweepResu
// JS thread, no re-entry while we hold the field borrow.
if !all.is_null() && unsafe { (*all).fake_timers.is_active() } {
let global = vm.global();
// SAFETY: as above; only touches `fake_timers.active` and the
// `CURRENT_TIME` static.
// SAFETY: as above; only touches `fake_timers`' clock state and
// the VM, never the heaps.
unsafe { (*all).fake_timers.reset_for_isolation(global) };
}
}
Expand Down
Loading
Loading