use core::ffi::c_void;
use core::ptr::NonNull;
use Timespec as timespec;
pub use bun_core::Timespec;
pub use bun_io::heap::IntrusiveField;
const NS_PER_MS: i64 = bun_core::time::NS_PER_MS as i64;
unsafe extern "Rust" {
fn __bun_fire_timer(t: *mut EventLoopTimer, now: *const timespec, vm: *mut ());
fn __bun_js_timer_epoch(tag: Tag, t: *const EventLoopTimer) -> Option<u32>;
}
pub struct EventLoopTimer {
pub next: timespec,
pub state: State,
pub tag: Tag,
pub heap: IntrusiveField<EventLoopTimer>,
pub in_heap: InHeap,
}
impl bun_io::heap::HeapNode for EventLoopTimer {
#[inline]
fn heap(&mut self) -> &mut IntrusiveField<Self> {
&mut self.heap
}
}
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub enum InHeap {
#[default]
None,
Regular,
Fake,
}
impl EventLoopTimer {
pub fn init_paused(tag: Tag) -> Self {
Self {
next: timespec::EPOCH,
state: State::PENDING,
tag,
heap: IntrusiveField::default(),
in_heap: InHeap::None,
}
}
pub fn less(_: (), a: &Self, b: &Self) -> bool {
let sec_order = a.next.sec.cmp(&b.next.sec);
if sec_order != core::cmp::Ordering::Equal {
return sec_order == core::cmp::Ordering::Less;
}
let maybe_a_epoch = a.js_timer_epoch();
let maybe_b_epoch = b.js_timer_epoch();
let mut a_ns = a.next.nsec;
let mut b_ns = b.next.nsec;
if maybe_a_epoch.is_some() {
a_ns = NS_PER_MS * (a_ns / NS_PER_MS);
}
if maybe_b_epoch.is_some() {
b_ns = NS_PER_MS * (b_ns / NS_PER_MS);
}
let order = a_ns.cmp(&b_ns);
if order == core::cmp::Ordering::Equal {
if let Some(a_epoch) = maybe_a_epoch {
if let Some(b_epoch) = maybe_b_epoch {
const U25_MAX: u32 = (1 << 25) - 1;
return (b_epoch.wrapping_sub(a_epoch) & U25_MAX) < U25_MAX / 2;
}
}
}
order == core::cmp::Ordering::Less
}
#[inline]
pub fn js_timer_epoch(&self) -> Option<u32> {
unsafe { __bun_js_timer_epoch(self.tag, self) }
}
pub unsafe fn fire(
this: *mut Self,
now: ×pec,
vm: *mut (),
) {
unsafe { __bun_fire_timer(this, now, vm) };
}
}
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, strum::IntoStaticStr)]
pub enum Tag {
TimerCallback,
TimeoutObject,
ImmediateObject,
StatWatcherScheduler,
UpgradedDuplex,
DNSResolver,
WindowsNamedPipe,
WTFTimer,
PostgresSQLConnectionTimeout,
PostgresSQLConnectionMaxLifetime,
MySQLConnectionTimeout,
MySQLConnectionMaxLifetime,
ValkeyConnectionTimeout,
ValkeyConnectionReconnect,
SubprocessTimeout,
DevServerSweepSourceMaps,
DevServerMemoryVisualizerTick,
AbortSignalTimeout,
DateHeaderTimer,
BunTest,
EventLoopDelayMonitor,
CronJob,
}
impl Tag {
pub fn allow_fake_timers(self) -> bool {
match self {
Tag::WTFTimer | Tag::BunTest | Tag::EventLoopDelayMonitor | Tag::StatWatcherScheduler
| Tag::CronJob => false,
_ => true,
}
}
}
pub struct TimerCallback {
pub callback: fn(*mut TimerCallback),
pub ctx: Option<NonNull<c_void>>,
pub event_loop_timer: EventLoopTimer,
}
#[macro_export]
macro_rules! impl_timer_owner {
($Owner:ty; $($method:ident => $field:ident),+ $(,)?) => {
impl $Owner {
$(
#[doc = concat!("`", stringify!($field), "` [`EventLoopTimer`] slot.")]
#[doc = concat!("`t` must point at the `", stringify!($field), "` field of a live `Self`.")]
#[inline]
pub unsafe fn $method(
t: *const $crate::EventLoopTimer::EventLoopTimer,
) -> *mut Self {
unsafe { ::bun_core::from_field_ptr!(Self, $field, t) }
}
)+
}
};
}
crate::impl_timer_owner!(TimerCallback; from_timer_ptr => event_loop_timer);
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub enum State {
#[default]
PENDING,
ACTIVE,
CANCELLED,
FIRED,
}
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Kind {
SetTimeout = 0,
SetInterval = 1,
SetImmediate = 2,
}
impl Kind {
#[inline]
pub fn big(self) -> KindBig {
match self {
Kind::SetTimeout => KindBig::SetTimeout,
Kind::SetInterval => KindBig::SetInterval,
Kind::SetImmediate => KindBig::SetImmediate,
}
}
}
#[repr(u32)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum KindBig {
SetTimeout = 0,
SetInterval = 1,
SetImmediate = 2,
}
impl From<Kind> for KindBig {
#[inline]
fn from(k: Kind) -> Self {
k.big()
}
}
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct TimerFlags(u32);
impl Default for TimerFlags {
fn default() -> Self {
Self(1 << 30)
}
}
impl TimerFlags {
const EPOCH_MASK: u32 = (1 << 25) - 1;
const KIND_SHIFT: u32 = 25;
const KIND_MASK: u32 = 0b11 << Self::KIND_SHIFT;
const HAS_CLEARED_TIMER: u32 = 1 << 27;
const IS_KEEPING_EVENT_LOOP_ALIVE: u32 = 1 << 28;
const HAS_ACCESSED_PRIMITIVE: u32 = 1 << 29;
const HAS_JS_REF: u32 = 1 << 30;
const IN_CALLBACK: u32 = 1 << 31;
#[inline]
pub fn epoch(self) -> u32 {
self.0 & Self::EPOCH_MASK
}
#[inline]
pub fn set_epoch(&mut self, v: u32) {
self.0 = (self.0 & !Self::EPOCH_MASK) | (v & Self::EPOCH_MASK);
}
#[inline]
pub fn kind(self) -> Kind {
match ((self.0 & Self::KIND_MASK) >> Self::KIND_SHIFT) as u8 {
0 => Kind::SetTimeout,
1 => Kind::SetInterval,
2 => Kind::SetImmediate,
_ => unreachable!(),
}
}
#[inline]
pub fn set_kind(&mut self, k: Kind) {
self.0 = (self.0 & !Self::KIND_MASK) | ((k as u32) << Self::KIND_SHIFT);
}
#[inline]
pub fn has_cleared_timer(self) -> bool {
self.0 & Self::HAS_CLEARED_TIMER != 0
}
#[inline]
pub fn set_has_cleared_timer(&mut self, v: bool) {
if v {
self.0 |= Self::HAS_CLEARED_TIMER
} else {
self.0 &= !Self::HAS_CLEARED_TIMER
}
}
#[inline]
pub fn is_keeping_event_loop_alive(self) -> bool {
self.0 & Self::IS_KEEPING_EVENT_LOOP_ALIVE != 0
}
#[inline]
pub fn set_is_keeping_event_loop_alive(&mut self, v: bool) {
if v {
self.0 |= Self::IS_KEEPING_EVENT_LOOP_ALIVE
} else {
self.0 &= !Self::IS_KEEPING_EVENT_LOOP_ALIVE
}
}
#[inline]
pub fn has_accessed_primitive(self) -> bool {
self.0 & Self::HAS_ACCESSED_PRIMITIVE != 0
}
#[inline]
pub fn set_has_accessed_primitive(&mut self, v: bool) {
if v {
self.0 |= Self::HAS_ACCESSED_PRIMITIVE
} else {
self.0 &= !Self::HAS_ACCESSED_PRIMITIVE
}
}
#[inline]
pub fn has_js_ref(self) -> bool {
self.0 & Self::HAS_JS_REF != 0
}
#[inline]
pub fn set_has_js_ref(&mut self, v: bool) {
if v {
self.0 |= Self::HAS_JS_REF
} else {
self.0 &= !Self::HAS_JS_REF
}
}
#[inline]
pub fn in_callback(self) -> bool {
self.0 & Self::IN_CALLBACK != 0
}
#[inline]
pub fn set_in_callback(&mut self, v: bool) {
if v {
self.0 |= Self::IN_CALLBACK
} else {
self.0 &= !Self::IN_CALLBACK
}
}
}