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                    let p =
89                        unsafe { &mut *(ctx.as_ptr() as *mut crate::context::ctx::RefCountHeader) };
90                    if p.ref_count <= 1 {
91                        // Lock was poisoned, this should only happen on a panic.
92                        // We should still free the context.
93                        // TODO see if there is a way to recover from a panic which could cause the
94                        // following assertion to trigger
95                        #[cfg(feature = "std")]
96                        assert!(std::thread::panicking());
97                    }
98                    unsafe { qjs::JS_FreeContext(ctx.as_ptr()) }
99                    return;
100                }
101                #[cfg(feature = "parallel")]
102                {
103                    self.drop_send
104                        .send(ctx)
105                        .expect("runtime should be alive while contexts life");
106                    return;
107                }
108            }
109        };
110        guard.runtime.update_stack_top();
111        unsafe { qjs::JS_FreeContext(ctx.as_ptr()) }
112        // Explicitly drop the guard to ensure it is valid during the entire use of runtime
113        mem::drop(guard);
114    }
115}
116
117/// An asynchronous single execution context with its own global variables and stack.
118///
119/// Can share objects with other contexts of the same runtime.
120#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
121#[derive(Clone)]
122pub struct AsyncContext(pub(crate) ContextOwner<AsyncRuntime>);
123
124impl AsyncContext {
125    /// Create a async context form a raw context pointer.
126    ///
127    /// # Safety
128    /// The context must be of the correct runtime.
129    /// The context must also have valid reference count, one which can be decremented when this
130    /// object is dropped without going negative.
131    pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: AsyncRuntime) -> Self {
132        AsyncContext(ContextOwner::new(ctx, rt))
133    }
134
135    /// Creates a base context with only the required functions registered.
136    /// If additional functions are required use [`AsyncContext::custom`],
137    /// [`AsyncContext::builder`] or [`AsyncContext::full`].
138    pub async fn base(runtime: &AsyncRuntime) -> Result<Self> {
139        Self::custom::<intrinsic::None>(runtime).await
140    }
141
142    /// Creates a context with only the required intrinsics registered.
143    /// If additional functions are required use [`AsyncContext::custom`],
144    /// [`AsyncContext::builder`] or [`AsyncContext::full`].
145    pub async fn custom<I: Intrinsic>(runtime: &AsyncRuntime) -> Result<Self> {
146        let guard = runtime.inner.lock().await;
147        let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.runtime.rt.as_ptr()) })
148            .ok_or(Error::Allocation)?;
149        unsafe { qjs::JS_AddIntrinsicBaseObjects(ctx.as_ptr()) };
150        unsafe { I::add_intrinsic(ctx) };
151        let res = unsafe { ContextOwner::new(ctx, runtime.clone()) };
152        guard.drop_pending();
153        mem::drop(guard);
154
155        Ok(AsyncContext(res))
156    }
157
158    /// Creates a context with all standard available intrinsics registered.
159    /// If precise control is required of which functions are available use
160    /// [`AsyncContext::custom`] or [`AsyncContext::builder`].
161    pub async fn full(runtime: &AsyncRuntime) -> Result<Self> {
162        let guard = runtime.inner.lock().await;
163        let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.runtime.rt.as_ptr()) })
164            .ok_or(Error::Allocation)?;
165        let res = unsafe { ContextOwner::new(ctx, runtime.clone()) };
166        // Explicitly drop the guard to ensure it is valid during the entire use of runtime
167        guard.drop_pending();
168        mem::drop(guard);
169
170        Ok(AsyncContext(res))
171    }
172
173    /// Create a context builder for creating a context with a specific set of intrinsics
174    pub fn builder() -> ContextBuilder<()> {
175        ContextBuilder::default()
176    }
177
178    /// Returns the associated runtime
179    pub fn runtime(&self) -> &AsyncRuntime {
180        self.0.rt()
181    }
182
183    /// A entry point for manipulating and using JavaScript objects and scripts.
184    ///
185    /// # Example
186    ///
187    /// ```
188    /// # use rquickjs::{prelude::*, Function, async_with, AsyncRuntime, AsyncContext, Result};
189    /// # use std::time::Duration;
190    /// # async fn run(){
191    /// # let rt = AsyncRuntime::new().unwrap();
192    ///
193    /// // In order for futures to convert to JavaScript promises they need to return `Result`.
194    /// async fn delay<'js>(amount: f64, cb: Function<'js>) -> Result<()> {
195    ///     tokio::time::sleep(Duration::from_secs_f64(amount)).await;
196    ///     cb.call::<(), ()>(());
197    ///     Ok(())
198    /// }
199    ///
200    /// let ctx = AsyncContext::full(&rt).await.unwrap();
201    /// ctx.async_with(async |ctx|{
202    ///
203    ///     let delay = Function::new(ctx.clone(),Async(delay))
204    ///         .unwrap()
205    ///         .with_name("delay")
206    ///         .unwrap();
207    ///
208    ///     let global = ctx.globals();
209    ///     global.set("delay",delay).unwrap();
210    ///     ctx.eval::<(),_>(r#"
211    ///         delay(1,() => {
212    ///             // do something
213    ///         })
214    ///     "#).unwrap();
215    /// }).await;
216    /// # }
217    /// ```
218    pub fn async_with<F, R>(&self, f: F) -> WithFuture<F, R>
219    where
220        F: for<'js> AsyncFnOnce(Ctx<'js>) -> R + ParallelSend,
221        R: ParallelSend,
222    {
223        WithFuture::new(self, f)
224    }
225
226    /// A entry point for manipulating and using JavaScript objects and scripts.
227    ///
228    /// This closure can't return a future, if you need to await JavaScript promises prefer the
229    /// [`async_with`] function.
230    pub async fn with<F, R>(&self, f: F) -> R
231    where
232        F: for<'js> FnOnce(Ctx<'js>) -> R + ParallelSend,
233        R: ParallelSend,
234    {
235        let guard = self.0.rt().inner.lock().await;
236        guard.runtime.update_stack_top();
237        let ctx = unsafe { Ctx::new_async(self) };
238        let res = f(ctx);
239        guard.drop_pending();
240        res
241    }
242}
243
244// Since the reference to runtime is behind a Arc this object is send
245#[cfg(feature = "parallel")]
246unsafe impl Send for AsyncContext {}
247
248// Since all functions lock the global runtime lock access is synchronized so
249// this object is sync
250#[cfg(feature = "parallel")]
251unsafe impl Sync for AsyncContext {}
252
253#[cfg(test)]
254mod test {
255    use crate::{AsyncContext, AsyncRuntime};
256
257    #[tokio::test]
258    async fn base_asyc_context() {
259        let rt = AsyncRuntime::new().unwrap();
260        let ctx = AsyncContext::builder().build_async(&rt).await.unwrap();
261        ctx.async_with(async |ctx| {
262            ctx.globals();
263        })
264        .await;
265    }
266
267    #[tokio::test]
268    async fn clone_ctx() {
269        let rt = AsyncRuntime::new().unwrap();
270        let ctx = AsyncContext::full(&rt).await.unwrap();
271
272        let ctx_clone = ctx.clone();
273
274        ctx.with(|ctx| {
275            let val: i32 = ctx.eval(r#"1+1"#).unwrap();
276
277            assert_eq!(val, 2);
278            println!("{:?}", ctx.globals());
279        })
280        .await;
281
282        ctx_clone
283            .with(|ctx| {
284                let val: i32 = ctx.eval(r#"1+1"#).unwrap();
285
286                assert_eq!(val, 2);
287                println!("{:?}", ctx.globals());
288            })
289            .await;
290    }
291
292    #[cfg(feature = "parallel")]
293    #[tokio::test]
294    async fn parallel_drop() {
295        use std::{
296            sync::{Arc, Barrier},
297            thread,
298        };
299
300        let wait_for_entry = Arc::new(Barrier::new(2));
301        let wait_for_exit = Arc::new(Barrier::new(2));
302
303        let rt = AsyncRuntime::new().unwrap();
304        let ctx_1 = AsyncContext::full(&rt).await.unwrap();
305        let ctx_2 = AsyncContext::full(&rt).await.unwrap();
306        let wait_for_entry_c = wait_for_entry.clone();
307        let wait_for_exit_c = wait_for_exit.clone();
308        thread::spawn(move || {
309            println!("wait_for entry ctx_1");
310            wait_for_entry_c.wait();
311            println!("dropping");
312            std::mem::drop(ctx_1);
313            println!("wait_for exit ctx_1");
314            wait_for_exit_c.wait();
315        });
316
317        println!("wait_for entry ctx_2");
318        rt.run_gc().await;
319        ctx_2
320            .with(|ctx| {
321                wait_for_entry.wait();
322                println!("evaling");
323                let i: i32 = ctx.eval("2 + 8").unwrap();
324                assert_eq!(i, 10);
325                println!("wait_for exit ctx_2");
326                wait_for_exit.wait();
327            })
328            .await;
329    }
330}