Skip to main content

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::{qjs, 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    /// Attaches a versioned backend and returns the guard that owns it.
68    #[cfg(feature = "jit-abi")]
69    #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "jit-abi")))]
70    pub fn attach_jit_backend<B>(
71        &self,
72        backend: B,
73    ) -> StdResult<super::RuntimeJitGuard, super::JitBackendAttachError>
74    where
75        B: super::JitBackend,
76    {
77        super::RuntimeJitGuard::attach(self, backend)
78    }
79
80    /// Returns the engine-assigned identity used by JIT artifacts.
81    #[cfg(feature = "jit-abi")]
82    #[doc(hidden)]
83    pub fn jit_runtime_id(&self) -> u64 {
84        self.inner.lock().jit_runtime_id()
85    }
86
87    /// Set a closure which is called when a promise is created, resolved, or chained.
88    #[inline]
89    pub fn set_promise_hook(&self, tracker: Option<PromiseHook>) {
90        unsafe {
91            self.inner.lock().set_promise_hook(tracker);
92        }
93    }
94
95    /// Set a closure which is called when a Promise is rejected.
96    #[inline]
97    pub fn set_host_promise_rejection_tracker(&self, tracker: Option<RejectionTracker>) {
98        unsafe {
99            self.inner
100                .lock()
101                .set_host_promise_rejection_tracker(tracker);
102        }
103    }
104
105    /// Set a closure which is regularly called by the engine when it is executing code.
106    /// If the provided closure returns `true` the interpreter will raise and uncatchable
107    /// exception and return control flow to the caller.
108    #[inline]
109    pub fn set_interrupt_handler(&self, handler: Option<InterruptHandler>) {
110        unsafe {
111            self.inner.lock().set_interrupt_handler(handler);
112        }
113    }
114
115    /// Set the module loader
116    #[cfg(feature = "loader")]
117    #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "loader")))]
118    pub fn set_loader<R, L>(&self, resolver: R, loader: L)
119    where
120        R: Resolver + 'static,
121        L: Loader + 'static,
122    {
123        unsafe {
124            self.inner.lock().set_loader(resolver, loader);
125        }
126    }
127
128    /// Set the info of the runtime
129    pub fn set_info<S: Into<Vec<u8>>>(&self, info: S) -> Result<()> {
130        let string = CString::new(info)?;
131        unsafe {
132            self.inner.lock().set_info(string);
133        }
134        Ok(())
135    }
136
137    /// Set a limit on the max amount of memory the runtime will use.
138    ///
139    /// Setting the limit to 0 is equivalent to unlimited memory.
140    ///
141    /// Note that is a Noop when a custom allocator is being used,
142    /// as is the case for the "rust-alloc" or "allocator" features.
143    pub fn set_memory_limit(&self, limit: usize) {
144        unsafe {
145            self.inner.lock().set_memory_limit(limit);
146        }
147    }
148
149    /// Set a limit on the max size of stack the runtime will use.
150    ///
151    /// The default values is 256x1024 bytes.
152    pub fn set_max_stack_size(&self, limit: usize) {
153        unsafe {
154            self.inner.lock().set_max_stack_size(limit);
155        }
156    }
157
158    /// Set a memory threshold for garbage collection.
159    pub fn set_gc_threshold(&self, threshold: usize) {
160        unsafe {
161            self.inner.lock().set_gc_threshold(threshold);
162        }
163    }
164
165    /// Set debug flags for dumping memory
166    pub fn set_dump_flags(&self, flags: u64) {
167        unsafe {
168            self.inner.lock().set_dump_flags(flags);
169        }
170    }
171
172    /// Manually run the garbage collection.
173    ///
174    /// Most of QuickJS values are reference counted and
175    /// will automatically free themselves when they have no more
176    /// references. The garbage collector is only for collecting
177    /// cyclic references.
178    pub fn run_gc(&self) {
179        unsafe {
180            self.inner.lock().run_gc();
181        }
182    }
183
184    /// Get memory usage stats
185    pub fn memory_usage(&self) -> MemoryUsage {
186        unsafe { self.inner.lock().memory_usage() }
187    }
188
189    /// Test for pending jobs
190    ///
191    /// Returns true when at least one job is pending.
192    #[inline]
193    pub fn is_job_pending(&self) -> bool {
194        self.inner.lock().is_job_pending()
195    }
196
197    /// Execute first pending job
198    ///
199    /// Returns true when job was executed or false when queue is empty or error when exception thrown under execution.
200    #[inline]
201    pub fn execute_pending_job(&self) -> StdResult<bool, JobException> {
202        let mut lock = self.inner.lock();
203        lock.update_stack_top();
204        lock.execute_pending_job().map_err(|e| {
205            let ptr = NonNull::new(e).expect("QuickJS returned null ptr for job error");
206            // JS_ExecutePendingJob returns a borrowed context pointer;
207            // dup it so Context can own a reference.
208            unsafe { qjs::JS_DupContext(ptr.as_ptr()) };
209            JobException(unsafe { Context::from_raw(ptr, self.clone()) })
210        })
211    }
212}
213
214// Since all functions which use runtime are behind a mutex
215// sending the runtime to other threads should be fine.
216#[cfg(feature = "parallel")]
217unsafe impl Send for Runtime {}
218#[cfg(feature = "parallel")]
219unsafe impl Send for WeakRuntime {}
220
221// Since a global lock needs to be locked for safe use
222// using runtime in a sync way should be safe as
223// simultaneous accesses is synchronized behind a lock.
224#[cfg(feature = "parallel")]
225unsafe impl Sync for Runtime {}
226#[cfg(feature = "parallel")]
227unsafe impl Sync for WeakRuntime {}
228
229#[cfg(test)]
230mod test {
231    use super::*;
232    #[test]
233    fn base_runtime() {
234        let rt = Runtime::new().unwrap();
235        rt.set_info("test runtime").unwrap();
236        rt.set_memory_limit(0xFFFF);
237        rt.set_gc_threshold(0xFF);
238        rt.run_gc();
239    }
240
241    #[test]
242    fn set_max_stack_size_large_values() {
243        let rt = Runtime::new().unwrap();
244        rt.set_max_stack_size(usize::MAX);
245        let ctx = crate::Context::full(&rt).unwrap();
246        ctx.with(|ctx| {
247            ctx.eval::<i32, _>("1 + 1").unwrap();
248        });
249        rt.set_max_stack_size(isize::MAX as usize);
250        ctx.with(|ctx| {
251            ctx.eval::<i32, _>("1 + 1").unwrap();
252        });
253        rt.set_max_stack_size(0);
254        ctx.with(|ctx| {
255            ctx.eval::<i32, _>("1 + 1").unwrap();
256        });
257        rt.set_max_stack_size(256 * 1024);
258        ctx.with(|ctx| {
259            ctx.eval::<i32, _>("1 + 1").unwrap();
260        });
261    }
262}