use core::ptr::NonNull;
use bun_alloc::Arena; use bun_core::{self, Output, zstr};
use bun_io as Async;
use bun_threading::unbounded_queue::{Node, UnboundedQueue};
use crate::bundle_v2::{FileMap, JSBundlerPlugin, dispatch};
use crate::{BundleV2, Transpiler};
#[cfg(windows)]
pub(crate) extern "C" fn timer_callback(_: *mut bun_sys::windows::libuv::Timer) {}
pub use bun_threading::ResetEvent;
pub struct BuildResult {
pub output_files: Vec<crate::options::OutputFile>,
pub metafile: Option<Box<[u8]>>,
pub metafile_markdown: Option<Box<[u8]>>,
}
pub enum BundleV2Result {
Pending,
Err(bun_core::Error),
Value(BuildResult),
}
pub struct BundleThread<C: Node> {
pub waker: Async::Waker,
pub ready_event: ResetEvent,
pub queue: UnboundedQueue<C>,
pub generation: bun_core::Generation,
}
pub trait CompletionStruct: Node + Send + 'static {
fn configure_bundler<'a>(
&mut self,
transpiler: &mut Transpiler<'a>,
bump: &'a Arena,
) -> Result<(), bun_core::Error>;
fn complete_on_bundle_thread(&mut self);
fn set_result(&mut self, result: BundleV2Result);
fn set_log(&mut self, log: bun_ast::Log);
fn set_transpiler(&mut self, this: *mut BundleV2<'_>);
fn plugins(&self) -> Option<NonNull<JSBundlerPlugin>>;
fn file_map(&mut self) -> Option<NonNull<FileMap>>;
fn as_js_bundle_completion_task(&mut self) -> dispatch::CompletionHandle;
#[allow(clippy::mut_from_ref)]
fn create_and_configure_transpiler<'a>(
&mut self,
bump: &'a Arena,
) -> Result<&'a mut Transpiler<'a>, bun_core::Error>;
fn init_and_run<'a>(
&mut self,
transpiler: &'a mut Transpiler<'a>,
bump: &'a Arena,
thread_pool: *mut bun_threading::ThreadPool,
) -> Result<(), bun_core::Error>;
}
impl<C: CompletionStruct> BundleThread<C> {
pub fn uninitialized() -> Self {
Self {
#[cfg(unix)]
waker: Async::Waker::placeholder(),
#[cfg(windows)]
waker: unsafe { bun_core::ffi::zeroed_unchecked() },
queue: UnboundedQueue::new(),
generation: 0,
ready_event: ResetEvent::default(),
}
}
pub unsafe fn spawn(instance: *mut Self) -> std::io::Result<std::thread::JoinHandle<()>> {
struct SendPtr<T>(*mut T);
unsafe impl<T> Send for SendPtr<T> {}
let ptr = SendPtr(instance);
let thread = std::thread::Builder::new()
.name("Bundler".into())
.spawn(move || {
let ptr = ptr;
unsafe { Self::thread_main(ptr.0) }
})?;
unsafe { (*instance).ready_event.wait() };
Ok(thread)
}
pub unsafe fn enqueue(instance: *mut Self, completion: *mut C) {
let completion = unsafe { core::ptr::NonNull::new_unchecked(completion) };
unsafe {
(*instance).queue.push(completion);
(*instance).waker.wake();
}
}
unsafe fn thread_main(instance: *mut Self) {
Output::Source::configure_named_thread(zstr!("Bundler"));
unsafe {
core::ptr::addr_of_mut!((*instance).waker)
.write(Async::Waker::init().unwrap_or_else(|_| panic!("Failed to create waker")));
}
unsafe { (*instance).ready_event.set() };
#[cfg(windows)]
let mut timer: bun_sys::windows::libuv::Timer = bun_core::ffi::zeroed();
#[cfg(windows)]
{
timer.init(unsafe { (*instance).waker.uv_loop() });
timer.start(u64::MAX, u64::MAX, Some(timer_callback));
}
let mut has_bundled = false;
loop {
loop {
let completion = unsafe { (*instance).queue.pop() };
if completion.is_null() {
break;
}
let completion = unsafe { &mut *completion };
let generation = unsafe { (*instance).generation };
match Self::generate_in_new_thread(completion, generation) {
Ok(()) => {}
Err(err) => {
completion.set_result(BundleV2Result::Err(err));
completion.complete_on_bundle_thread();
}
}
has_bundled = true;
}
unsafe {
let g = core::ptr::addr_of_mut!((*instance).generation);
*g = (*g).saturating_add(1);
}
if has_bundled {
bun_alloc::mimalloc::mi_collect(false);
has_bundled = false;
}
unsafe { (*instance).waker.wait() };
}
}
fn generate_in_new_thread(
completion: &mut C,
generation: bun_core::Generation,
) -> Result<(), bun_core::Error> {
let heap = Arena::new();
let bump = &heap;
let ast_memory_store: &mut bun_ast::ASTMemoryAllocator =
bump.alloc(bun_ast::ASTMemoryAllocator::new(bump));
ast_memory_store.reset();
ast_memory_store.push();
let transpiler = completion.create_and_configure_transpiler(bump)?;
transpiler.resolver.generation = generation;
let transpiler_ptr: *mut Transpiler<'_> = transpiler;
let run = completion.init_and_run(
unsafe { &mut *transpiler_ptr },
bump,
std::ptr::from_ref(bun_threading::work_pool::WorkPool::get()).cast_mut(),
);
let mut out_log = bun_ast::Log::init();
let _ = unsafe { (*(*transpiler_ptr).log).append_to_with_recycled(&mut out_log, true) }; completion.set_log(out_log);
if run.is_ok() {
completion.complete_on_bundle_thread();
}
ast_memory_store.pop();
unsafe {
core::ptr::drop_in_place(transpiler_ptr);
core::ptr::drop_in_place(std::ptr::from_mut::<bun_ast::ASTMemoryAllocator>(
ast_memory_store,
));
}
run
}
}
pub mod singleton {
use super::*;
struct Instance(NonNull<()>);
unsafe impl Send for Instance {}
unsafe impl Sync for Instance {}
static INSTANCE: std::sync::OnceLock<Instance> = std::sync::OnceLock::new();
fn load_once_impl<C: CompletionStruct>() -> Instance {
let bundle_thread = bun_core::heap::into_raw(Box::new(BundleThread::<C>::uninitialized()));
let os_thread = unsafe { BundleThread::spawn(bundle_thread) }
.unwrap_or_else(|_| Output::panic(format_args!("Failed to spawn bun build thread")));
drop(os_thread);
Instance(unsafe { NonNull::new_unchecked(bundle_thread.cast::<()>()) })
}
pub fn get<C: CompletionStruct>() -> *mut BundleThread<C> {
INSTANCE
.get_or_init(load_once_impl::<C>)
.0
.as_ptr()
.cast::<BundleThread<C>>()
}
pub fn enqueue<C: CompletionStruct>(completion: *mut C) {
let completion = NonNull::new(completion).unwrap_or_else(|| {
Output::panic(format_args!("BundleThread enqueue: null completion"))
});
unsafe { BundleThread::enqueue(get::<C>(), completion.as_ptr()) };
}
}
pub use crate::DeferredBatchTask;
pub use crate::ParseTask;
pub use crate::ThreadPool;