use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;
use super::Vm;
tokio::task_local! {
static ASYNC_BUILTIN_CTX: AsyncBuiltinCtx;
}
pub struct AsyncBuiltinCtx {
child: Rc<RefCell<Vm>>,
}
impl AsyncBuiltinCtx {
fn new(vm: Vm) -> Self {
Self {
child: Rc::new(RefCell::new(vm)),
}
}
}
pub fn clone_async_builtin_child_vm() -> Option<Vm> {
ASYNC_BUILTIN_CTX
.try_with(|ctx| ctx.child.borrow().child_vm())
.ok()
}
pub fn forward_child_output_to_parent(text: &str) {
if text.is_empty() {
return;
}
let _ = ASYNC_BUILTIN_CTX.try_with(|ctx| ctx.child.borrow_mut().append_output(text));
}
pub(crate) fn run_async_builtin_with<F>(
child: Vm,
fut: F,
) -> impl Future<Output = (F::Output, String)>
where
F: Future,
{
let ctx = AsyncBuiltinCtx::new(child);
let sink = Rc::clone(&ctx.child);
let scoped = ASYNC_BUILTIN_CTX.scope(ctx, fut);
async move {
let output = scoped.await;
let captured = sink.borrow_mut().take_output();
(output, captured)
}
}
pub fn scope_async_builtin<F>(vm: Vm, fut: F) -> impl Future<Output = F::Output>
where
F: Future,
{
ASYNC_BUILTIN_CTX.scope(AsyncBuiltinCtx::new(vm), fut)
}
pub fn spawn_local_with_async_builtin_ctx<F>(fut: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
let captured = clone_async_builtin_child_vm();
tokio::task::spawn_local(async move {
match captured {
Some(vm) => scope_async_builtin(vm, fut).await,
None => fut.await,
}
})
}
pub fn with_async_builtin_ctx_sync<R>(vm: Vm, f: impl FnOnce() -> R) -> R {
ASYNC_BUILTIN_CTX.sync_scope(AsyncBuiltinCtx::new(vm), f)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Vm;
#[tokio::test]
async fn ctx_absent_outside_any_scope() {
assert!(clone_async_builtin_child_vm().is_none());
forward_child_output_to_parent("dropped");
}
#[tokio::test]
async fn scope_binds_ctx_and_captures_forwarded_output() {
let visible_inside = run_async_builtin_with(Vm::new(), async {
let present = clone_async_builtin_child_vm().is_some();
forward_child_output_to_parent("hello ");
forward_child_output_to_parent("world");
present
})
.await;
assert_eq!(visible_inside, (true, "hello world".to_string()));
assert!(clone_async_builtin_child_vm().is_none());
}
#[tokio::test]
async fn nested_scopes_restore_the_parent_context() {
run_async_builtin_with(Vm::new(), async {
assert!(clone_async_builtin_child_vm().is_some());
scope_async_builtin(Vm::new(), async {
assert!(clone_async_builtin_child_vm().is_some());
})
.await;
assert!(clone_async_builtin_child_vm().is_some());
})
.await;
assert!(clone_async_builtin_child_vm().is_none());
}
#[tokio::test]
async fn cancelled_scope_strands_nothing() {
use std::future::pending;
let never = run_async_builtin_with(Vm::new(), pending::<()>());
drop(never);
assert!(clone_async_builtin_child_vm().is_none());
}
}