1use crate::builder::QuickJsRuntimeBuilder;
4use crate::jsutils::{JsError, Script};
5use crate::quickjs_utils::{functions, objects};
6use crate::quickjsrealmadapter::QuickJsRealmAdapter;
7use crate::quickjsruntimeadapter::{
8 CompiledModuleLoaderAdapter, MemoryUsage, NativeModuleLoaderAdapter, QuickJsRuntimeAdapter,
9 ScriptModuleLoaderAdapter, QJS_RT,
10};
11use crate::quickjsvalueadapter::QuickJsValueAdapter;
12use crate::reflection;
13use crate::values::JsValueFacade;
14use either::{Either, Left, Right};
15use hirofa_utils::eventloop::EventLoop;
16use hirofa_utils::task_manager::TaskManager;
17use libquickjs_sys as q;
18use lru::LruCache;
19use std::cell::RefCell;
20use std::future::Future;
21use std::num::NonZeroUsize;
22use std::pin::Pin;
23use std::rc::Rc;
24use std::sync::{Arc, Weak};
25use tokio::task::JoinError;
26
27lazy_static! {
28 static ref HELPER_TASKS: TaskManager = TaskManager::new(std::cmp::max(2, num_cpus::get()));
30}
31
32impl Drop for QuickJsRuntimeFacade {
33 fn drop(&mut self) {
34 log::trace!("> EsRuntime::drop");
35 self.clear_contexts();
36 log::trace!("< EsRuntime::drop");
37 }
38}
39
40pub struct QuickjsRuntimeFacadeInner {
41 event_loop: EventLoop,
42}
43
44impl QuickjsRuntimeFacadeInner {
45 pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
62 where
63 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
64 R: Send + 'static,
65 {
66 self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
67 }
68
69 pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
81 &self,
82 consumer: C,
83 ) -> impl Future<Output = R>
84 where
85 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
86 {
87 self.add_task_to_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
88 }
89
90 pub fn add_rt_task_to_event_loop_void<C>(&self, consumer: C)
91 where
92 C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
93 {
94 self.add_task_to_event_loop_void(|| QuickJsRuntimeAdapter::do_with(consumer))
95 }
96
97 pub fn add_task_to_event_loop_void<C>(&self, task: C)
100 where
101 C: FnOnce() + Send + 'static,
102 {
103 self.event_loop.add_void(move || {
104 task();
105 EventLoop::add_local_void(|| {
106 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
107 q_js_rt.run_pending_jobs_if_any();
108 })
109 })
110 });
111 }
112
113 pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
114 where
115 C: FnOnce() -> R + Send + 'static,
116 {
117 self.event_loop.exe(move || {
118 let res = task();
119 EventLoop::add_local_void(|| {
120 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
121 q_js_rt.run_pending_jobs_if_any();
122 })
123 });
124 res
125 })
126 }
127
128 pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
129 where
130 C: FnOnce() -> R + Send + 'static,
131 {
132 self.event_loop.add(move || {
133 let res = task();
134 EventLoop::add_local_void(|| {
135 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
136 q_js_rt.run_pending_jobs_if_any();
137 });
138 });
139 res
140 })
141 }
142
143 #[allow(dead_code)]
145 pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
146 where
147 C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
148 {
149 EventLoop::add_local_void(move || {
150 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
151 consumer(q_js_rt);
152 });
153 EventLoop::add_local_void(|| {
154 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
155 q_js_rt.run_pending_jobs_if_any();
156 })
157 })
158 });
159 }
160}
161
162pub struct QuickJsRuntimeFacade {
170 inner: Arc<QuickjsRuntimeFacadeInner>,
171}
172
173impl QuickJsRuntimeFacade {
174 pub(crate) fn new(mut builder: QuickJsRuntimeBuilder) -> Self {
175 let ret = Self {
176 inner: Arc::new(QuickjsRuntimeFacadeInner {
177 event_loop: EventLoop::new(),
178 }),
179 };
180
181 ret.exe_task_in_event_loop(|| {
182 let rt_ptr = unsafe { q::JS_NewRuntime() };
183 let rt = QuickJsRuntimeAdapter::new(rt_ptr);
184 QuickJsRuntimeAdapter::init_rt_for_current_thread(rt);
185 functions::init_statics();
186 reflection::init_statics();
187 });
188
189 let rti_weak = Arc::downgrade(&ret.inner);
192
193 ret.exe_task_in_event_loop(move || {
194 QuickJsRuntimeAdapter::do_with_mut(move |m_q_js_rt| {
195 m_q_js_rt.init_rti_ref(rti_weak);
196 })
197 });
198
199 #[cfg(any(
202 feature = "settimeout",
203 feature = "setinterval",
204 feature = "console",
205 feature = "setimmediate"
206 ))]
207 {
208 let res = crate::features::init(&ret);
209 if res.is_err() {
210 panic!("could not init features: {}", res.err().unwrap());
211 }
212 }
213
214 if let Some(interval) = builder.opt_gc_interval {
215 let rti_ref: Weak<QuickjsRuntimeFacadeInner> = Arc::downgrade(&ret.inner);
216 std::thread::spawn(move || loop {
217 std::thread::sleep(interval);
218 if let Some(el) = rti_ref.upgrade() {
219 log::debug!("running gc from gc interval thread");
220 el.event_loop.add_void(|| {
221 QJS_RT
222 .try_with(|rc| {
223 let rt = &*rc.borrow();
224 rt.as_ref().unwrap().gc();
225 })
226 .expect("QJS_RT.try_with failed");
227 });
228 } else {
229 break;
230 }
231 });
232 }
233
234 let init_hooks: Vec<_> = builder.runtime_init_hooks.drain(..).collect();
235
236 ret.exe_task_in_event_loop(move || {
237 QuickJsRuntimeAdapter::do_with_mut(|q_js_rt| {
238 for native_module_loader in builder.native_module_loaders {
239 q_js_rt.add_native_module_loader(NativeModuleLoaderAdapter::new(
240 native_module_loader,
241 ));
242 }
243 for script_module_loader in builder.script_module_loaders {
244 q_js_rt.add_script_module_loader(ScriptModuleLoaderAdapter::new(
245 script_module_loader,
246 ));
247 }
248 for compiled_module_loader in builder.compiled_module_loaders {
249 q_js_rt.add_compiled_module_loader(CompiledModuleLoaderAdapter::new(
250 compiled_module_loader,
251 ));
252 }
253 q_js_rt.script_pre_processors = builder.script_pre_processors;
254
255 if let Some(limit) = builder.opt_memory_limit_bytes {
256 unsafe {
257 q::JS_SetMemoryLimit(q_js_rt.runtime, limit as _);
258 }
259 }
260 if let Some(threshold) = builder.opt_gc_threshold {
261 unsafe {
262 q::JS_SetGCThreshold(q_js_rt.runtime, threshold as _);
263 }
264 }
265 if let Some(stack_size) = builder.opt_max_stack_size {
266 unsafe {
267 q::JS_SetMaxStackSize(q_js_rt.runtime, stack_size as _);
268 }
269 }
270 if let Some(interrupt_handler) = builder.interrupt_handler {
271 q_js_rt.set_interrupt_handler(interrupt_handler);
272 }
273 })
274 });
275
276 for hook in init_hooks {
277 match hook(&ret) {
278 Ok(_) => {}
279 Err(e) => {
280 panic!("runtime_init_hook failed: {}", e);
281 }
282 }
283 }
284
285 ret
286 }
287
288 pub async fn memory_usage(&self) -> MemoryUsage {
290 self.loop_async(|rt| rt.memory_usage()).await
291 }
292
293 pub(crate) fn clear_contexts(&self) {
294 log::trace!("EsRuntime::clear_contexts");
295 self.exe_task_in_event_loop(|| {
296 let context_ids = QuickJsRuntimeAdapter::get_context_ids();
297 for id in context_ids {
298 let _ = QuickJsRuntimeAdapter::remove_context(id.as_str());
299 }
300 });
301 }
302
303 pub fn add_task_to_event_loop_void<C>(&self, task: C)
306 where
307 C: FnOnce() + Send + 'static,
308 {
309 self.inner.add_task_to_event_loop_void(task)
310 }
311
312 pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
313 where
314 C: FnOnce() -> R + Send + 'static,
315 {
316 self.inner.exe_task_in_event_loop(task)
317 }
318
319 pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
320 where
321 C: FnOnce() -> R + Send + 'static,
322 {
323 self.inner.add_task_to_event_loop(task)
324 }
325
326 pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
338 &self,
339 task: C,
340 ) -> impl Future<Output = R>
341 where
342 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
343 {
344 self.inner.add_rt_task_to_event_loop(task)
345 }
346
347 pub fn add_rt_task_to_event_loop_void<C>(&self, task: C)
348 where
349 C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
350 {
351 self.inner.add_rt_task_to_event_loop_void(task)
352 }
353
354 #[allow(dead_code)]
356 pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
357 where
358 C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
359 {
360 QuickjsRuntimeFacadeInner::add_local_task_to_event_loop(consumer)
361 }
362
363 pub fn builder() -> QuickJsRuntimeBuilder {
364 QuickJsRuntimeBuilder::new()
365 }
366
367 pub async fn gc(&self) {
369 self.add_rt_task_to_event_loop(|q_js_rt| q_js_rt.gc()).await
370 }
371
372 pub fn gc_sync(&self) {
374 self.exe_rt_task_in_event_loop(|q_js_rt| q_js_rt.gc())
375 }
376
377 pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
394 where
395 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
396 R: Send + 'static,
397 {
398 self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
399 }
400
401 pub fn set_function<F>(
422 &self,
423 namespace: &[&str],
424 name: &str,
425 function: F,
426 ) -> Result<(), JsError>
427 where
428 F: Fn(&QuickJsRealmAdapter, Vec<JsValueFacade>) -> Result<JsValueFacade, JsError>
429 + Send
430 + 'static,
431 {
432 let name = name.to_string();
433
434 let namespace = namespace
435 .iter()
436 .map(|s| s.to_string())
437 .collect::<Vec<String>>();
438
439 self.exe_rt_task_in_event_loop(move |q_js_rt| {
440 let func_rc = Rc::new(function);
441 let name = name.to_string();
442
443 q_js_rt.add_context_init_hook(move |_q_js_rt, realm| {
444 let namespace_slice = namespace.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
445 let ns = objects::get_namespace_q(realm, &namespace_slice, true)?;
446
447 let func_rc = func_rc.clone();
448
449 let func = functions::new_function_q(
450 realm,
451 name.as_str(),
452 move |realm, _this_ref, args| {
453 let mut args_facades = vec![];
454
455 for arg_ref in args {
456 args_facades.push(realm.to_js_value_facade(arg_ref)?);
457 }
458
459 let res = func_rc(realm, args_facades);
460
461 match res {
462 Ok(val_jsvf) => realm.from_js_value_facade(val_jsvf),
463 Err(e) => Err(e),
464 }
465 },
466 1,
467 )?;
468
469 objects::set_property2_q(realm, &ns, name.as_str(), &func, 0)?;
470
471 Ok(())
472 })
473 })
474 }
475
476 pub fn add_helper_task<T>(task: T)
478 where
479 T: FnOnce() + Send + 'static,
480 {
481 log::trace!("adding a helper task");
482 HELPER_TASKS.add_task(task);
483 }
484
485 pub fn add_helper_task_async<R: Send + 'static, T: Future<Output = R> + Send + 'static>(
487 task: T,
488 ) -> impl Future<Output = Result<R, JoinError>> {
489 log::trace!("adding an async helper task");
490 HELPER_TASKS.add_task_async(task)
491 }
492
493 pub fn create_context(&self, id: &str) -> Result<(), JsError> {
506 let id = id.to_string();
507 self.inner
508 .event_loop
509 .exe(move || QuickJsRuntimeAdapter::create_context(id.as_str()))
510 }
511
512 pub fn drop_context(&self, id: &str) -> anyhow::Result<()> {
514 let id = id.to_string();
515 self.inner
516 .event_loop
517 .exe(move || QuickJsRuntimeAdapter::remove_context(id.as_str()))
518 }
519}
520
521thread_local! {
522 static REALM_ID_LRU_CACHE: RefCell<LruCache<String, ()>> = RefCell::new(LruCache::new(NonZeroUsize::new(128).unwrap()));
525}
526
527fn loop_realm_func<
528 R: Send + 'static,
529 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
530>(
531 realm_name: Option<String>,
532 consumer: C,
533) -> R {
534 if let Some(realm_str) = realm_name.as_ref() {
539 REALM_ID_LRU_CACHE.with(|cache_cell| {
540 let mut cache = cache_cell.borrow_mut();
541 cache.promote(realm_str);
543 });
544 }
545
546 let res: Either<R, C> = QuickJsRuntimeAdapter::do_with(|q_js_rt| {
549 if let Some(realm_str) = realm_name.as_ref() {
550 if let Some(realm) = q_js_rt.get_realm(realm_str) {
551 Left(consumer(q_js_rt, realm))
552 } else {
553 Right(consumer)
554 }
555 } else {
556 Left(consumer(q_js_rt, q_js_rt.get_main_realm()))
557 }
558 });
559
560 match res {
561 Left(r) => r,
562 Right(consumer) => {
563 let realm_str = realm_name.expect("invalid state");
567
568 REALM_ID_LRU_CACHE.with(|cache_cell| {
569 let mut cache = cache_cell.borrow_mut();
570 if cache.len() == cache.cap().get() {
572 if let Some((_evicted_key, _evicted_value)) = cache.pop_lru() {
573 }
577 }
578 cache.put(realm_str.to_string(), ());
579 });
580
581 QuickJsRuntimeAdapter::do_with_mut(|m_rt| {
584 let ctx = QuickJsRealmAdapter::new(realm_str.to_string(), m_rt);
585 m_rt.contexts.insert(realm_str.to_string(), ctx);
586 });
587
588 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
589 let realm = q_js_rt
590 .get_realm(realm_str.as_str())
591 .expect("invalid state");
592 let hooks = &*q_js_rt.context_init_hooks.borrow();
593 for hook in hooks {
594 let res = hook(q_js_rt, realm);
595 if res.is_err() {
596 panic!("realm init hook failed: {}", res.err().unwrap());
597 }
598 }
599
600 consumer(q_js_rt, realm)
601 })
602 }
603 }
604}
605
606impl QuickJsRuntimeFacade {
607 pub fn create_realm(&self, name: &str) -> Result<(), JsError> {
608 let name = name.to_string();
609 self.inner
610 .event_loop
611 .exe(move || QuickJsRuntimeAdapter::create_context(name.as_str()))
612 }
613
614 pub fn destroy_realm(&self, name: &str) -> anyhow::Result<()> {
615 let name = name.to_string();
616 self.exe_task_in_event_loop(move || QuickJsRuntimeAdapter::remove_context(name.as_str()))
617 }
618
619 pub fn has_realm(&self, name: &str) -> Result<bool, JsError> {
620 let name = name.to_string();
621 self.exe_rt_task_in_event_loop(move |rt| Ok(rt.get_realm(name.as_str()).is_some()))
622 }
623
624 pub fn loop_sync<R: Send + 'static, C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static>(
626 &self,
627 consumer: C,
628 ) -> R {
629 self.exe_rt_task_in_event_loop(consumer)
630 }
631
632 pub fn loop_sync_mut<
633 R: Send + 'static,
634 C: FnOnce(&mut QuickJsRuntimeAdapter) -> R + Send + 'static,
635 >(
636 &self,
637 consumer: C,
638 ) -> R {
639 self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with_mut(consumer))
640 }
641
642 pub fn loop_async<
645 R: Send + 'static,
646 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
647 >(
648 &self,
649 consumer: C,
650 ) -> Pin<Box<dyn Future<Output = R> + Send>> {
651 Box::pin(self.add_rt_task_to_event_loop(consumer))
652 }
653
654 pub fn loop_void<C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static>(&self, consumer: C) {
656 self.add_rt_task_to_event_loop_void(consumer)
657 }
658
659 pub fn loop_realm_sync<
661 R: Send + 'static,
662 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
663 >(
664 &self,
665 realm_name: Option<&str>,
666 consumer: C,
667 ) -> R {
668 let realm_name = realm_name.map(|s| s.to_string());
669 self.exe_task_in_event_loop(|| loop_realm_func(realm_name, consumer))
670 }
671
672 pub fn loop_realm<
675 R: Send + 'static,
676 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
677 >(
678 &self,
679 realm_name: Option<&str>,
680 consumer: C,
681 ) -> Pin<Box<dyn Future<Output = R>>> {
682 let realm_name = realm_name.map(|s| s.to_string());
683 Box::pin(self.add_task_to_event_loop(|| loop_realm_func(realm_name, consumer)))
684 }
685
686 pub fn loop_realm_void<
689 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) + Send + 'static,
690 >(
691 &self,
692 realm_name: Option<&str>,
693 consumer: C,
694 ) {
695 let realm_name = realm_name.map(|s| s.to_string());
696 self.add_task_to_event_loop_void(|| loop_realm_func(realm_name, consumer));
697 }
698
699 #[allow(clippy::type_complexity)]
712 pub fn eval(
713 &self,
714 realm_name: Option<&str>,
715 script: Script,
716 ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
717 self.loop_realm(realm_name, |_rt, realm| {
718 let res = realm.eval(script);
719 match res {
720 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
721 Err(e) => Err(e),
722 }
723 })
724 }
725
726 #[allow(clippy::type_complexity)]
737 pub fn eval_sync(
738 &self,
739 realm_name: Option<&str>,
740 script: Script,
741 ) -> Result<JsValueFacade, JsError> {
742 self.loop_realm_sync(realm_name, |_rt, realm| {
743 let res = realm.eval(script);
744 match res {
745 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
746 Err(e) => Err(e),
747 }
748 })
749 }
750
751 pub fn eval_module(
786 &self,
787 realm_name: Option<&str>,
788 script: Script,
789 ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
790 self.loop_realm(realm_name, |_rt, realm| {
791 let res = realm.eval_module(script)?;
792 realm.to_js_value_facade(&res)
793 })
794 }
795
796 pub fn eval_module_sync(
829 &self,
830 realm_name: Option<&str>,
831 script: Script,
832 ) -> Result<JsValueFacade, JsError> {
833 self.loop_realm_sync(realm_name, |_rt, realm| {
834 let res = realm.eval_module(script)?;
835 realm.to_js_value_facade(&res)
836 })
837 }
838
839 #[warn(clippy::type_complexity)]
852 pub fn invoke_function_sync(
853 &self,
854 realm_name: Option<&str>,
855 namespace: &[&str],
856 method_name: &str,
857 args: Vec<JsValueFacade>,
858 ) -> Result<JsValueFacade, JsError> {
859 let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
860 let movable_method_name = method_name.to_string();
861
862 self.loop_realm_sync(realm_name, move |_rt, realm| {
863 let args_adapters: Vec<QuickJsValueAdapter> = args
864 .into_iter()
865 .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
866 .collect();
867
868 let namespace = movable_namespace
869 .iter()
870 .map(|s| s.as_str())
871 .collect::<Vec<&str>>();
872
873 let res = realm.invoke_function_by_name(
874 namespace.as_slice(),
875 movable_method_name.as_str(),
876 args_adapters.as_slice(),
877 );
878
879 match res {
880 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
881 Err(e) => Err(e),
882 }
883 })
884 }
885
886 #[allow(clippy::type_complexity)]
899 pub fn invoke_function(
900 &self,
901 realm_name: Option<&str>,
902 namespace: &[&str],
903 method_name: &str,
904 args: Vec<JsValueFacade>,
905 ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>>>> {
906 let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
907 let movable_method_name = method_name.to_string();
908
909 self.loop_realm(realm_name, move |_rt, realm| {
910 let args_adapters: Vec<QuickJsValueAdapter> = args
911 .into_iter()
912 .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
913 .collect();
914
915 let namespace = movable_namespace
916 .iter()
917 .map(|s| s.as_str())
918 .collect::<Vec<&str>>();
919
920 let res = realm.invoke_function_by_name(
921 namespace.as_slice(),
922 movable_method_name.as_str(),
923 args_adapters.as_slice(),
924 );
925
926 match res {
927 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
928 Err(e) => Err(e),
929 }
930 })
931 }
932
933 pub fn invoke_function_void(
934 &self,
935 realm_name: Option<&str>,
936 namespace: &[&str],
937 method_name: &str,
938 args: Vec<JsValueFacade>,
939 ) {
940 let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
941 let movable_method_name = method_name.to_string();
942
943 self.loop_realm_void(realm_name, move |_rt, realm| {
944 let args_adapters: Vec<QuickJsValueAdapter> = args
945 .into_iter()
946 .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
947 .collect();
948
949 let namespace = movable_namespace
950 .iter()
951 .map(|s| s.as_str())
952 .collect::<Vec<&str>>();
953
954 let res = realm
955 .invoke_function_by_name(
956 namespace.as_slice(),
957 movable_method_name.as_str(),
958 args_adapters.as_slice(),
959 )
960 .map(|jsvr| realm.to_js_value_facade(&jsvr));
961
962 match res {
963 Ok(_) => {
964 log::trace!(
965 "js_function_invoke_void succeeded: {}",
966 movable_method_name.as_str()
967 );
968 }
969 Err(err) => {
970 log::trace!(
971 "js_function_invoke_void failed: {}: {}",
972 movable_method_name.as_str(),
973 err
974 );
975 }
976 }
977 })
978 }
979}
980
981#[cfg(test)]
982lazy_static! {
983 static ref INITTED: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
984}
985
986#[cfg(test)]
987pub mod tests {
988 use crate::facades::QuickJsRuntimeFacade;
989 use crate::jsutils::modules::{NativeModuleLoader, ScriptModuleLoader};
990 use crate::jsutils::JsError;
991 use crate::jsutils::Script;
992 use crate::quickjs_utils::{primitives, promises};
993 use crate::quickjsrealmadapter::QuickJsRealmAdapter;
994 use crate::quickjsvalueadapter::QuickJsValueAdapter;
995 use crate::values::{JsValueConvertable, JsValueFacade};
996 use backtrace::Backtrace;
997 use futures::executor::block_on;
998 use log::debug;
999 use std::panic;
1000 use std::time::Duration;
1001
1002 struct TestNativeModuleLoader {}
1003 struct TestScriptModuleLoader {}
1004
1005 impl NativeModuleLoader for TestNativeModuleLoader {
1006 fn has_module(&self, _q_ctx: &QuickJsRealmAdapter, module_name: &str) -> bool {
1007 module_name.starts_with("greco://")
1008 }
1009
1010 fn get_module_export_names(
1011 &self,
1012 _q_ctx: &QuickJsRealmAdapter,
1013 _module_name: &str,
1014 ) -> Vec<&str> {
1015 vec!["a", "b", "c"]
1016 }
1017
1018 fn get_module_exports(
1019 &self,
1020 _q_ctx: &QuickJsRealmAdapter,
1021 _module_name: &str,
1022 ) -> Vec<(&str, QuickJsValueAdapter)> {
1023 vec![
1024 ("a", primitives::from_i32(1234)),
1025 ("b", primitives::from_i32(64834)),
1026 ("c", primitives::from_i32(333)),
1027 ]
1028 }
1029 }
1030
1031 impl ScriptModuleLoader for TestScriptModuleLoader {
1032 fn normalize_path(
1033 &self,
1034 _realm: &QuickJsRealmAdapter,
1035 _ref_path: &str,
1036 path: &str,
1037 ) -> Option<String> {
1038 if path.eq("notfound.mes") || path.starts_with("greco://") {
1039 None
1040 } else {
1041 Some(path.to_string())
1042 }
1043 }
1044
1045 fn load_module(&self, _realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
1046 if absolute_path.eq("notfound.mes") || absolute_path.starts_with("greco://") {
1047 panic!("tht realy should not happen");
1048 } else if absolute_path.eq("invalid.mes") {
1049 "I am the great cornholio! thou'gh shalt&s not p4arse mie!".to_string()
1050 } else {
1051 "export const foo = 'bar';\nexport const mltpl = function(a, b){return a*b;}; globalThis;".to_string()
1052 }
1053 }
1054 }
1055
1056 #[test]
1057 fn test_rt_drop() {
1058 let rt = init_test_rt();
1059 log::trace!("before drop");
1060
1061 drop(rt);
1062 log::trace!("after before drop");
1063 std::thread::sleep(Duration::from_secs(5));
1064 log::trace!("after sleep");
1065 }
1066
1067 #[test]
1068 pub fn test_stack_size() {
1069 let rt = init_test_rt();
1070 let res = rt.eval_sync(
1072 None,
1073 Script::new(
1074 "stack_test.js",
1075 "let f = function(a){let f2 = arguments.callee; if (a < 20) {f2(a + 1);}}; f(1);",
1076 ),
1077 );
1078 match res {
1079 Ok(_) => {}
1080 Err(e) => {
1081 log::error!("fail: {}", e);
1082 panic!("fail: {}", e);
1083 }
1084 }
1085
1086 let res = rt.eval_sync(
1087 None,
1088 Script::new(
1089 "stack_test.js",
1090 "let f = function(a){let f2 = arguments.callee; if (a < 1000) {f2(a + 1);}}; f(1);",
1091 ),
1092 );
1093 if res.is_ok() {
1094 panic!("stack should have overflowed");
1095 }
1096 }
1097
1098 pub fn init_logging() {
1099 {
1100 let i_lock = &mut *crate::facades::INITTED.lock().unwrap();
1101 if !*i_lock {
1102 panic::set_hook(Box::new(|panic_info| {
1103 let backtrace = Backtrace::new();
1104 println!("thread panic occurred: {panic_info}\nbacktrace: {backtrace:?}");
1105 log::error!(
1106 "thread panic occurred: {}\nbacktrace: {:?}",
1107 panic_info,
1108 backtrace
1109 );
1110 }));
1111
1112 simple_logging::log_to_file("./quickjs_runtime.log", log::LevelFilter::max())
1113 .expect("could not init logger");
1114
1115 *i_lock = true;
1116 }
1117 }
1118 }
1119
1120 pub fn init_test_rt() -> QuickJsRuntimeFacade {
1121 init_logging();
1122
1123 QuickJsRuntimeFacade::builder()
1124 .gc_interval(Duration::from_secs(1))
1125 .max_stack_size(128 * 1024)
1126 .script_module_loader(TestScriptModuleLoader {})
1127 .native_module_loader(TestNativeModuleLoader {})
1128 .build()
1129 }
1130
1131 #[test]
1132 fn test_func() {
1133 let rt = init_test_rt();
1134 let res = rt.set_function(&["nl", "my", "utils"], "methodA", |_q_ctx, args| {
1135 if args.len() != 2 || !args.first().unwrap().is_i32() || !args.get(1).unwrap().is_i32()
1136 {
1137 Err(JsError::new_str(
1138 "i'd really like 2 args of the int32 kind please",
1139 ))
1140 } else {
1141 let a = args.first().unwrap().get_i32();
1142 let b = args.get(1).unwrap().get_i32();
1143 Ok((a * b).to_js_value_facade())
1144 }
1145 });
1146
1147 match res {
1148 Ok(_) => {}
1149 Err(e) => {
1150 panic!("set_function failed: {}", e);
1151 }
1152 }
1153
1154 let res = rt.eval_sync(
1155 None,
1156 Script::new("test_func.es", "(nl.my.utils.methodA(13, 56));"),
1157 );
1158
1159 match res {
1160 Ok(val) => {
1161 assert!(val.is_i32());
1162 assert_eq!(val.get_i32(), 13 * 56);
1163 }
1164 Err(e) => {
1165 panic!("test_func.es failed: {}", e);
1166 }
1167 }
1168 }
1169
1170 #[test]
1171 fn test_eval_sync() {
1172 let rt = init_test_rt();
1173 let res = rt.eval_sync(None, Script::new("test.es", "console.log('foo bar');"));
1174
1175 match res {
1176 Ok(_) => {}
1177 Err(e) => {
1178 panic!("eval failed: {}", e);
1179 }
1180 }
1181
1182 let res = rt
1183 .eval_sync(None, Script::new("test.es", "(2 * 7);"))
1184 .expect("script failed");
1185
1186 assert_eq!(res.get_i32(), 14);
1187 }
1188
1189 #[test]
1190 fn t1234() {
1191 let rt = init_test_rt();
1193
1194 rt.exe_rt_task_in_event_loop(|q_js_rt| {
1195 let q_ctx = q_js_rt.get_main_realm();
1197 let r = q_ctx.eval(Script::new(
1198 "test_async.es",
1199 "let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f();",
1200 )).ok().unwrap();
1201 log::trace!("tag = {}", r.get_tag());
1202 assert!(promises::is_promise_q(q_ctx, &r));
1205
1206 if promises::is_promise_q(q_ctx, &r) {
1207 log::info!("r IS a Promise");
1208 } else {
1209 log::error!("r is NOT a Promise");
1210 }
1211
1212 std::thread::sleep(Duration::from_secs(1));
1213
1214 });
1216 rt.exe_rt_task_in_event_loop(|q_js_rt| {
1217 q_js_rt.run_pending_jobs_if_any();
1218 });
1219
1220 std::thread::sleep(Duration::from_secs(1));
1221 }
1222
1223 #[test]
1224 fn test_eval_await() {
1225 let rt = init_test_rt();
1226
1227 let res = rt.eval_sync(None, Script::new(
1228 "test_async.es",
1229 "{let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f()};",
1230 ));
1231
1232 match res {
1233 Ok(esvf) => {
1234 assert!(esvf.is_js_promise());
1235 match esvf {
1236 JsValueFacade::JsPromise { cached_promise } => {
1237 let p_res = cached_promise
1238 .get_promise_result_sync()
1239 .expect("promise timed out");
1240 if p_res.is_err() {
1241 panic!("{:?}", p_res.err().unwrap());
1242 }
1243 let res = p_res.ok().unwrap();
1244 assert!(res.is_i32());
1245 assert_eq!(res.get_i32(), 12345);
1246 }
1247 _ => {}
1248 }
1249 }
1250 Err(e) => {
1251 panic!("eval failed: {}", e);
1252 }
1253 }
1254 }
1255
1256 #[test]
1257 fn test_promise() {
1258 let rt = init_test_rt();
1259
1260 let res = rt.eval_sync(None, Script::new(
1261 "testp2.es",
1262 "let test_promise_P = (new Promise(function(res, rej) {console.log('before res');res(123);console.log('after res');}).then(function (a) {console.log('prom ressed to ' + a);}).catch(function(x) {console.log('p.ca ex=' + x);}))",
1263 ));
1264
1265 match res {
1266 Ok(_) => {}
1267 Err(e) => panic!("p script failed: {}", e),
1268 }
1269 std::thread::sleep(Duration::from_secs(1));
1270 }
1271
1272 #[test]
1273 fn test_module_sync() {
1274 log::info!("> test_module_sync");
1275
1276 let rt = init_test_rt();
1277 debug!("test static import");
1278 let res: Result<JsValueFacade, JsError> = rt.eval_module_sync(
1279 None,
1280 Script::new(
1281 "test.es",
1282 "import {foo} from 'test_module.mes';\n console.log('static imp foo = ' + foo);",
1283 ),
1284 );
1285
1286 match res {
1287 Ok(_) => {
1288 log::debug!("static import ok");
1289 }
1290 Err(e) => {
1291 log::error!("static import failed: {}", e);
1292 }
1293 }
1294
1295 debug!("test dynamic import");
1296 let res: Result<JsValueFacade, JsError> = rt.eval_sync(None, Script::new(
1297 "test_dyn.es",
1298 "console.log('about to load dynamic module');let dyn_p = import('test_module.mes');dyn_p.then(function (some) {console.log('after dyn');console.log('after dyn ' + typeof some);console.log('mltpl 5, 7 = ' + some.mltpl(5, 7));});dyn_p.catch(function (x) {console.log('imp.cat x=' + x);});console.log('dyn done');",
1299 ));
1300
1301 match res {
1302 Ok(_) => {
1303 log::debug!("dynamic import ok");
1304 }
1305 Err(e) => {
1306 log::error!("dynamic import failed: {}", e);
1307 }
1308 }
1309 std::thread::sleep(Duration::from_secs(1));
1310
1311 log::info!("< test_module_sync");
1312 }
1313
1314 async fn test_async1() -> i32 {
1315 let rt = init_test_rt();
1316
1317 let a = rt
1318 .eval(None, Script::new("test_async.es", "122 + 1;"))
1319 .await;
1320 match a {
1321 Ok(a) => a.get_i32(),
1322 Err(e) => panic!("script failed: {}", e),
1323 }
1324 }
1325
1326 #[test]
1327 fn test_async() {
1328 let fut = test_async1();
1329 let res = block_on(fut);
1330 assert_eq!(res, 123);
1331 }
1332}
1333
1334#[cfg(test)]
1335pub mod abstraction_tests {
1336 use crate::builder::QuickJsRuntimeBuilder;
1337 use crate::facades::tests::init_test_rt;
1338 use crate::facades::QuickJsRuntimeFacade;
1339 use crate::jsutils::Script;
1340 use crate::values::JsValueFacade;
1341 use futures::executor::block_on;
1342 use serde::Deserialize;
1343 use serde::Serialize;
1344
1345 async fn example(rt: &QuickJsRuntimeFacade) -> JsValueFacade {
1346 rt.loop_realm(None, |_rt_adapter, realm_adapter| {
1348 let script = Script::new("example.js", "7 + 13");
1349 let value_adapter = realm_adapter.eval(script).expect("script failed");
1350 realm_adapter
1352 .to_js_value_facade(&value_adapter)
1353 .expect("conversion failed")
1354 })
1355 .await
1356 }
1357
1358 #[test]
1359 fn test1() {
1360 let rt = QuickJsRuntimeBuilder::new().build();
1362 let val = block_on(example(&rt));
1363 if let JsValueFacade::I32 { val } = val {
1364 assert_eq!(val, 20);
1365 } else {
1366 panic!("not an i32");
1367 }
1368 }
1369
1370 #[tokio::test]
1371 async fn test_serde() {
1372 let json = r#"
1373 {
1374 "a": 1,
1375 "b": true,
1376 "c": {
1377 "d": "q",
1378 "e": [1, 2, 3.3]
1379 }
1380 }
1381 "#;
1382
1383 let value = serde_json::from_str::<serde_json::Value>(json).expect("json fail");
1384 let input: JsValueFacade = JsValueFacade::SerdeValue { value };
1385 let rt = init_test_rt();
1386
1387 let _ = rt.eval(None, Script::new("t.js", r#"
1388 function testSerde(input) {
1389 return "" + input.a + input.b + input.c.d + input.c.e[0] + input.c.e[1] + input.c.e[2];
1390 }
1391 "#)).await.expect("script failed");
1392
1393 let res = rt
1394 .invoke_function(None, &[], "testSerde", vec![input])
1395 .await
1396 .expect("func failed");
1397
1398 assert!(res.is_string());
1399 assert_eq!(res.get_str(), "1trueq123.3");
1400 }
1401
1402 #[derive(Serialize, Deserialize)]
1403 #[serde(rename_all = "camelCase")]
1404 struct User {
1405 name: String,
1406 last_name: String,
1407 }
1408
1409 #[tokio::test]
1410 async fn serde_tests_serialize() {
1411 let rtb: QuickJsRuntimeBuilder = QuickJsRuntimeBuilder::new();
1412 let rt = rtb.build();
1413
1414 rt.eval(
1416 None,
1417 Script::new(
1418 "test.js",
1419 r#"
1420 function myTest(user) {
1421 return {
1422 name: "proc_" + user.name,
1423 lastName: "proc_" + user.lastName
1424 }
1425 }
1426 "#,
1427 ),
1428 )
1429 .await
1430 .expect("script failed");
1431
1432 let test_user_input = User {
1434 last_name: "Anderson".to_string(),
1435 name: "Mister".to_string(),
1436 };
1437
1438 let args = vec![JsValueFacade::from_serializable(&test_user_input)
1439 .expect("could not serialize to JsValueFacade")];
1440
1441 let res: JsValueFacade = rt
1442 .invoke_function(None, &[], "myTest", args)
1443 .await
1444 .expect("func failed");
1445
1446 let json_result = res
1447 .to_json_string()
1448 .await
1449 .expect("could not serialize to json");
1450
1451 assert_eq!(
1452 json_result.as_str(),
1453 r#"{"name":"proc_Mister","lastName":"proc_Anderson"}"#
1454 );
1455
1456 let user_output: User = serde_json::from_str(json_result.as_str()).unwrap();
1458 assert_eq!(user_output.name.as_str(), "proc_Mister");
1459 assert_eq!(user_output.last_name.as_str(), "proc_Anderson");
1460 }
1461
1462 #[tokio::test]
1463 async fn serde_tests_value() {
1464 let rtb: QuickJsRuntimeBuilder = QuickJsRuntimeBuilder::new();
1465 let rt = rtb.build();
1466
1467 rt.eval(
1469 None,
1470 Script::new(
1471 "test.js",
1472 r#"
1473 function myTest(user) {
1474 return {
1475 name: "proc_" + user.name,
1476 lastName: "proc_" + user.lastName
1477 }
1478 }
1479 "#,
1480 ),
1481 )
1482 .await
1483 .expect("script failed");
1484
1485 let test_user_input = User {
1487 last_name: "Anderson".to_string(),
1488 name: "Mister".to_string(),
1489 };
1490
1491 let input_value: serde_json::Value =
1492 serde_json::to_value(test_user_input).expect("could not to_value");
1493 let args = vec![JsValueFacade::SerdeValue { value: input_value }];
1494
1495 let res: JsValueFacade = rt
1496 .invoke_function(None, &[], "myTest", args)
1497 .await
1498 .expect("func failed");
1499
1500 let value_result: serde_json::Value = res
1502 .to_serde_value()
1503 .await
1504 .expect("could not serialize to json");
1505
1506 assert!(value_result.is_object());
1507
1508 let user_output: User = serde_json::from_value(value_result).unwrap();
1510 assert_eq!(user_output.name.as_str(), "proc_Mister");
1511 assert_eq!(user_output.last_name.as_str(), "proc_Anderson");
1512 }
1513
1514 #[tokio::test]
1515 async fn test_realm_lifetime() -> anyhow::Result<()> {
1516 let rt = QuickJsRuntimeBuilder::new().build();
1517
1518 rt.add_rt_task_to_event_loop(|rt| {
1519 println!("ctx list: [{}]", rt.list_contexts().join(",").as_str());
1520 })
1521 .await;
1522
1523 for x in 0..10240 {
1524 let rid = format!("x_{x}");
1525 let _ = rt
1526 .eval(Some(rid.as_str()), Script::new("x.js", "const a = 1;"))
1527 .await;
1528 }
1529
1530 rt.add_rt_task_to_event_loop(|rt| {
1531 println!("ctx list: [{}]", rt.list_contexts().join(",").as_str());
1532 })
1533 .await;
1534
1535 for x in 0..8 {
1536 let rid = format!("x_{x}");
1537 let _ = rt
1538 .eval(Some(rid.as_str()), Script::new("x.js", "const a = 1;"))
1539 .await;
1540 }
1541
1542 rt.add_rt_task_to_event_loop(|rt| {
1543 println!("ctx list: [{}]", rt.list_contexts().join(",").as_str());
1544 })
1545 .await;
1546
1547 Ok(())
1548 }
1549}