Skip to main content

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