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}