//! Parse files in parallel and then `Send` them to the main thread for processing. #![allow(clippy::future_not_send)] // clippy warns `Allocator` is not `Send` #![allow(clippy::redundant_pub_crate)] // comes from `ouroboros`'s macro // Instruction: // run `cargo run -p oxc_parser --example multi-thread` // or `cargo watch -x "run -p oxc_parser --example multi-thread"` use std::sync::mpsc; use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_parser::Parser; use oxc_span::SourceType; use std::time::{SystemTime, UNIX_EPOCH}; use std::{sync::Arc, thread}; /// Wrap the AST for unsafe `Send` and `Sync` struct BumpaloProgram<'a>(Program<'a>); // SAFETY: It is now our responsibility to never simultaneously mutate the AST across threads. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl<'a> Send for BumpaloProgram<'a> {} unsafe impl<'a> Sync for BumpaloProgram<'a> {} /// `ouroboros` is used to "bind" the allocator and AST together to remove the lifetime. #[ouroboros::self_referencing] struct AST { index: usize, allocator: Allocator, source_text: Arc, #[borrows(allocator, source_text)] #[not_covariant] ast: &'this BumpaloProgram<'this>, } /// Example output: /// ``` /// sent ast(0) in ThreadId(2) at 1691652865800 /// sent ast(1) in ThreadId(3) at 1691652865801 /// sent ast(2) in ThreadId(4) at 1691652865801 /// received ast(0) in ThreadId(1) at 1691652865801 /// received ast(1) in ThreadId(1) at 1691652865801 /// received ast(2) in ThreadId(1) at 1691652865801 /// ``` fn main() { let (ast_tx, ast_rx) = mpsc::channel::(); let sources = (0..3).map(|i| Arc::from(format!("const a = {i};"))).collect::>(); // Construct AST from different threads for (index, source_text) in sources.iter().enumerate() { let ast_tx = ast_tx.clone(); let source_text = Arc::clone(source_text); _ = thread::spawn(move || { let ast = ASTBuilder { index, allocator: Allocator::default(), source_text, ast_builder: |allocator, source_text| { let ret = Parser::new(allocator, source_text, SourceType::default()).parse(); allocator.alloc(BumpaloProgram(ret.program)) }, } .build(); ast_tx.send(ast).unwrap(); println!("sent ast({index}) in {:?} at {}", thread::current().id(), timestamp()); }) .join(); } // Collect all ASTs on the main thread for _ in 0..sources.len() { let ast = ast_rx.recv().unwrap(); let index = ast.borrow_index(); println!("received ast({index}) in {:?} at {}", thread::current().id(), timestamp()); } } fn timestamp() -> u128 { SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() }