use once_cell::sync::Lazy;
use rusty_v8 as v8;
use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::Duration;
use crate::event_loop::{get_async_timer_manager, TimerScheduleError};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TimerType {
Timeout,
Interval,
Immediate,
}
#[derive(Clone, Debug)]
pub struct TimerMetadata {
pub timer_type: TimerType,
pub delay: u64, pub is_unrefed: bool,
pub epoch: u64, }
pub static TIMER_METADATA: Lazy<Mutex<HashMap<u64, TimerMetadata>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
thread_local! {
static OWNED_TIMER_IDS: std::cell::RefCell<HashSet<u64>> =
std::cell::RefCell::new(HashSet::new());
}
fn track_owned_timer(timer_id: u64) {
OWNED_TIMER_IDS.with(|ids| {
ids.borrow_mut().insert(timer_id);
});
}
fn forget_owned_timer(timer_id: u64) {
OWNED_TIMER_IDS.with(|ids| {
ids.borrow_mut().remove(&timer_id);
});
}
fn owns_timer(timer_id: u64) -> bool {
OWNED_TIMER_IDS.with(|ids| ids.borrow().contains(&timer_id))
}
fn owned_timer_ids() -> HashSet<u64> {
OWNED_TIMER_IDS.with(|ids| ids.borrow().clone())
}
fn take_owned_timer_ids() -> HashSet<u64> {
OWNED_TIMER_IDS.with(|ids| std::mem::take(&mut *ids.borrow_mut()))
}
static TIMER_EPOCH: AtomicU64 = AtomicU64::new(0);
pub fn get_timer_epoch() -> u64 {
TIMER_EPOCH.load(Ordering::SeqCst)
}
pub fn increment_timer_epoch() {
TIMER_EPOCH.fetch_add(1, Ordering::SeqCst);
}
pub struct TimerCallbackStorage {
callbacks: HashMap<u64, v8::Global<v8::Function>>,
args: HashMap<u64, Vec<v8::Global<v8::Value>>>,
}
impl TimerCallbackStorage {
fn new() -> Self {
Self {
callbacks: HashMap::new(),
args: HashMap::new(),
}
}
fn insert(
&mut self,
timer_id: u64,
callback: v8::Global<v8::Function>,
args: Vec<v8::Global<v8::Value>>,
) {
self.callbacks.insert(timer_id, callback);
self.args.insert(timer_id, args);
}
fn remove(
&mut self,
timer_id: u64,
) -> Option<(v8::Global<v8::Function>, Vec<v8::Global<v8::Value>>)> {
if let Some(callback) = self.callbacks.remove(&timer_id) {
let args = self.args.remove(&timer_id).unwrap_or_default();
Some((callback, args))
} else {
None
}
}
}
unsafe impl Send for TimerCallbackStorage {}
unsafe impl Sync for TimerCallbackStorage {}
static TIMER_CALLBACKS: Lazy<Mutex<TimerCallbackStorage>> =
Lazy::new(|| Mutex::new(TimerCallbackStorage::new()));
pub struct ImmediateCallbackStorage {
callbacks: Vec<(
u64,
v8::Global<v8::Function>,
Vec<v8::Global<v8::Value>>,
bool,
)>,
}
impl ImmediateCallbackStorage {
fn new() -> Self {
Self {
callbacks: Vec::new(),
}
}
fn push(
&mut self,
timer_id: u64,
callback: v8::Global<v8::Function>,
args: Vec<v8::Global<v8::Value>>,
is_deferred: bool,
) {
self.callbacks.push((timer_id, callback, args, is_deferred));
}
fn mark_all_as_deferred(&mut self) {
for (_, _, _, deferred) in self.callbacks.iter_mut() {
*deferred = true;
}
}
fn unmark_all_deferred(&mut self) {
for (_, _, _, deferred) in self.callbacks.iter_mut() {
*deferred = false;
}
}
fn drain_non_deferred(
&mut self,
) -> Vec<(u64, v8::Global<v8::Function>, Vec<v8::Global<v8::Value>>)> {
let (non_deferred, remaining): (Vec<_>, Vec<_>) = self
.callbacks
.drain(..)
.partition(|(_, _, _, deferred)| !*deferred);
self.callbacks.extend(remaining);
non_deferred
.into_iter()
.map(|(id, cb, args, _)| (id, cb, args))
.collect()
}
#[allow(dead_code)]
fn drain(&mut self) -> Vec<(u64, v8::Global<v8::Function>, Vec<v8::Global<v8::Value>>)> {
self.callbacks
.drain(..)
.map(|(id, cb, args, _)| (id, cb, args))
.collect()
}
fn remove(&mut self, timer_id: u64) -> bool {
if let Some(pos) = self
.callbacks
.iter()
.position(|(id, _, _, _)| *id == timer_id)
{
self.callbacks.remove(pos);
true
} else {
false
}
}
fn is_empty(&self) -> bool {
self.callbacks.is_empty()
}
fn clear_all(&mut self) {
self.callbacks.clear();
}
}
thread_local! {
static IMMEDIATE_CALLBACKS: std::cell::RefCell<ImmediateCallbackStorage> =
std::cell::RefCell::new(ImmediateCallbackStorage::new());
}
pub static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(1);
const TIMER_ID_EPOCH_OFFSET: u64 = 10000;
pub fn get_next_timer_id() -> u64 {
let base_id = NEXT_TIMER_ID.fetch_add(1, Ordering::SeqCst);
let epoch = get_timer_epoch();
let id = base_id + (epoch * TIMER_ID_EPOCH_OFFSET);
id
}
pub fn get_timer_metadata(timer_id: u64) -> Option<TimerMetadata> {
let metadata = TIMER_METADATA.lock().unwrap();
metadata.get(&timer_id).cloned()
}
#[cfg(test)]
pub fn get_all_timer_metadata() -> Vec<(u64, TimerMetadata)> {
let metadata = TIMER_METADATA.lock().unwrap();
metadata
.iter()
.map(|(id, meta)| (*id, meta.clone()))
.collect()
}
pub fn remove_timer_metadata(timer_id: u64) {
forget_owned_timer(timer_id);
let mut metadata = TIMER_METADATA.lock().unwrap();
metadata.remove(&timer_id);
}
fn throw_timer_schedule_error(scope: &mut v8::PinScope, err: TimerScheduleError) {
let message = v8::String::new(scope, &format!("timer scheduling failed: {}", err)).unwrap();
let error = v8::Exception::error(scope, message);
scope.throw_exception(error.into());
}
fn cleanup_scheduled_timer_state(timer_id: u64) {
remove_timer_metadata(timer_id);
let _ = remove_timer_callback(timer_id);
}
pub fn remove_timer_callback(
timer_id: u64,
) -> Option<(v8::Global<v8::Function>, Vec<v8::Global<v8::Value>>)> {
let mut storage = TIMER_CALLBACKS.lock().unwrap();
storage.remove(timer_id)
}
fn clone_timer_callback(
timer_id: u64,
) -> Option<(v8::Global<v8::Function>, Vec<v8::Global<v8::Value>>)> {
let storage = TIMER_CALLBACKS.lock().unwrap();
let callback = storage.callbacks.get(&timer_id)?.clone();
let args = storage.args.get(&timer_id).cloned().unwrap_or_default();
Some((callback, args))
}
fn create_timer_object<'a>(
scope: &mut v8::PinScope<'a, '_>,
timer_id: u64,
timer_type: TimerType,
) -> v8::Local<'a, v8::Object> {
let timer_obj = v8::Object::new(scope);
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_value = v8::Number::new(scope, timer_id as f64);
timer_obj.set(scope, id_key.into(), id_value.into());
let type_key = v8::String::new(scope, "_timerType").unwrap();
let type_value = v8::String::new(
scope,
match timer_type {
TimerType::Timeout => "Timeout",
TimerType::Interval => "Interval",
TimerType::Immediate => "Immediate",
},
)
.unwrap();
timer_obj.set(scope, type_key.into(), type_value.into());
let unref_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let this = args.this();
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_val = this.get(scope, id_key.into()).unwrap();
let timer_id_val = id_val.to_integer(scope).unwrap().value() as u64;
let mut metadata = TIMER_METADATA.lock().unwrap();
if let Some(info) = metadata.get_mut(&timer_id_val) {
info.is_unrefed = true;
}
retval.set(this.into());
},
)
.unwrap();
let unref_key = v8::String::new(scope, "unref").unwrap();
timer_obj.set(scope, unref_key.into(), unref_fn.into());
let ref_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let this = args.this();
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_val = this.get(scope, id_key.into()).unwrap();
let timer_id_val = id_val.to_integer(scope).unwrap().value() as u64;
let mut metadata = TIMER_METADATA.lock().unwrap();
if let Some(info) = metadata.get_mut(&timer_id_val) {
info.is_unrefed = false;
}
retval.set(this.into());
},
)
.unwrap();
let ref_key = v8::String::new(scope, "ref").unwrap();
timer_obj.set(scope, ref_key.into(), ref_fn.into());
let refresh_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let this = args.this();
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_val = this.get(scope, id_key.into()).unwrap();
let timer_id_val = id_val.to_integer(scope).unwrap().value() as u64;
let mut metadata = TIMER_METADATA.lock().unwrap();
if let Some(info) = metadata.get_mut(&timer_id_val) {
let effective_delay = if info.delay == 0 { 1 } else { info.delay };
drop(metadata);
if let Err(err) = get_async_timer_manager().try_schedule_timeout(
Duration::from_millis(effective_delay),
timer_id_val,
|| {},
) {
throw_timer_schedule_error(scope, err);
return;
}
}
retval.set(this.into());
},
)
.unwrap();
let refresh_key = v8::String::new(scope, "refresh").unwrap();
timer_obj.set(scope, refresh_key.into(), refresh_fn.into());
let has_ref_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let this = args.this();
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_val = this.get(scope, id_key.into()).unwrap();
let timer_id_val = id_val.to_integer(scope).unwrap().value() as u64;
let metadata = TIMER_METADATA.lock().unwrap();
let is_unrefed = metadata
.get(&timer_id_val)
.map(|m| m.is_unrefed)
.unwrap_or(true);
retval.set(v8::Boolean::new(scope, !is_unrefed).into());
},
)
.unwrap();
let has_ref_key = v8::String::new(scope, "hasRef").unwrap();
timer_obj.set(scope, has_ref_key.into(), has_ref_fn.into());
let value_of_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let this = args.this();
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_val = this.get(scope, id_key.into()).unwrap();
let timer_id_val = id_val.to_number(scope).unwrap().value();
retval.set(v8::Number::new(scope, timer_id_val).into());
},
)
.unwrap();
let value_of_key = v8::String::new(scope, "valueOf").unwrap();
timer_obj.set(scope, value_of_key.into(), value_of_fn.into());
let delay_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let this = args.this();
let id_key = v8::String::new(scope, "_timerId").unwrap();
let id_val = this.get(scope, id_key.into()).unwrap();
let timer_id_val = id_val.to_integer(scope).unwrap().value() as u64;
if args.length() == 0 {
let metadata = TIMER_METADATA.lock().unwrap();
let delay = metadata.get(&timer_id_val).map(|m| m.delay).unwrap_or(0);
retval.set(v8::Number::new(scope, delay as f64).into());
} else {
let new_delay = args
.get(0)
.to_integer(scope)
.map(|i| i.value().max(0) as u64)
.unwrap_or(0);
{
let mut metadata = TIMER_METADATA.lock().unwrap();
if let Some(info) = metadata.get_mut(&timer_id_val) {
info.delay = new_delay;
}
}
let type_key = v8::String::new(scope, "_timerType").unwrap();
let type_val = this.get(scope, type_key.into()).unwrap();
let type_str = type_val
.to_string(scope)
.unwrap()
.to_rust_string_lossy(scope);
if type_str != "Immediate" && new_delay > 0 {
let effective_delay = if new_delay == 0 { 1 } else { new_delay };
if let Err(err) = get_async_timer_manager().try_schedule_timeout(
Duration::from_millis(effective_delay),
timer_id_val,
|| {},
) {
throw_timer_schedule_error(scope, err);
return;
}
}
retval.set(this.into());
}
},
)
.unwrap();
let delay_key = v8::String::new(scope, "delay").unwrap();
timer_obj.set(scope, delay_key.into(), delay_fn.into());
timer_obj
}
pub fn setup_timers_api(
scope: &mut v8::ContextScope<v8::HandleScope>,
context: &v8::Local<v8::Context>,
) -> Result<(), anyhow::Error> {
let global = context.global(scope);
let set_timeout_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
if args.length() < 1 {
let error =
v8::String::new(scope, "setTimeout requires at least 1 argument").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback = args.get(0);
if !callback.is_function() {
let error =
v8::String::new(scope, "setTimeout: callback must be a function").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let delay = args
.get(1)
.to_integer(scope)
.map(|i| i.value().max(0) as u64)
.unwrap_or(0);
let callback_args: Vec<v8::Local<v8::Value>> =
(2..args.length()).map(|i| args.get(i)).collect();
let timer_id = get_next_timer_id();
let callback_fn = v8::Local::<v8::Function>::try_from(callback).unwrap();
let callback_global = v8::Global::new(scope, callback_fn);
let args_global: Vec<v8::Global<v8::Value>> = callback_args
.iter()
.map(|v| v8::Global::new(scope, v.clone()))
.collect();
track_owned_timer(timer_id);
let mut metadata = TIMER_METADATA.lock().unwrap();
metadata.insert(
timer_id,
TimerMetadata {
timer_type: TimerType::Timeout,
delay,
is_unrefed: false,
epoch: get_timer_epoch(),
},
);
drop(metadata);
store_timer_callback(timer_id, callback_global, args_global);
if delay == 0 {
get_async_timer_manager().mark_timer_fired(timer_id);
} else if let Err(err) = get_async_timer_manager().try_schedule_timeout(
Duration::from_millis(delay),
timer_id,
|| {},
) {
cleanup_scheduled_timer_state(timer_id);
throw_timer_schedule_error(scope, err);
return;
}
let timer_obj = create_timer_object(scope, timer_id, TimerType::Timeout);
retval.set(timer_obj.into());
},
)
.unwrap();
let set_interval_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
if args.length() < 1 {
let error =
v8::String::new(scope, "setInterval requires at least 1 argument").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback = args.get(0);
if !callback.is_function() {
let error =
v8::String::new(scope, "setInterval: callback must be a function").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let delay = args
.get(1)
.to_integer(scope)
.map(|i| i.value().max(0) as u64)
.unwrap_or(0);
let effective_delay = delay.max(1);
let timer_id = get_next_timer_id();
let callback_args: Vec<v8::Local<v8::Value>> =
(2..args.length()).map(|i| args.get(i)).collect();
let callback_fn = v8::Local::<v8::Function>::try_from(callback).unwrap();
let callback_global = v8::Global::new(scope, callback_fn);
let args_global: Vec<v8::Global<v8::Value>> = callback_args
.iter()
.map(|v| v8::Global::new(scope, v.clone()))
.collect();
track_owned_timer(timer_id);
let mut metadata = TIMER_METADATA.lock().unwrap();
metadata.insert(
timer_id,
TimerMetadata {
timer_type: TimerType::Interval,
delay: effective_delay,
is_unrefed: false,
epoch: get_timer_epoch(),
},
);
drop(metadata);
store_timer_callback(timer_id, callback_global, args_global);
if let Err(err) = get_async_timer_manager().try_schedule_interval(
Duration::from_millis(effective_delay),
0,
timer_id,
|| {},
) {
cleanup_scheduled_timer_state(timer_id);
throw_timer_schedule_error(scope, err);
return;
}
let timer_obj = create_timer_object(scope, timer_id, TimerType::Interval);
retval.set(timer_obj.into());
},
)
.unwrap();
let set_immediate_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
if args.length() < 1 {
let error =
v8::String::new(scope, "setImmediate requires at least 1 argument").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback = args.get(0);
if !callback.is_function() {
let error =
v8::String::new(scope, "setImmediate: callback must be a function").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback_args: Vec<v8::Local<v8::Value>> =
(1..args.length()).map(|i| args.get(i)).collect();
let timer_id = get_next_timer_id();
track_owned_timer(timer_id);
let mut metadata = TIMER_METADATA.lock().unwrap();
metadata.insert(
timer_id,
TimerMetadata {
timer_type: TimerType::Immediate,
delay: 0,
is_unrefed: false,
epoch: get_timer_epoch(),
},
);
drop(metadata);
let callback_fn = v8::Local::<v8::Function>::try_from(callback).unwrap();
let callback_global = v8::Global::new(scope, callback_fn);
let args_global: Vec<v8::Global<v8::Value>> = callback_args
.iter()
.map(|v| v8::Global::new(scope, v.clone()))
.collect();
store_immediate_callback(timer_id, callback_global, args_global);
let timer_obj = create_timer_object(scope, timer_id, TimerType::Immediate);
retval.set(timer_obj.into());
},
)
.unwrap();
let clear_timer_fn = v8::Function::new(
scope,
|_scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
_retval: v8::ReturnValue| {
if args.length() < 1 {
return;
}
let timer_arg = args.get(0);
let mut timer_id: u64 = 0;
if timer_arg.is_number() {
timer_id = timer_arg
.to_integer(_scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
} else if timer_arg.is_object() {
let timer_obj = v8::Local::<v8::Object>::try_from(timer_arg).ok();
if let Some(obj) = timer_obj {
let id_key = v8::String::new(_scope, "_timerId").unwrap();
if let Some(id_val) = obj.get(_scope, id_key.into()) {
timer_id = id_val
.to_integer(_scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
}
}
}
if timer_id > 0 {
forget_owned_timer(timer_id);
let mut metadata = TIMER_METADATA.lock().unwrap();
metadata.remove(&timer_id);
let _ = get_async_timer_manager().cancel(timer_id);
let _ = remove_timer_callback(timer_id);
let _ = remove_immediate_callback(timer_id);
}
},
)
.unwrap();
let unref_fn = v8::Function::new(
scope,
|_scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
if args.length() < 1 {
retval.set(v8::Boolean::new(_scope, false).into());
return;
}
let timer_id_val = args.get(0);
let timer_id = timer_id_val
.to_integer(_scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
if timer_id > 0 {
let result = set_timer_unrefed(timer_id, true);
retval.set(v8::Boolean::new(_scope, result).into());
} else {
retval.set(v8::Boolean::new(_scope, false).into());
}
},
)
.unwrap();
let ref_fn = v8::Function::new(
scope,
|_scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
if args.length() < 1 {
retval.set(v8::Boolean::new(_scope, false).into());
return;
}
let timer_id_val = args.get(0);
let timer_id = timer_id_val
.to_integer(_scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
if timer_id > 0 {
let result = set_timer_unrefed(timer_id, false);
retval.set(v8::Boolean::new(_scope, result).into());
} else {
retval.set(v8::Boolean::new(_scope, false).into());
}
},
)
.unwrap();
let queue_microtask_fn = v8::Function::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
_retval: v8::ReturnValue| {
if args.length() < 1 {
let error =
v8::String::new(scope, "queueMicrotask requires at least 1 argument").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback = args.get(0);
if !callback.is_function() {
let error =
v8::String::new(scope, "queueMicrotask: callback must be a function").unwrap();
let error_obj = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback_fn = v8::Local::<v8::Function>::try_from(callback).unwrap();
scope.enqueue_microtask(callback_fn);
},
)
.unwrap();
let set_timeout_key = v8::String::new(scope, "setTimeout").unwrap();
let set_interval_key = v8::String::new(scope, "setInterval").unwrap();
let set_immediate_key = v8::String::new(scope, "setImmediate").unwrap();
let clear_timeout_key = v8::String::new(scope, "clearTimeout").unwrap();
let clear_interval_key = v8::String::new(scope, "clearInterval").unwrap();
let clear_immediate_key = v8::String::new(scope, "clearImmediate").unwrap();
let unref_key = v8::String::new(scope, "unref").unwrap();
let ref_key = v8::String::new(scope, "ref").unwrap();
let queue_microtask_key = v8::String::new(scope, "queueMicrotask").unwrap();
global.set(scope, set_timeout_key.into(), set_timeout_fn.into());
global.set(scope, set_interval_key.into(), set_interval_fn.into());
global.set(scope, set_immediate_key.into(), set_immediate_fn.into());
global.set(scope, clear_timeout_key.into(), clear_timer_fn.into());
global.set(scope, clear_interval_key.into(), clear_timer_fn.into());
global.set(scope, clear_immediate_key.into(), clear_timer_fn.into());
global.set(scope, unref_key.into(), unref_fn.into());
global.set(scope, ref_key.into(), ref_fn.into());
global.set(scope, queue_microtask_key.into(), queue_microtask_fn.into());
Ok(())
}
pub fn set_timer_unrefed(timer_id: u64, unrefed: bool) -> bool {
let mut metadata = TIMER_METADATA.lock().unwrap();
if let Some(meta) = metadata.get_mut(&timer_id) {
meta.is_unrefed = unrefed;
true
} else {
false
}
}
pub fn is_timer_unrefed(timer_id: u64) -> bool {
let metadata = TIMER_METADATA.lock().unwrap();
metadata
.get(&timer_id)
.map(|meta| meta.is_unrefed)
.unwrap_or(false)
}
pub fn clear_all_async_timers() {
let manager = get_async_timer_manager();
for timer_id in owned_timer_ids() {
let _ = manager.cancel(timer_id);
}
}
pub fn clear_all_timers() {
let owned = take_owned_timer_ids();
if owned.is_empty() {
return;
}
let manager = get_async_timer_manager();
for timer_id in &owned {
let _ = manager.cancel(*timer_id);
}
if let Ok(mut metadata) = TIMER_METADATA.lock() {
metadata.retain(|id, _| !owned.contains(id));
}
}
pub fn clear_all_timer_callbacks() {
let mut storage = TIMER_CALLBACKS.lock().unwrap();
storage.callbacks.clear();
storage.args.clear();
IMMEDIATE_CALLBACKS.with(|s| s.borrow_mut().clear_all());
}
pub fn cleanup_all_timers() {
clear_all_timer_callbacks();
clear_all_timers();
clear_all_async_timers();
}
pub fn store_timer_callback(
timer_id: u64,
callback: v8::Global<v8::Function>,
args: Vec<v8::Global<v8::Value>>,
) {
let mut storage = TIMER_CALLBACKS.lock().unwrap();
storage.insert(timer_id, callback, args);
}
pub fn take_timer_callback(
timer_id: u64,
) -> Option<(v8::Global<v8::Function>, Vec<v8::Global<v8::Value>>)> {
let mut storage = TIMER_CALLBACKS.lock().unwrap();
storage.remove(timer_id)
}
pub fn execute_timer_callback(scope: &mut v8::PinScope, timer_id: u64) -> bool {
let timer_type = {
let metadata = TIMER_METADATA.lock().unwrap();
metadata.get(&timer_id).map(|meta| meta.timer_type)
};
let callback_entry = if matches!(timer_type, Some(TimerType::Interval)) {
clone_timer_callback(timer_id)
} else {
take_timer_callback(timer_id)
};
if let Some((callback, args)) = callback_entry {
let callback = v8::Local::<v8::Function>::new(scope, callback);
let args: Vec<v8::Local<v8::Value>> = args
.into_iter()
.map(|arg| v8::Local::new(scope, arg))
.collect();
let undefined = v8::undefined(scope);
let result = callback.call(scope, undefined.into(), &args);
let metadata = TIMER_METADATA.lock().unwrap();
if let Some(meta) = metadata.get(&timer_id) {
if meta.timer_type != TimerType::Interval {
drop(metadata);
remove_timer_metadata(timer_id);
}
}
result.is_some()
} else {
false
}
}
pub fn execute_fired_timers(scope: &mut v8::PinScope) {
let timer_manager = get_async_timer_manager();
let fired_timers = timer_manager.poll_fired_timers();
for timer_id in fired_timers {
let timer_is_active = {
let metadata = TIMER_METADATA.lock().unwrap();
metadata.contains_key(&timer_id)
};
if !timer_is_active {
let _ = remove_timer_callback(timer_id);
continue;
}
if !owns_timer(timer_id) {
timer_manager.mark_timer_fired(timer_id);
continue;
}
let callback_exists = {
let storage = TIMER_CALLBACKS.lock().unwrap();
storage.callbacks.contains_key(&timer_id)
};
if !callback_exists {
continue;
}
let _ = execute_timer_callback(scope, timer_id);
}
}
pub fn execute_immediate_callbacks(scope: &mut v8::PinScope) {
let callbacks = IMMEDIATE_CALLBACKS.with(|s| s.borrow_mut().drain_non_deferred());
for (timer_id, callback, args) in callbacks {
let callback = v8::Local::<v8::Function>::new(scope, callback);
let args: Vec<v8::Local<v8::Value>> = args
.into_iter()
.map(|arg| v8::Local::new(scope, arg))
.collect();
let undefined = v8::undefined(scope);
let _ = callback.call(scope, undefined.into(), &args);
remove_timer_metadata(timer_id);
}
}
pub fn has_pending_immediates() -> bool {
IMMEDIATE_CALLBACKS.with(|s| !s.borrow().is_empty())
}
pub fn has_pending_zero_delay_timers() -> bool {
let owned = owned_timer_ids();
let metadata = TIMER_METADATA.lock().unwrap();
metadata.iter().any(|(id, meta)| {
owned.contains(id)
&& meta.delay == 0
&& matches!(meta.timer_type, TimerType::Timeout | TimerType::Interval)
})
}
pub fn has_pending_drainable_timeout_timers(max_delay_ms: u64) -> bool {
let owned = owned_timer_ids();
let metadata = TIMER_METADATA.lock().unwrap();
metadata.iter().any(|(id, meta)| {
owned.contains(id)
&& !meta.is_unrefed
&& meta.delay <= max_delay_ms
&& matches!(meta.timer_type, TimerType::Timeout)
})
}
pub fn has_pending_drainable_timers(max_delay_ms: u64) -> bool {
let owned = owned_timer_ids();
let metadata = TIMER_METADATA.lock().unwrap();
metadata.iter().any(|(id, meta)| {
owned.contains(id)
&& !meta.is_unrefed
&& meta.delay <= max_delay_ms
&& matches!(meta.timer_type, TimerType::Timeout | TimerType::Interval)
})
}
pub fn has_pending_refed_timers() -> bool {
has_pending_drainable_timers(u64::MAX)
}
pub fn store_immediate_callback(
timer_id: u64,
callback: v8::Global<v8::Function>,
args: Vec<v8::Global<v8::Value>>,
) {
IMMEDIATE_CALLBACKS.with(|s| {
s.borrow_mut().push(timer_id, callback, args, false);
});
}
pub fn mark_immediate_callbacks_deferred() {
IMMEDIATE_CALLBACKS.with(|s| s.borrow_mut().mark_all_as_deferred());
}
pub fn unmark_immediate_callbacks_deferred() {
IMMEDIATE_CALLBACKS.with(|s| s.borrow_mut().unmark_all_deferred());
}
pub fn clear_pending_immediates() {
IMMEDIATE_CALLBACKS.with(|s| s.borrow_mut().clear_all());
}
pub fn remove_immediate_callback(timer_id: u64) -> bool {
IMMEDIATE_CALLBACKS.with(|s| s.borrow_mut().remove(timer_id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timer_creation() {
let timer_id = get_next_timer_id();
assert!(timer_id > 0);
}
#[test]
fn test_timer_metadata() {
let mut metadata = TIMER_METADATA.lock().unwrap();
metadata.insert(
1,
TimerMetadata {
timer_type: TimerType::Timeout,
delay: 1000,
is_unrefed: false,
epoch: get_timer_epoch(),
},
);
assert!(metadata.contains_key(&1));
}
#[test]
fn test_timer_type_variants() {
assert_eq!(TimerType::Timeout, TimerType::Timeout);
assert_eq!(TimerType::Interval, TimerType::Interval);
assert_eq!(TimerType::Immediate, TimerType::Immediate);
}
}