quickjs_runtime/
quickjsruntimeadapter.rs

1// store in thread_local
2
3use crate::facades::QuickjsRuntimeFacadeInner;
4use crate::jsutils::modules::{CompiledModuleLoader, NativeModuleLoader, ScriptModuleLoader};
5use crate::jsutils::{JsError, Script, ScriptPreProcessor};
6use crate::quickjs_utils::compile::from_bytecode;
7use crate::quickjs_utils::modules::{
8    add_module_export, compile_module, get_module_def, get_module_name, new_module,
9    set_module_export,
10};
11use crate::quickjs_utils::runtime::new_class_id;
12use crate::quickjs_utils::{gc, interrupthandler, modules, promises};
13use crate::quickjsrealmadapter::QuickJsRealmAdapter;
14use libquickjs_sys as q;
15use serde::Serialize;
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::ffi::CString;
19use std::fmt::{Debug, Formatter};
20use std::os::raw::c_int;
21use std::panic;
22use std::sync::{Arc, Weak};
23
24/// this is the internal abstract loader which is used to actually load the modules
25pub trait ModuleLoader {
26    /// the normalize methods is used to translate a possible relative path to an absolute path of a module
27    /// it doubles as a method to see IF a module can actually be loaded by a module loader (return None if the module can not be found)
28    fn normalize_path(
29        &self,
30        q_ctx: &QuickJsRealmAdapter,
31        ref_path: &str,
32        path: &str,
33    ) -> Option<String>;
34    /// load the Module
35    fn load_module(
36        &self,
37        q_ctx: &QuickJsRealmAdapter,
38        absolute_path: &str,
39    ) -> Result<*mut q::JSModuleDef, JsError>;
40    /// has module is used to check if a loader can provide a certain module, this is currently used to check which loader should init a native module
41    fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool;
42    /// init a module, currently used to init native modules
43    /// # Safety
44    /// be safe with the moduledef ptr
45    unsafe fn init_module(
46        &self,
47        q_ctx: &QuickJsRealmAdapter,
48        module: *mut q::JSModuleDef,
49    ) -> Result<(), JsError>;
50}
51
52// these are the external (util) loaders (todo move these to esruntime?)
53
54pub struct CompiledModuleLoaderAdapter {
55    inner: Box<dyn CompiledModuleLoader>,
56}
57
58impl CompiledModuleLoaderAdapter {
59    pub fn new(loader: Box<dyn CompiledModuleLoader>) -> Self {
60        Self { inner: loader }
61    }
62}
63
64pub struct ScriptModuleLoaderAdapter {
65    inner: Box<dyn ScriptModuleLoader>,
66}
67
68impl ScriptModuleLoaderAdapter {
69    pub fn new(loader: Box<dyn ScriptModuleLoader>) -> Self {
70        Self { inner: loader }
71    }
72}
73
74impl ModuleLoader for CompiledModuleLoaderAdapter {
75    fn normalize_path(
76        &self,
77        q_ctx: &QuickJsRealmAdapter,
78        ref_path: &str,
79        path: &str,
80    ) -> Option<String> {
81        self.inner.normalize_path(q_ctx, ref_path, path)
82    }
83
84    fn load_module(
85        &self,
86        q_ctx: &QuickJsRealmAdapter,
87        absolute_path: &str,
88    ) -> Result<*mut q::JSModuleDef, JsError> {
89        let bytes = self.inner.load_module(q_ctx, absolute_path);
90
91        let compiled_module = unsafe { from_bytecode(q_ctx.context, &bytes)? };
92        Ok(get_module_def(&compiled_module))
93    }
94
95    fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool {
96        self.normalize_path(q_ctx, absolute_path, absolute_path)
97            .is_some()
98    }
99
100    unsafe fn init_module(
101        &self,
102        _q_ctx: &QuickJsRealmAdapter,
103        _module: *mut q::JSModuleDef,
104    ) -> Result<(), JsError> {
105        Ok(())
106    }
107}
108
109impl ModuleLoader for ScriptModuleLoaderAdapter {
110    fn normalize_path(
111        &self,
112        realm: &QuickJsRealmAdapter,
113        ref_path: &str,
114        path: &str,
115    ) -> Option<String> {
116        self.inner.normalize_path(realm, ref_path, path)
117    }
118
119    fn load_module(
120        &self,
121        realm: &QuickJsRealmAdapter,
122        absolute_path: &str,
123    ) -> Result<*mut q::JSModuleDef, JsError> {
124        log::trace!("load_module");
125        let code = self.inner.load_module(realm, absolute_path);
126
127        let mut script = Script::new(absolute_path, code.as_str());
128        script = QuickJsRuntimeAdapter::pre_process(script)?;
129        log::trace!("load_module / 2");
130        let compiled_module = unsafe { compile_module(realm.context, script)? };
131        log::trace!("load_module / 3");
132        Ok(get_module_def(&compiled_module))
133    }
134
135    fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool {
136        self.normalize_path(q_ctx, absolute_path, absolute_path)
137            .is_some()
138    }
139
140    unsafe fn init_module(
141        &self,
142        _q_ctx: &QuickJsRealmAdapter,
143        _module: *mut q::JSModuleDef,
144    ) -> Result<(), JsError> {
145        Ok(())
146    }
147}
148
149pub struct NativeModuleLoaderAdapter {
150    inner: Box<dyn NativeModuleLoader>,
151}
152
153impl NativeModuleLoaderAdapter {
154    pub fn new(loader: Box<dyn NativeModuleLoader>) -> Self {
155        Self { inner: loader }
156    }
157}
158
159impl ModuleLoader for NativeModuleLoaderAdapter {
160    fn normalize_path(
161        &self,
162        q_ctx: &QuickJsRealmAdapter,
163        _ref_path: &str,
164        path: &str,
165    ) -> Option<String> {
166        if self.inner.has_module(q_ctx, path) {
167            Some(path.to_string())
168        } else {
169            None
170        }
171    }
172
173    fn load_module(
174        &self,
175        q_ctx: &QuickJsRealmAdapter,
176        absolute_path: &str,
177    ) -> Result<*mut q::JSModuleDef, JsError> {
178        // create module
179        let module = unsafe { new_module(q_ctx.context, absolute_path, Some(native_module_init))? };
180
181        for name in self.inner.get_module_export_names(q_ctx, absolute_path) {
182            unsafe { add_module_export(q_ctx.context, module, name)? }
183        }
184
185        //std::ptr::null_mut()
186        Ok(module)
187    }
188
189    fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool {
190        self.inner.has_module(q_ctx, absolute_path)
191    }
192
193    unsafe fn init_module(
194        &self,
195        q_ctx: &QuickJsRealmAdapter,
196        module: *mut q::JSModuleDef,
197    ) -> Result<(), JsError> {
198        let module_name = get_module_name(q_ctx.context, module)?;
199
200        for (name, val) in self.inner.get_module_exports(q_ctx, module_name.as_str()) {
201            set_module_export(q_ctx.context, module, name, val)?;
202        }
203        Ok(())
204    }
205}
206
207unsafe extern "C" fn native_module_init(
208    ctx: *mut q::JSContext,
209    module: *mut q::JSModuleDef,
210) -> c_int {
211    let module_name = get_module_name(ctx, module).expect("could not get name");
212    log::trace!("native_module_init: {}", module_name);
213
214    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
215        QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
216            q_js_rt
217                .with_all_module_loaders(|module_loader| {
218                    if module_loader.has_module(q_ctx, module_name.as_str()) {
219                        match module_loader.init_module(q_ctx, module) {
220                            Ok(_) => {
221                                Some(0) // ok
222                            }
223                            Err(e) => {
224                                q_ctx.report_ex(
225                                    format!(
226                                        "Failed to init native module: {module_name} caused by {e}"
227                                    )
228                                    .as_str(),
229                                );
230                                Some(1)
231                            }
232                        }
233                    } else {
234                        None
235                    }
236                })
237                .unwrap_or(0)
238        })
239    })
240}
241
242thread_local! {
243   /// the thread-local QuickJsRuntime
244   /// this only exists for the worker thread of the EsEventQueue
245   /// todo move rt init to toplevel stackframe (out of lazy init)
246   /// so the thread_local should be a refcel containing a null reF? or a None
247   pub(crate) static QJS_RT: RefCell<Option<QuickJsRuntimeAdapter >> = const { RefCell::new(None) };
248
249}
250
251pub type ContextInitHooks =
252    Vec<Box<dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<(), JsError>>>;
253
254pub struct QuickJsRuntimeAdapter {
255    pub(crate) runtime: *mut q::JSRuntime,
256    pub(crate) contexts: HashMap<String, QuickJsRealmAdapter>,
257    rti_ref: Option<Weak<QuickjsRuntimeFacadeInner>>,
258    id: String,
259    pub(crate) context_init_hooks: RefCell<ContextInitHooks>,
260    script_module_loaders: Vec<ScriptModuleLoaderAdapter>,
261    native_module_loaders: Vec<NativeModuleLoaderAdapter>,
262    compiled_module_loaders: Vec<CompiledModuleLoaderAdapter>,
263    // script preprocs just preproc the input code, typescript transpiler will be special option which is run as last preproc
264    pub(crate) script_pre_processors: Vec<Box<dyn ScriptPreProcessor + Send>>,
265    #[allow(clippy::type_complexity)]
266    pub(crate) interrupt_handler: Option<Box<dyn Fn(&QuickJsRuntimeAdapter) -> bool>>,
267}
268
269thread_local! {
270    static NESTED: RefCell<bool> = const {RefCell::new(false)};
271}
272
273#[derive(Serialize)]
274pub struct MemoryUsage {
275    pub realm_ct: usize,
276    pub malloc_size: i64,
277    pub malloc_limit: i64,
278    pub memory_used_size: i64,
279    pub malloc_count: i64,
280    pub memory_used_count: i64,
281    pub atom_count: i64,
282    pub atom_size: i64,
283    pub str_count: i64,
284    pub str_size: i64,
285    pub obj_count: i64,
286    pub obj_size: i64,
287    pub prop_count: i64,
288    pub prop_size: i64,
289    pub shape_count: i64,
290    pub shape_size: i64,
291    pub js_func_count: i64,
292    pub js_func_size: i64,
293    pub js_func_code_size: i64,
294    pub js_func_pc2line_count: i64,
295    pub js_func_pc2line_size: i64,
296    pub c_func_count: i64,
297    pub array_count: i64,
298    pub fast_array_count: i64,
299    pub fast_array_elements: i64,
300    pub binary_object_count: i64,
301    pub binary_object_size: i64,
302}
303
304impl Debug for MemoryUsage {
305    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
306        f.write_str("MemoryUsage:")?;
307        f.write_str(format!("\n realm_ct: {}", self.realm_ct).as_str())?;
308        f.write_str(format!("\n malloc_size: {}", self.malloc_size).as_str())?;
309        f.write_str(format!("\n malloc_limit: {}", self.malloc_limit).as_str())?;
310        f.write_str(format!("\n memory_used_size: {}", self.memory_used_size).as_str())?;
311        f.write_str(format!("\n malloc_count: {}", self.malloc_count).as_str())?;
312        f.write_str(format!("\n memory_used_count: {}", self.memory_used_count).as_str())?;
313        f.write_str(format!("\n atom_count: {}", self.atom_count).as_str())?;
314        f.write_str(format!("\n atom_size: {}", self.atom_size).as_str())?;
315        f.write_str(format!("\n str_count: {}", self.str_count).as_str())?;
316        f.write_str(format!("\n str_size: {}", self.str_size).as_str())?;
317        f.write_str(format!("\n obj_count: {}", self.obj_count).as_str())?;
318        f.write_str(format!("\n obj_size: {}", self.obj_size).as_str())?;
319        f.write_str(format!("\n prop_count: {}", self.prop_count).as_str())?;
320        f.write_str(format!("\n prop_size: {}", self.prop_size).as_str())?;
321        f.write_str(format!("\n shape_count: {}", self.shape_count).as_str())?;
322        f.write_str(format!("\n shape_size: {}", self.shape_size).as_str())?;
323        f.write_str(format!("\n js_func_count: {}", self.js_func_count).as_str())?;
324        f.write_str(format!("\n js_func_size: {}", self.js_func_size).as_str())?;
325        f.write_str(format!("\n js_func_code_size: {}", self.js_func_code_size).as_str())?;
326        f.write_str(format!("\n js_func_pc2line_count: {}", self.js_func_pc2line_count).as_str())?;
327        f.write_str(format!("\n js_func_pc2line_size: {}", self.js_func_pc2line_size).as_str())?;
328        f.write_str(format!("\n c_func_count: {}", self.c_func_count).as_str())?;
329        f.write_str(format!("\n array_count: {}", self.array_count).as_str())?;
330        f.write_str(format!("\n fast_array_count: {}", self.fast_array_count).as_str())?;
331        f.write_str(format!("\n fast_array_elements: {}", self.fast_array_elements).as_str())?;
332        f.write_str(format!("\n binary_object_count: {}", self.binary_object_count).as_str())?;
333        f.write_str(format!("\n binary_object_size: {}", self.binary_object_size).as_str())?;
334        Ok(())
335    }
336}
337
338impl QuickJsRuntimeAdapter {
339    pub(crate) fn init_rt_for_current_thread(rt: QuickJsRuntimeAdapter) {
340        QJS_RT.with(|rc| {
341            let opt = &mut *rc.borrow_mut();
342            opt.replace(rt);
343        })
344    }
345
346    pub fn new_class_id(&self) -> u32 {
347        unsafe { new_class_id(self.runtime) }
348    }
349
350    pub fn print_stats(&self) {
351        for ctx in &self.contexts {
352            println!("> ----- ctx: {}", ctx.0);
353            ctx.1.print_stats();
354            println!("< ----- ctx: {}", ctx.0);
355        }
356
357        //
358        crate::quickjs_utils::typedarrays::BUFFERS.with(|rc| {
359            let map = &*rc.borrow();
360            println!("typedarrays::BUFFERS.len() = {}", map.len());
361        });
362        crate::quickjs_utils::functions::CALLBACK_REGISTRY.with(|rc| {
363            let map = &*rc.borrow();
364            println!("functions::CALLBACK_REGISTRY.len() = {}", map.len());
365        });
366        crate::quickjs_utils::functions::CALLBACK_IDS.with(|rc| {
367            let map = &*rc.borrow();
368            println!("functions::CALLBACK_IDS.len() = {}", map.len());
369        });
370
371        let mu = self.memory_usage();
372        println!("MemoryUsage: {mu:?}");
373    }
374
375    /// get memory usage for this runtime
376    pub fn memory_usage(&self) -> MemoryUsage {
377        let mu: q::JSMemoryUsage = unsafe { crate::quickjs_utils::get_memory_usage(self.runtime) };
378
379        MemoryUsage {
380            realm_ct: self.contexts.len(),
381            malloc_size: mu.malloc_size,
382            malloc_limit: mu.malloc_limit,
383            memory_used_size: mu.memory_used_size,
384            malloc_count: mu.malloc_count,
385            memory_used_count: mu.memory_used_count,
386            atom_count: mu.atom_count,
387            atom_size: mu.atom_size,
388            str_count: mu.str_count,
389            str_size: mu.str_size,
390            obj_count: mu.obj_count,
391            obj_size: mu.obj_size,
392            prop_count: mu.prop_count,
393            prop_size: mu.prop_size,
394            shape_count: mu.shape_count,
395            shape_size: mu.shape_size,
396            js_func_count: mu.js_func_count,
397            js_func_size: mu.js_func_size,
398            js_func_code_size: mu.js_func_code_size,
399            js_func_pc2line_count: mu.js_func_pc2line_count,
400            js_func_pc2line_size: mu.js_func_pc2line_size,
401            c_func_count: mu.c_func_count,
402            array_count: mu.array_count,
403            fast_array_count: mu.fast_array_count,
404            fast_array_elements: mu.fast_array_elements,
405            binary_object_count: mu.binary_object_count,
406            binary_object_size: mu.binary_object_size,
407        }
408    }
409
410    pub(crate) fn pre_process(mut script: Script) -> Result<Script, JsError> {
411        Self::do_with(|q_js_rt| {
412            for pp in &q_js_rt.script_pre_processors {
413                pp.process(&mut script)?;
414            }
415            #[cfg(feature = "typescript")]
416            crate::typescript::transpile_serverside(q_js_rt, &mut script)?;
417
418            Ok(script)
419        })
420    }
421
422    pub fn add_context_init_hook<H>(&self, hook: H) -> Result<(), JsError>
423    where
424        H: Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<(), JsError> + 'static,
425    {
426        let i = {
427            let hooks = &mut *self.context_init_hooks.borrow_mut();
428            hooks.push(Box::new(hook));
429            hooks.len() - 1
430        };
431
432        let hooks = &*self.context_init_hooks.borrow();
433        let hook = hooks.get(i).expect("invalid state");
434        for ctx in self.contexts.values() {
435            if let Err(e) = hook(self, ctx) {
436                panic!("hook failed {}", e);
437            }
438        }
439
440        Ok(())
441    }
442
443    pub fn create_context(id: &str) -> Result<(), JsError> {
444        let ctx = Self::do_with(|q_js_rt| {
445            assert!(!q_js_rt.has_context(id));
446            QuickJsRealmAdapter::new(id.to_string(), q_js_rt)
447        });
448
449        QuickJsRuntimeAdapter::do_with_mut(|q_js_rt| {
450            q_js_rt.contexts.insert(id.to_string(), ctx);
451        });
452
453        Self::do_with(|q_js_rt| {
454            let ctx = q_js_rt.get_context(id);
455            let hooks = &*q_js_rt.context_init_hooks.borrow();
456            for hook in hooks {
457                hook(q_js_rt, ctx)?;
458            }
459            Ok(())
460        })
461    }
462    pub fn list_contexts(&self) -> Vec<&str> {
463        self.contexts.keys().map(|k| k.as_str()).collect()
464    }
465    pub fn remove_context(id: &str) -> anyhow::Result<()> {
466        log::debug!("QuickJsRuntime::drop_context: {}", id);
467
468        QuickJsRuntimeAdapter::do_with(|rt| {
469            let q_ctx = rt.get_context(id);
470            log::trace!("QuickJsRuntime::q_ctx.free: {}", id);
471            q_ctx.free();
472            log::trace!("after QuickJsRuntime::q_ctx.free: {}", id);
473            rt.gc();
474        });
475
476        let ctx = QuickJsRuntimeAdapter::do_with_mut(|m_rt| m_rt.contexts.remove(id));
477
478        match ctx {
479            None => {
480                anyhow::bail!("no such context");
481            }
482            Some(ctx) => {
483                drop(ctx);
484            }
485        }
486
487        Ok(())
488    }
489    pub(crate) fn get_context_ids() -> Vec<String> {
490        QuickJsRuntimeAdapter::do_with(|q_js_rt| {
491            q_js_rt.contexts.iter().map(|c| c.0.clone()).collect()
492        })
493    }
494    pub fn get_context(&self, id: &str) -> &QuickJsRealmAdapter {
495        self.contexts.get(id).expect("no such context")
496    }
497    pub fn opt_context(&self, id: &str) -> Option<&QuickJsRealmAdapter> {
498        self.contexts.get(id)
499    }
500    pub fn has_context(&self, id: &str) -> bool {
501        self.contexts.contains_key(id)
502    }
503    pub(crate) fn init_rti_ref(&mut self, el_ref: Weak<QuickjsRuntimeFacadeInner>) {
504        self.rti_ref = Some(el_ref);
505    }
506    /// # Safety
507    /// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
508    pub unsafe fn get_quickjs_context(&self, context: *mut q::JSContext) -> &QuickJsRealmAdapter {
509        let id = QuickJsRealmAdapter::get_id(context);
510        self.get_context(id)
511    }
512
513    pub fn get_rti_ref(&self) -> Option<Arc<QuickjsRuntimeFacadeInner>> {
514        if let Some(rt_ref) = &self.rti_ref {
515            rt_ref.upgrade()
516        } else {
517            None
518        }
519    }
520
521    pub(crate) fn new(runtime: *mut q::JSRuntime) -> Self {
522        log::trace!("creating new QuickJsRuntime");
523
524        if runtime.is_null() {
525            panic!("RuntimeCreationFailed");
526        }
527
528        // Configure memory limit if specified.
529        //let memory_limit = None;
530        //if let Some(limit) = memory_limit {
531        //  unsafe {
532        //q::JS_SetMemoryLimit(runtime, limit as _);
533        //}
534        //}
535
536        let id = format!("q_{}", thread_id::get());
537
538        let mut q_rt = Self {
539            runtime,
540            contexts: Default::default(),
541
542            rti_ref: None,
543            id,
544            context_init_hooks: RefCell::new(vec![]),
545            script_module_loaders: vec![],
546            native_module_loaders: vec![],
547            compiled_module_loaders: vec![],
548            script_pre_processors: vec![],
549            interrupt_handler: None,
550        };
551
552        modules::set_module_loader(&q_rt);
553        promises::init_promise_rejection_tracker(&q_rt);
554
555        let main_ctx = QuickJsRealmAdapter::new("__main__".to_string(), &q_rt);
556        q_rt.contexts.insert("__main__".to_string(), main_ctx);
557
558        q_rt
559    }
560
561    pub fn set_interrupt_handler<I: Fn(&QuickJsRuntimeAdapter) -> bool + 'static>(
562        &mut self,
563        interrupt_handler: I,
564    ) -> &mut Self {
565        self.interrupt_handler = Some(Box::new(interrupt_handler));
566        interrupthandler::init(self);
567        self
568    }
569
570    pub fn add_script_module_loader(&mut self, sml: ScriptModuleLoaderAdapter) {
571        self.script_module_loaders.push(sml);
572    }
573
574    pub fn add_compiled_module_loader(&mut self, cml: CompiledModuleLoaderAdapter) {
575        self.compiled_module_loaders.push(cml);
576    }
577
578    pub fn add_native_module_loader(&mut self, nml: NativeModuleLoaderAdapter) {
579        self.native_module_loaders.push(nml);
580    }
581
582    pub fn get_main_realm(&self) -> &QuickJsRealmAdapter {
583        // todo store this somewhere so we don't need a lookup in the map every time
584        self.get_context("__main__")
585    }
586
587    pub fn with_all_module_loaders<C, R>(&self, consumer: C) -> Option<R>
588    where
589        C: Fn(&dyn ModuleLoader) -> Option<R>,
590    {
591        for loader in &self.compiled_module_loaders {
592            let res = consumer(loader);
593            if res.is_some() {
594                return res;
595            }
596        }
597        for loader in &self.native_module_loaders {
598            let res = consumer(loader);
599            if res.is_some() {
600                return res;
601            }
602        }
603        for loader in &self.script_module_loaders {
604            let res = consumer(loader);
605            if res.is_some() {
606                return res;
607            }
608        }
609        None
610    }
611
612    /// run the garbage collector
613    pub fn gc(&self) {
614        gc(self);
615    }
616
617    pub fn do_with<C, R>(task: C) -> R
618    where
619        C: FnOnce(&QuickJsRuntimeAdapter) -> R,
620    {
621        let most_outer = NESTED.with(|rc| {
622            if *rc.borrow() {
623                false
624            } else {
625                *rc.borrow_mut() = true;
626                true
627            }
628        });
629
630        let res = QJS_RT.with(|qjs_rc| {
631            let qjs_rt_opt = &*qjs_rc.borrow();
632            let q_js_rt = qjs_rt_opt
633                .as_ref()
634                .expect("runtime was not yet initialized for this thread");
635            if most_outer {
636                unsafe { libquickjs_sys::JS_UpdateStackTop(q_js_rt.runtime) };
637            }
638            task(q_js_rt)
639        });
640
641        if most_outer {
642            NESTED.with(|rc| {
643                *rc.borrow_mut() = false;
644            });
645        }
646
647        res
648    }
649
650    pub fn do_with_mut<C, R>(task: C) -> R
651    where
652        C: FnOnce(&mut QuickJsRuntimeAdapter) -> R,
653    {
654        let most_outer = NESTED.with(|rc| {
655            if *rc.borrow() {
656                false
657            } else {
658                *rc.borrow_mut() = true;
659                true
660            }
661        });
662
663        let res = QJS_RT.with(|qjs_rc| {
664            let qjs_rt_opt = &mut *qjs_rc.borrow_mut();
665            let qjs_rt = qjs_rt_opt
666                .as_mut()
667                .expect("runtime was not yet initialized for this thread");
668            if most_outer {
669                unsafe { libquickjs_sys::JS_UpdateStackTop(qjs_rt.runtime) };
670            }
671            task(qjs_rt)
672        });
673
674        if most_outer {
675            NESTED.with(|rc| {
676                *rc.borrow_mut() = false;
677            });
678        }
679
680        res
681    }
682
683    /// run pending jobs if avail
684    /// # todo
685    /// move this to a quickjs_utils::pending_jobs so it can be used without doing QuickjsRuntime.do_with()
686    pub fn run_pending_jobs_if_any(&self) {
687        log::trace!("quick_js_rt.run_pending_jobs_if_any");
688        while self.has_pending_jobs() {
689            log::trace!("quick_js_rt.has_pending_jobs!");
690            let res = self.run_pending_job();
691            match res {
692                Ok(_) => {
693                    log::trace!("run_pending_job OK!");
694                }
695                Err(e) => {
696                    log::error!("run_pending_job failed: {}", e);
697                }
698            }
699        }
700    }
701
702    pub fn has_pending_jobs(&self) -> bool {
703        let flag = unsafe { q::JS_IsJobPending(self.runtime) };
704        #[cfg(feature = "bellard")]
705        {
706            flag > 0
707        }
708        #[cfg(feature = "quickjs-ng")]
709        {
710            flag
711        }
712    }
713
714    pub fn run_pending_job(&self) -> Result<(), JsError> {
715        let mut ctx: *mut q::JSContext = std::ptr::null_mut();
716        let flag = unsafe {
717            // ctx is a return arg here
718            q::JS_ExecutePendingJob(self.runtime, &mut ctx)
719        };
720        if flag < 0 {
721            let e = unsafe { QuickJsRealmAdapter::get_exception(ctx) }
722                .unwrap_or_else(|| JsError::new_str("Unknown exception while running pending job"));
723            return Err(e);
724        }
725        Ok(())
726    }
727
728    pub fn get_id(&self) -> &str {
729        self.id.as_str()
730    }
731
732    /// this method tries to load a module script using the runtimes script_module loaders
733    pub fn load_module_script_opt(&self, ref_path: &str, path: &str) -> Option<Script> {
734        let realm = self.get_main_realm();
735        for loader in &self.script_module_loaders {
736            let i = &loader.inner;
737            if let Some(normalized) = i.normalize_path(realm, ref_path, path) {
738                let code = i.load_module(realm, normalized.as_str());
739                return Some(Script::new(normalized.as_str(), code.as_str()));
740            }
741        }
742
743        None
744    }
745}
746
747impl Drop for QuickJsRuntimeAdapter {
748    fn drop(&mut self) {
749        // drop contexts first, should be done when Dropping EsRuntime?
750        log::trace!("drop QuickJsRuntime, dropping contexts");
751
752        self.contexts.clear();
753        log::trace!("drop QuickJsRuntime, after dropping contexts");
754
755        log::trace!("before JS_FreeRuntime");
756        unsafe { q::JS_FreeRuntime(self.runtime) };
757        log::trace!("after JS_FreeRuntime");
758    }
759}
760
761impl QuickJsRuntimeAdapter {
762    pub fn load_module_script(&self, ref_path: &str, path: &str) -> Option<Script> {
763        self.load_module_script_opt(ref_path, path)
764    }
765
766    pub fn get_realm(&self, id: &str) -> Option<&QuickJsRealmAdapter> {
767        if self.has_context(id) {
768            Some(self.get_context(id))
769        } else {
770            None
771        }
772    }
773
774    pub fn add_realm_init_hook<H>(&self, hook: H) -> Result<(), JsError>
775    where
776        H: Fn(&Self, &QuickJsRealmAdapter) -> Result<(), JsError> + 'static,
777    {
778        self.add_context_init_hook(hook)
779    }
780}
781
782/// Helper for creating CStrings.
783pub(crate) fn make_cstring(value: &str) -> Result<CString, JsError> {
784    let res = CString::new(value);
785    match res {
786        Ok(val) => Ok(val),
787        Err(_) => Err(JsError::new_string(format!(
788            "could not create cstring from {value}"
789        ))),
790    }
791}
792
793#[cfg(test)]
794pub mod tests {
795    use crate::builder::QuickJsRuntimeBuilder;
796    use crate::quickjsrealmadapter::QuickJsRealmAdapter;
797    use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
798
799    use crate::facades::tests::init_test_rt;
800    use std::panic;
801
802    use crate::jsutils::modules::ScriptModuleLoader;
803    use crate::jsutils::Script;
804
805    struct FooScriptModuleLoader {}
806    impl ScriptModuleLoader for FooScriptModuleLoader {
807        fn normalize_path(
808            &self,
809            _realm: &QuickJsRealmAdapter,
810            _ref_path: &str,
811            path: &str,
812        ) -> Option<String> {
813            Some(path.to_string())
814        }
815
816        fn load_module(&self, _realm: &QuickJsRealmAdapter, _absolute_path: &str) -> String {
817            log::debug!("load_module");
818            "{}".to_string()
819        }
820    }
821
822    #[test]
823    fn test_mem_usage() {
824        let rt = QuickJsRuntimeBuilder::new()
825            .memory_limit(1024 * 1024)
826            .build();
827        rt.eval_sync(None, Script::new("", "globalThis.f = function(){};"))
828            .expect("script failed");
829
830        rt.loop_realm_sync(None, |rt, _realm| {
831            let mu = rt.memory_usage();
832            println!("mu: {mu:?}");
833        });
834    }
835
836    #[test]
837    fn test_script_load() {
838        log::debug!("testing1");
839        let rt = QuickJsRuntimeBuilder::new()
840            .script_module_loader(FooScriptModuleLoader {})
841            .build();
842        rt.exe_rt_task_in_event_loop(|q_js_rt| {
843            log::debug!("testing2");
844            let script = q_js_rt.load_module_script_opt("", "test.mjs").unwrap();
845            assert_eq!(script.get_runnable_code(), "{}");
846            log::debug!("tested");
847        });
848    }
849
850    #[test]
851    fn test_eval() {
852        let rt = init_test_rt();
853        rt.eval_sync(
854            None,
855            Script::new(
856                "eval.js",
857                "console.log('euc: ' + encodeURIComponent('hello world'));",
858            ),
859        )
860        .expect("script failed to compile");
861    }
862
863    #[test]
864    fn test_realm_init() {
865        /*panic::set_hook(Box::new(|panic_info| {
866                    let backtrace = Backtrace::new();
867                    println!(
868                        "thread panic occurred: {}\nbacktrace: {:?}",
869                        panic_info, backtrace
870                    );
871                    log::error!(
872                        "thread panic occurred: {}\nbacktrace: {:?}",
873                        panic_info,
874                        backtrace
875                    );
876                }));
877
878                simple_logging::log_to_stderr(LevelFilter::max());
879        */
880        let rt = QuickJsRuntimeBuilder::new().build();
881
882        rt.exe_task_in_event_loop(|| {
883            QuickJsRuntimeAdapter::do_with(|rt| {
884                rt.add_realm_init_hook(|_rt, realm| {
885                    realm
886                        .install_function(
887                            &["utils"],
888                            "doSomething",
889                            |_rt, realm, _this, _args| realm.create_null(),
890                            0,
891                        )
892                        .expect("failed to install function");
893                    match realm.eval(Script::new("t.js", "1+1")) {
894                        Ok(_) => {}
895                        Err(e) => {
896                            panic!("script failed {}", e);
897                        }
898                    }
899                    Ok(())
900                })
901                .expect("init hook addition failed");
902            })
903        });
904
905        rt.loop_realm_void(Some("testrealm1"), |_rt, realm| {
906            realm
907                .eval(Script::new("test.js", "console.log(utils.doSomething());"))
908                .expect("script failed");
909        });
910    }
911}