rquickjs_core/runtime/
base.rs

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