rquickjs_core/context/
async.rs

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