rquickjs_core/context/
async.rs

1use super::{intrinsic, r#ref::ContextRef, ContextBuilder, Intrinsic};
2use crate::{markers::ParallelSend, qjs, runtime::AsyncRuntime, Ctx, Error, Result};
3use std::{future::Future, mem, pin::Pin, ptr::NonNull};
4
5mod future;
6
7use future::WithFuture;
8
9/// A macro for safely using an asynchronous context while capturing the environment.
10///
11/// # Usage
12/// ```
13/// # use rquickjs::{prelude::*, Function, async_with, AsyncRuntime, AsyncContext, Result};
14/// # use std::time::Duration;
15/// # async fn run(){
16/// let rt = AsyncRuntime::new().unwrap();
17/// let ctx = AsyncContext::full(&rt).await.unwrap();
18///
19/// // In order for futures to convert to JavaScript promises they need to return `Result`.
20/// async fn delay<'js>(amount: f64, cb: Function<'js>) -> Result<()> {
21///     tokio::time::sleep(Duration::from_secs_f64(amount)).await;
22///     cb.call::<(), ()>(());
23///     Ok(())
24/// }
25///
26/// fn print(text: String) -> Result<()> {
27///     println!("{}", text);
28///     Ok(())
29/// }
30///
31/// let mut some_var = 1;
32/// // closure always moves, so create a ref.
33/// let some_var_ref = &mut some_var;
34/// async_with!(ctx => |ctx|{
35///     
36///     // With the macro you can borrow the environment.
37///     *some_var_ref += 1;
38///
39///     let delay = Function::new(ctx.clone(),Async(delay))
40///         .unwrap()
41///         .with_name("print")
42///         .unwrap();
43///
44///     let global = ctx.globals();
45///     global.set("print",Func::from(print)).unwrap();
46///     global.set("delay",delay).unwrap();
47///     ctx.eval::<(),_>(r#"
48///         print("start");
49///         delay(1,() => {
50///             print("delayed");
51///         })
52///         print("after");
53///     "#).unwrap();
54/// }).await;
55/// assert_eq!(some_var,2);
56///
57/// rt.idle().await
58/// # }
59/// ```
60#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
61#[macro_export]
62macro_rules! async_with{
63    ($context:expr => |$ctx:ident| { $($t:tt)* }) => {
64        $crate::AsyncContext::async_with(&$context,|$ctx| {
65            let fut = Box::pin(async move {
66                $($t)*
67            });
68            /// SAFETY: While rquickjs objects have a 'js lifetime attached to them,
69            /// they actually life much longer an the lifetime is just for checking
70            /// if they belong to the correct context.
71            /// By requiring that everything is moved into the closure outside
72            /// environments still can't life shorter than the closure.
73            /// This allows use to recast the future to a higher lifetime without problems.
74            /// Second, the future will always acquire a lock before running. The closure
75            /// enforces that everything moved into the future is send, but non of the
76            /// rquickjs objects are send so the future will never be send.
77            /// Since we acquire a lock before running the future and nothing can escape the closure
78            /// and future it is safe to recast the future as send.
79            unsafe fn uplift<'a,'b,R>(f: std::pin::Pin<Box<dyn std::future::Future<Output = R> + 'a>>) -> std::pin::Pin<Box<dyn std::future::Future<Output = R> + 'b + Send>>{
80                std::mem::transmute(f)
81            }
82            unsafe{ uplift(fut) }
83        })
84    };
85}
86
87pub(crate) struct Inner {
88    pub(crate) ctx: NonNull<qjs::JSContext>,
89    pub(crate) rt: AsyncRuntime,
90}
91
92impl Clone for Inner {
93    fn clone(&self) -> Inner {
94        let ctx = unsafe { NonNull::new_unchecked(qjs::JS_DupContext(self.ctx.as_ptr())) };
95        let rt = self.rt.clone();
96        Self { ctx, rt }
97    }
98}
99
100#[cfg(feature = "parallel")]
101unsafe impl Send for Inner {}
102
103impl Drop for Inner {
104    fn drop(&mut self) {
105        //TODO
106        let guard = match self.rt.inner.try_lock() {
107            Some(x) => x,
108            None => {
109                #[cfg(not(feature = "parallel"))]
110                {
111                    let p = unsafe {
112                        &mut *(self.ctx.as_ptr() as *mut crate::context::ctx::RefCountHeader)
113                    };
114                    if p.ref_count <= 1 {
115                        // Lock was poisoned, this should only happen on a panic.
116                        // We should still free the context.
117                        // TODO see if there is a way to recover from a panic which could cause the
118                        // following assertion to trigger
119                        assert!(std::thread::panicking());
120                    }
121                    unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) }
122                    return;
123                }
124                #[cfg(feature = "parallel")]
125                {
126                    self.rt
127                        .drop_send
128                        .send(self.ctx)
129                        .expect("runtime should be alive while contexts life");
130                    return;
131                }
132            }
133        };
134        guard.runtime.update_stack_top();
135        unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) }
136        // Explicitly drop the guard to ensure it is valid during the entire use of runtime
137        mem::drop(guard);
138    }
139}
140
141/// An asynchronous single execution context with its own global variables and stack.
142///
143/// Can share objects with other contexts of the same runtime.
144#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
145#[derive(Clone)]
146pub struct AsyncContext(pub(crate) ContextRef<Inner>);
147
148impl AsyncContext {
149    /// Create a async context form a raw context pointer.
150    ///
151    /// # Safety
152    /// The context must be of the correct runtime.
153    /// The context must also have valid reference count, one which can be decremented when this
154    /// object is dropped without going negative.
155    pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: AsyncRuntime) -> Self {
156        AsyncContext(ContextRef::new(Inner { ctx, rt }))
157    }
158
159    /// Creates a base context with only the required functions registered.
160    /// If additional functions are required use [`AsyncContext::custom`],
161    /// [`AsyncContext::builder`] or [`AsyncContext::full`].
162    pub async fn base(runtime: &AsyncRuntime) -> Result<Self> {
163        Self::custom::<intrinsic::None>(runtime).await
164    }
165
166    /// Creates a context with only the required intrinsics registered.
167    /// If additional functions are required use [`AsyncContext::custom`],
168    /// [`AsyncContext::builder`] or [`AsyncContext::full`].
169    pub async fn custom<I: Intrinsic>(runtime: &AsyncRuntime) -> Result<Self> {
170        let guard = runtime.inner.lock().await;
171        let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.runtime.rt.as_ptr()) })
172            .ok_or_else(|| Error::Allocation)?;
173        unsafe { I::add_intrinsic(ctx) };
174        let res = Inner {
175            ctx,
176            rt: runtime.clone(),
177        };
178        guard.drop_pending();
179        mem::drop(guard);
180
181        Ok(AsyncContext(ContextRef::new(res)))
182    }
183
184    /// Creates a context with all standard available intrinsics registered.
185    /// If precise control is required of which functions are available use
186    /// [`AsyncContext::custom`] or [`AsyncContext::builder`].
187    pub async fn full(runtime: &AsyncRuntime) -> Result<Self> {
188        let guard = runtime.inner.lock().await;
189        let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.runtime.rt.as_ptr()) })
190            .ok_or_else(|| Error::Allocation)?;
191        let res = Inner {
192            ctx,
193            rt: runtime.clone(),
194        };
195        // Explicitly drop the guard to ensure it is valid during the entire use of runtime
196        guard.drop_pending();
197        mem::drop(guard);
198
199        Ok(AsyncContext(ContextRef::new(res)))
200    }
201
202    /// Create a context builder for creating a context with a specific set of intrinsics
203    pub fn builder() -> ContextBuilder<()> {
204        ContextBuilder::default()
205    }
206
207    /// Returns the associated runtime
208    pub fn runtime(&self) -> &AsyncRuntime {
209        &self.0.rt
210    }
211
212    /// A entry point for manipulating and using JavaScript objects and scripts.
213    ///
214    /// This function is rather limited in what environment it can capture. If you need to borrow
215    /// the environment in the closure use the [`async_with!`] macro.
216    ///
217    /// Unfortunately it is currently impossible to have closures return a generic future which has a higher
218    /// rank trait bound lifetime. So, to allow closures to work, the closure must return a boxed
219    /// future.
220    pub fn async_with<F, R>(&self, f: F) -> WithFuture<F, R>
221    where
222        F: for<'js> FnOnce(Ctx<'js>) -> Pin<Box<dyn Future<Output = R> + 'js + Send>>
223            + ParallelSend,
224        R: ParallelSend,
225    {
226        WithFuture::new(self, f)
227    }
228
229    /// A entry point for manipulating and using JavaScript objects and scripts.
230    ///
231    /// This closure can't return a future, if you need to await JavaScript promises prefer the
232    /// [`async_with!`] macro.
233    pub async fn with<F, R>(&self, f: F) -> R
234    where
235        F: for<'js> FnOnce(Ctx<'js>) -> R + ParallelSend,
236        R: ParallelSend,
237    {
238        let guard = self.0.rt.inner.lock().await;
239        guard.runtime.update_stack_top();
240        let ctx = unsafe { Ctx::new_async(self) };
241        let res = f(ctx);
242        guard.drop_pending();
243        res
244    }
245}
246
247// Since the reference to runtime is behind a Arc this object is send
248#[cfg(feature = "parallel")]
249unsafe impl Send for AsyncContext {}
250
251// Since all functions lock the global runtime lock access is synchronized so
252// this object is sync
253#[cfg(feature = "parallel")]
254unsafe impl Sync for AsyncContext {}
255
256#[cfg(test)]
257mod test {
258    #[cfg(feature = "parallel")]
259    use crate::{AsyncContext, AsyncRuntime};
260    #[cfg(feature = "parallel")]
261    #[tokio::test]
262    async fn parallel_drop() {
263        use std::{
264            sync::{Arc, Barrier},
265            thread,
266        };
267
268        let wait_for_entry = Arc::new(Barrier::new(2));
269        let wait_for_exit = Arc::new(Barrier::new(2));
270
271        let rt = AsyncRuntime::new().unwrap();
272        let ctx_1 = AsyncContext::full(&rt).await.unwrap();
273        let ctx_2 = AsyncContext::full(&rt).await.unwrap();
274        let wait_for_entry_c = wait_for_entry.clone();
275        let wait_for_exit_c = wait_for_exit.clone();
276        thread::spawn(move || {
277            println!("wait_for entry ctx_1");
278            wait_for_entry_c.wait();
279            println!("dropping");
280            std::mem::drop(ctx_1);
281            println!("wait_for exit ctx_1");
282            wait_for_exit_c.wait();
283        });
284
285        println!("wait_for entry ctx_2");
286        rt.run_gc().await;
287        ctx_2
288            .with(|ctx| {
289                wait_for_entry.wait();
290                println!("evaling");
291                let i: i32 = ctx.eval("2 + 8").unwrap();
292                assert_eq!(i, 10);
293                println!("wait_for exit ctx_2");
294                wait_for_exit.wait();
295            })
296            .await;
297    }
298}