use super::{intrinsic, r#ref::ContextRef, ContextBuilder, Intrinsic};
use crate::{markers::ParallelSend, qjs, runtime::AsyncRuntime, Ctx, Error, Result};
use std::{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 = Box::pin(async move {
$($t)*
});
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>>{
std::mem::transmute(f)
}
unsafe{ uplift(fut) }
})
};
}
pub(crate) struct Inner {
pub(crate) ctx: NonNull<qjs::JSContext>,
pub(crate) rt: AsyncRuntime,
}
impl Clone for Inner {
fn clone(&self) -> Inner {
let ctx = unsafe { NonNull::new_unchecked(qjs::JS_DupContext(self.ctx.as_ptr())) };
let rt = self.rt.clone();
Self { ctx, rt }
}
}
#[cfg(feature = "parallel")]
unsafe impl Send for Inner {}
impl Drop for Inner {
fn drop(&mut self) {
let guard = match self.rt.inner.try_lock() {
Some(x) => x,
None => {
#[cfg(not(feature = "parallel"))]
{
let p = unsafe {
&mut *(self.ctx.as_ptr() as *mut crate::context::ctx::RefCountHeader)
};
if p.ref_count <= 1 {
assert!(std::thread::panicking());
}
unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) }
return;
}
#[cfg(feature = "parallel")]
{
self.rt
.drop_send
.send(self.ctx)
.expect("runtime should be alive while contexts life");
return;
}
}
};
guard.runtime.update_stack_top();
unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) }
mem::drop(guard);
}
}
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
#[derive(Clone)]
pub struct AsyncContext(pub(crate) ContextRef<Inner>);
impl AsyncContext {
pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: AsyncRuntime) -> Self {
AsyncContext(ContextRef::new(Inner { 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_else(|| Error::Allocation)?;
unsafe { I::add_intrinsic(ctx) };
let res = Inner {
ctx,
rt: runtime.clone(),
};
guard.drop_pending();
mem::drop(guard);
Ok(AsyncContext(ContextRef::new(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_else(|| Error::Allocation)?;
let res = Inner {
ctx,
rt: runtime.clone(),
};
guard.drop_pending();
mem::drop(guard);
Ok(AsyncContext(ContextRef::new(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 {
#[cfg(feature = "parallel")]
use crate::{AsyncContext, AsyncRuntime};
#[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;
}
}