use core::cell::Cell;
use core::ptr::NonNull;
use bun_core::{Timespec, TimespecMockMode};
#[cfg(windows)]
use bun_sys::windows::libuv;
#[cfg(windows)]
use bun_sys::windows::libuv::UvHandle as _;
use bun_uws as uws;
use crate::EventLoopHandle;
#[cfg(unix)]
pub type VmEventLoopHandle = Option<NonNull<uws::Loop>>;
#[cfg(windows)]
pub type VmEventLoopHandle = Option<NonNull<libuv::Loop>>;
unsafe extern "Rust" {
safe fn __bun_spawn_sync_create_event_loop(vm: *mut (), uws_loop: *mut uws::Loop) -> *mut ();
safe fn __bun_spawn_sync_destroy_event_loop(el: *mut ());
safe fn __bun_spawn_sync_event_loop_set_vm(el: *mut (), vm: *mut ());
safe fn __bun_spawn_sync_event_loop_tick_tasks_only(el: *mut ());
safe fn __bun_spawn_sync_vm_get_event_loop_handle(vm: *mut ()) -> VmEventLoopHandle;
safe fn __bun_spawn_sync_vm_set_event_loop_handle(vm: *mut (), h: VmEventLoopHandle);
safe fn __bun_spawn_sync_vm_set_event_loop(vm: *mut (), el: *mut ());
safe fn __bun_spawn_sync_vm_swap_suppress_microtask_drain(vm: *mut (), v: bool) -> bool;
}
struct SuppressMicrotaskDrain {
vm: *mut (),
prev: bool,
}
impl SuppressMicrotaskDrain {
#[inline]
fn new(vm: *mut ()) -> Self {
let prev = __bun_spawn_sync_vm_swap_suppress_microtask_drain(vm, true);
Self { vm, prev }
}
}
impl Drop for SuppressMicrotaskDrain {
#[inline]
fn drop(&mut self) {
__bun_spawn_sync_vm_swap_suppress_microtask_drain(self.vm, self.prev);
}
}
pub struct SpawnSyncEventLoop {
event_loop: *mut (),
vm: *mut (),
uws_loop: NonNull<uws::Loop>,
original_event_loop_handle: VmEventLoopHandle,
#[cfg(windows)]
uv_timer: Option<NonNull<libuv::Timer>>,
did_timeout: Cell<bool>,
}
mod handler {
use super::uws;
pub(super) extern "C" fn wakeup(_loop: *mut uws::Loop) {
}
pub(super) extern "C" fn pre(_loop: *mut uws::Loop) {
}
pub(super) extern "C" fn post(_loop: *mut uws::Loop) {
}
pub(super) struct Handler;
impl uws::LoopHandler for Handler {
const WAKEUP: unsafe extern "C" fn(*mut uws::Loop) = wakeup;
const PRE: Option<unsafe extern "C" fn(*mut uws::Loop)> = Some(pre);
const POST: Option<unsafe extern "C" fn(*mut uws::Loop)> = Some(post);
}
}
impl SpawnSyncEventLoop {
pub fn init(
this: &mut core::mem::MaybeUninit<Self>,
vm: *mut (),
) {
let loop_ = uws::Loop::create::<handler::Handler>();
let loop_ =
NonNull::new(loop_).expect("uws::Loop::create never returns null (asserts on OOM)");
let event_loop = __bun_spawn_sync_create_event_loop(vm, loop_.as_ptr());
this.write(Self {
uws_loop: loop_,
original_event_loop_handle: None, #[cfg(windows)]
uv_timer: None,
did_timeout: Cell::new(false),
event_loop,
vm,
});
let this = unsafe { this.assume_init_mut() };
debug_assert!(!this.event_loop.is_null(), "spawn-sync event loop alloc");
let (tag, ptr) = EventLoopHandle::init(this.event_loop).into_tag_ptr();
let loop_data = &mut this.uws_loop_mut().internal_loop_data;
loop_data.set_parent_raw(tag, ptr);
loop_data.jsc_vm = core::ptr::null();
}
#[inline]
pub fn event_loop_ptr(&self) -> *mut () {
self.event_loop
}
#[inline]
pub fn vm_ptr(&self) -> *mut () {
self.vm
}
#[inline]
pub fn uws_loop(&self) -> &uws::Loop {
unsafe { self.uws_loop.as_ref() }
}
#[inline]
pub fn uws_loop_mut(&mut self) -> &mut uws::Loop {
unsafe { self.uws_loop.as_mut() }
}
#[cfg(windows)]
#[inline]
fn uv_timer_mut(&mut self) -> Option<&mut libuv::Timer> {
self.uv_timer.as_mut().map(|p| unsafe { p.as_mut() })
}
}
#[cfg(windows)]
extern "C" fn on_close_uv_timer(timer: *mut libuv::Timer) {
drop(unsafe { bun_core::heap::take(timer) });
}
impl Drop for SpawnSyncEventLoop {
fn drop(&mut self) {
#[cfg(windows)]
{
if let Some(timer) = self.uv_timer.take() {
unsafe {
(*timer.as_ptr()).stop();
(*timer.as_ptr()).unref();
(*timer.as_ptr()).close(on_close_uv_timer);
}
}
}
__bun_spawn_sync_destroy_event_loop(self.event_loop);
unsafe { uws::Loop::destroy(self.uws_loop.as_ptr()) };
}
}
impl SpawnSyncEventLoop {
pub fn prepare(&mut self, vm: *mut () ) {
__bun_spawn_sync_event_loop_set_vm(self.event_loop, vm);
self.did_timeout.set(false);
self.vm = vm;
self.original_event_loop_handle = __bun_spawn_sync_vm_get_event_loop_handle(vm);
#[cfg(unix)]
let new_handle: VmEventLoopHandle = Some(self.uws_loop);
#[cfg(windows)]
let new_handle: VmEventLoopHandle = Some(
NonNull::new(self.uws_loop().uv_loop)
.expect("uv_loop is set by us_create_loop for the loop's lifetime"),
);
__bun_spawn_sync_vm_set_event_loop_handle(vm, new_handle);
}
pub fn cleanup(
&mut self,
vm: *mut (),
prev_event_loop: *mut (),
) {
__bun_spawn_sync_vm_set_event_loop_handle(vm, self.original_event_loop_handle);
__bun_spawn_sync_vm_set_event_loop(vm, prev_event_loop);
#[cfg(windows)]
{
if let Some(timer) = self.uv_timer_mut() {
timer.stop();
timer.unref();
}
}
}
pub fn handle(&mut self) -> EventLoopHandle {
debug_assert!(!self.event_loop.is_null(), "spawn-sync event loop");
EventLoopHandle::init(self.event_loop)
}
}
#[cfg(windows)]
extern "C" fn on_uv_timer(timer_: *mut libuv::Timer) {
unsafe {
let this: *mut SpawnSyncEventLoop = (*timer_).data.cast::<SpawnSyncEventLoop>();
(*this).did_timeout.set(true);
(*libuv::uv_handle_get_loop(timer_.cast())).stop();
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum TickState {
Timeout,
Completed,
}
impl SpawnSyncEventLoop {
#[cfg(windows)]
fn prepare_timer_on_windows(&mut self, ts: &Timespec) {
if self.uv_timer.is_none() {
let uv_timer: Box<libuv::Timer> = Box::new(bun_core::ffi::zeroed());
self.uv_timer = Some(bun_core::heap::into_raw_nn(uv_timer));
let uv_loop = self.uws_loop().uv_loop;
self.uv_timer_mut().expect("just set").init(uv_loop);
}
let timer = self.uv_timer_mut().expect("set above");
timer.start(ts.ms_unsigned(), 0, Some(on_uv_timer));
timer.ref_();
}
pub fn tick_with_timeout(&mut self, timeout: Option<&Timespec>) -> TickState {
let duration_storage: Option<Timespec>;
let duration: Option<&Timespec> = match timeout {
Some(ts) => {
duration_storage =
Some(ts.duration(&Timespec::now(TimespecMockMode::AllowMockedTime)));
duration_storage.as_ref()
}
None => None,
};
#[cfg(windows)]
{
if let Some(ts) = duration {
self.prepare_timer_on_windows(ts);
}
}
let _suppress = SuppressMicrotaskDrain::new(self.vm);
let loop_ = self.uws_loop;
#[cfg(windows)]
if let Some(t) = self.uv_timer {
unsafe { (*t.as_ptr()).data = (core::ptr::from_mut(self)).cast() };
}
unsafe { (*loop_.as_ptr()).tick_with_timeout(duration) };
if let Some(ts) = timeout {
#[cfg(windows)]
let _ = ts;
#[cfg(windows)]
{
let t = self
.uv_timer_mut()
.expect("set in prepare_timer_on_windows");
t.unref();
t.stop();
}
#[cfg(not(windows))]
{
self.did_timeout.set(
Timespec::now(TimespecMockMode::AllowMockedTime).order(ts)
!= core::cmp::Ordering::Less,
);
}
}
__bun_spawn_sync_event_loop_tick_tasks_only(self.event_loop);
let did_timeout = self.did_timeout.replace(false);
if did_timeout {
return TickState::Timeout;
}
TickState::Completed
}
pub fn is_active(&self) -> bool {
self.uws_loop().is_active()
}
}