use super::{
intrinsic,
owner::{ContextOwner, DropContext},
ContextBuilder, Intrinsic,
};
use crate::{markers::ParallelSend, qjs, runtime::AsyncRuntime, Ctx, Error, Result};
use alloc::boxed::Box;
use core::{future::Future, mem, pin::Pin, ptr::NonNull};
mod future;
use future::WithFuture;
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
#[macro_export]
macro_rules! async_with{
($context:expr => |$ctx:ident| { $($t:tt)* }) => {
$crate::AsyncContext::async_with(&$context,|$ctx| {
let fut = $crate::alloc::boxed::Box::pin(async move {
$($t)*
});
unsafe fn uplift<'a,'b,R>(f: core::pin::Pin<$crate::alloc::boxed::Box<dyn core::future::Future<Output = R> + 'a>>) -> core::pin::Pin<$crate::alloc::boxed::Box<dyn core::future::Future<Output = R> + 'b + Send>>{
core::mem::transmute(f)
}
unsafe{ uplift(fut) }
})
};
}
impl DropContext for AsyncRuntime {
unsafe fn drop_context(&self, ctx: NonNull<qjs::JSContext>) {
let guard = match self.inner.try_lock() {
Some(x) => x,
None => {
#[cfg(not(feature = "parallel"))]
{
let p =
unsafe { &mut *(ctx.as_ptr() as *mut crate::context::ctx::RefCountHeader) };
if p.ref_count <= 1 {
#[cfg(feature = "std")]
assert!(std::thread::panicking());
}
unsafe { qjs::JS_FreeContext(ctx.as_ptr()) }
return;
}
#[cfg(feature = "parallel")]
{
self.drop_send
.send(ctx)
.expect("runtime should be alive while contexts life");
return;
}
}
};
guard.runtime.update_stack_top();
unsafe { qjs::JS_FreeContext(ctx.as_ptr()) }
mem::drop(guard);
}
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
#[derive(Clone)]
pub struct AsyncContext(pub(crate) ContextOwner<AsyncRuntime>);
impl AsyncContext {
pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: AsyncRuntime) -> Self {
AsyncContext(ContextOwner::new(ctx, rt))
}
pub async fn base(runtime: &AsyncRuntime) -> Result<Self> {
Self::custom::<intrinsic::None>(runtime).await
}
pub async fn custom<I: Intrinsic>(runtime: &AsyncRuntime) -> Result<Self> {
let guard = runtime.inner.lock().await;
let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.runtime.rt.as_ptr()) })
.ok_or(Error::Allocation)?;
unsafe { qjs::JS_AddIntrinsicBaseObjects(ctx.as_ptr()) };
unsafe { I::add_intrinsic(ctx) };
let res = unsafe { ContextOwner::new(ctx, runtime.clone()) };
guard.drop_pending();
mem::drop(guard);
Ok(AsyncContext(res))
}
pub async fn full(runtime: &AsyncRuntime) -> Result<Self> {
let guard = runtime.inner.lock().await;
let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.runtime.rt.as_ptr()) })
.ok_or(Error::Allocation)?;
let res = unsafe { ContextOwner::new(ctx, runtime.clone()) };
guard.drop_pending();
mem::drop(guard);
Ok(AsyncContext(res))
}
pub fn builder() -> ContextBuilder<()> {
ContextBuilder::default()
}
pub fn runtime(&self) -> &AsyncRuntime {
self.0.rt()
}
pub fn async_with<F, R>(&self, f: F) -> WithFuture<F, R>
where
F: for<'js> FnOnce(Ctx<'js>) -> Pin<Box<dyn Future<Output = R> + 'js + Send>>
+ ParallelSend,
R: ParallelSend,
{
WithFuture::new(self, f)
}
pub async fn with<F, R>(&self, f: F) -> R
where
F: for<'js> FnOnce(Ctx<'js>) -> R + ParallelSend,
R: ParallelSend,
{
let guard = self.0.rt().inner.lock().await;
guard.runtime.update_stack_top();
let ctx = unsafe { Ctx::new_async(self) };
let res = f(ctx);
guard.drop_pending();
res
}
}
#[cfg(feature = "parallel")]
unsafe impl Send for AsyncContext {}
#[cfg(feature = "parallel")]
unsafe impl Sync for AsyncContext {}
#[cfg(test)]
mod test {
use crate::{AsyncContext, AsyncRuntime};
#[tokio::test]
async fn base_asyc_context() {
let rt = AsyncRuntime::new().unwrap();
let ctx = AsyncContext::builder().build_async(&rt).await.unwrap();
async_with!(&ctx => |ctx|{
ctx.globals();
})
.await;
}
#[tokio::test]
async fn clone_ctx() {
let rt = AsyncRuntime::new().unwrap();
let ctx = AsyncContext::full(&rt).await.unwrap();
let ctx_clone = ctx.clone();
ctx.with(|ctx| {
let val: i32 = ctx.eval(r#"1+1"#).unwrap();
assert_eq!(val, 2);
println!("{:?}", ctx.globals());
})
.await;
ctx_clone
.with(|ctx| {
let val: i32 = ctx.eval(r#"1+1"#).unwrap();
assert_eq!(val, 2);
println!("{:?}", ctx.globals());
})
.await;
}
#[cfg(feature = "parallel")]
#[tokio::test]
async fn parallel_drop() {
use std::{
sync::{Arc, Barrier},
thread,
};
let wait_for_entry = Arc::new(Barrier::new(2));
let wait_for_exit = Arc::new(Barrier::new(2));
let rt = AsyncRuntime::new().unwrap();
let ctx_1 = AsyncContext::full(&rt).await.unwrap();
let ctx_2 = AsyncContext::full(&rt).await.unwrap();
let wait_for_entry_c = wait_for_entry.clone();
let wait_for_exit_c = wait_for_exit.clone();
thread::spawn(move || {
println!("wait_for entry ctx_1");
wait_for_entry_c.wait();
println!("dropping");
std::mem::drop(ctx_1);
println!("wait_for exit ctx_1");
wait_for_exit_c.wait();
});
println!("wait_for entry ctx_2");
rt.run_gc().await;
ctx_2
.with(|ctx| {
wait_for_entry.wait();
println!("evaling");
let i: i32 = ctx.eval("2 + 8").unwrap();
assert_eq!(i, 10);
println!("wait_for exit ctx_2");
wait_for_exit.wait();
})
.await;
}
}