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