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}