rquickjs_core/runtime/
base.rs

1//! QuickJS runtime related types.
2
3use super::{
4    opaque::Opaque, raw::RawRuntime, InterruptHandler, MemoryUsage, PromiseHook, RejectionTracker,
5};
6use crate::allocator::Allocator;
7#[cfg(feature = "loader")]
8use crate::loader::{Loader, Resolver};
9use crate::{result::JobException, Context, Mut, Ref, Result, Weak};
10use alloc::{ffi::CString, vec::Vec};
11use core::{ptr::NonNull, result::Result as StdResult};
12
13/// A weak handle to the runtime.
14///
15/// Holding onto this struct does not prevent the runtime from being dropped.
16#[derive(Clone)]
17#[repr(transparent)]
18pub struct WeakRuntime(Weak<Mut<RawRuntime>>);
19
20impl WeakRuntime {
21    pub fn try_ref(&self) -> Option<Runtime> {
22        self.0.upgrade().map(|inner| Runtime { inner })
23    }
24}
25
26/// QuickJS runtime, entry point of the library.
27#[derive(Clone)]
28#[repr(transparent)]
29pub struct Runtime {
30    pub(crate) inner: Ref<Mut<RawRuntime>>,
31}
32
33impl Runtime {
34    /// Create a new runtime.
35    ///
36    /// Will generally only fail if not enough memory was available.
37    ///
38    /// # Features
39    /// *If the `"rust-alloc"` feature is enabled the Rust's global allocator will be used in favor of libc's one.*
40    pub fn new() -> Result<Self> {
41        let opaque = Opaque::new();
42        let rt = unsafe { RawRuntime::new(opaque)? };
43        Ok(Self {
44            inner: Ref::new(Mut::new(rt)),
45        })
46    }
47
48    /// Create a new runtime using specified allocator
49    ///
50    /// Will generally only fail if not enough memory was available.
51    pub fn new_with_alloc<A>(allocator: A) -> Result<Self>
52    where
53        A: Allocator + 'static,
54    {
55        let opaque = Opaque::new();
56        let rt = unsafe { RawRuntime::new_with_allocator(opaque, allocator)? };
57        Ok(Self {
58            inner: Ref::new(Mut::new(rt)),
59        })
60    }
61
62    /// Get weak ref to runtime
63    pub fn weak(&self) -> WeakRuntime {
64        WeakRuntime(Ref::downgrade(&self.inner))
65    }
66
67    /// Set a closure which is called when a promise is created, resolved, or chained.
68    #[inline]
69    pub fn set_promise_hook(&self, tracker: Option<PromiseHook>) {
70        unsafe {
71            self.inner.lock().set_promise_hook(tracker);
72        }
73    }
74
75    /// Set a closure which is called when a Promise is rejected.
76    #[inline]
77    pub fn set_host_promise_rejection_tracker(&self, tracker: Option<RejectionTracker>) {
78        unsafe {
79            self.inner
80                .lock()
81                .set_host_promise_rejection_tracker(tracker);
82        }
83    }
84
85    /// Set a closure which is regularly called by the engine when it is executing code.
86    /// If the provided closure returns `true` the interpreter will raise and uncatchable
87    /// exception and return control flow to the caller.
88    #[inline]
89    pub fn set_interrupt_handler(&self, handler: Option<InterruptHandler>) {
90        unsafe {
91            self.inner.lock().set_interrupt_handler(handler);
92        }
93    }
94
95    /// Set the module loader
96    #[cfg(feature = "loader")]
97    #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
98    pub fn set_loader<R, L>(&self, resolver: R, loader: L)
99    where
100        R: Resolver + 'static,
101        L: Loader + 'static,
102    {
103        unsafe {
104            self.inner.lock().set_loader(resolver, loader);
105        }
106    }
107
108    /// Set the info of the runtime
109    pub fn set_info<S: Into<Vec<u8>>>(&self, info: S) -> Result<()> {
110        let string = CString::new(info)?;
111        unsafe {
112            self.inner.lock().set_info(string);
113        }
114        Ok(())
115    }
116
117    /// Set a limit on the max amount of memory the runtime will use.
118    ///
119    /// Setting the limit to 0 is equivalent to unlimited memory.
120    ///
121    /// Note that is a Noop when a custom allocator is being used,
122    /// as is the case for the "rust-alloc" or "allocator" features.
123    pub fn set_memory_limit(&self, limit: usize) {
124        unsafe {
125            self.inner.lock().set_memory_limit(limit);
126        }
127    }
128
129    /// Set a limit on the max size of stack the runtime will use.
130    ///
131    /// The default values is 256x1024 bytes.
132    pub fn set_max_stack_size(&self, limit: usize) {
133        unsafe {
134            self.inner.lock().set_max_stack_size(limit);
135        }
136    }
137
138    /// Set a memory threshold for garbage collection.
139    pub fn set_gc_threshold(&self, threshold: usize) {
140        unsafe {
141            self.inner.lock().set_gc_threshold(threshold);
142        }
143    }
144
145    /// Set debug flags for dumping memory
146    pub fn set_dump_flags(&self, flags: u64) {
147        unsafe {
148            self.inner.lock().set_dump_flags(flags);
149        }
150    }
151
152    /// Manually run the garbage collection.
153    ///
154    /// Most of QuickJS values are reference counted and
155    /// will automatically free themselves when they have no more
156    /// references. The garbage collector is only for collecting
157    /// cyclic references.
158    pub fn run_gc(&self) {
159        unsafe {
160            self.inner.lock().run_gc();
161        }
162    }
163
164    /// Get memory usage stats
165    pub fn memory_usage(&self) -> MemoryUsage {
166        unsafe { self.inner.lock().memory_usage() }
167    }
168
169    /// Test for pending jobs
170    ///
171    /// Returns true when at least one job is pending.
172    #[inline]
173    pub fn is_job_pending(&self) -> bool {
174        self.inner.lock().is_job_pending()
175    }
176
177    /// Execute first pending job
178    ///
179    /// Returns true when job was executed or false when queue is empty or error when exception thrown under execution.
180    #[inline]
181    pub fn execute_pending_job(&self) -> StdResult<bool, JobException> {
182        let mut lock = self.inner.lock();
183        lock.update_stack_top();
184        lock.execute_pending_job().map_err(|e| {
185            JobException(unsafe {
186                Context::from_raw(
187                    NonNull::new(e).expect("QuickJS returned null ptr for job error"),
188                    self.clone(),
189                )
190            })
191        })
192    }
193}
194
195// Since all functions which use runtime are behind a mutex
196// sending the runtime to other threads should be fine.
197#[cfg(feature = "parallel")]
198unsafe impl Send for Runtime {}
199#[cfg(feature = "parallel")]
200unsafe impl Send for WeakRuntime {}
201
202// Since a global lock needs to be locked for safe use
203// using runtime in a sync way should be safe as
204// simultaneous accesses is synchronized behind a lock.
205#[cfg(feature = "parallel")]
206unsafe impl Sync for Runtime {}
207#[cfg(feature = "parallel")]
208unsafe impl Sync for WeakRuntime {}
209
210#[cfg(test)]
211mod test {
212    use super::*;
213    #[test]
214    fn base_runtime() {
215        let rt = Runtime::new().unwrap();
216        rt.set_info("test runtime").unwrap();
217        rt.set_memory_limit(0xFFFF);
218        rt.set_gc_threshold(0xFF);
219        rt.run_gc();
220    }
221}