Path: blob/main/tests/all/component_model/async.rs
1692 views
use crate::async_functions::{PollOnce, execute_across_threads};1use anyhow::Result;2use wasmtime::component::*;3use wasmtime::{Engine, Store, StoreContextMut, Trap};4use wasmtime_component_util::REALLOC_AND_FREE;56/// This is super::func::thunks, except with an async store.7#[tokio::test]8#[cfg_attr(miri, ignore)]9async fn smoke() -> Result<()> {10let component = r#"11(component12(core module $m13(func (export "thunk"))14(func (export "thunk-trap") unreachable)15)16(core instance $i (instantiate $m))17(func (export "thunk")18(canon lift (core func $i "thunk"))19)20(func (export "thunk-trap")21(canon lift (core func $i "thunk-trap"))22)23)24"#;2526let engine = super::async_engine();27let component = Component::new(&engine, component)?;28let mut store = Store::new(&engine, ());29let instance = Linker::new(&engine)30.instantiate_async(&mut store, &component)31.await?;3233let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?;3435thunk.call_async(&mut store, ()).await?;36thunk.post_return_async(&mut store).await?;3738let err = instance39.get_typed_func::<(), ()>(&mut store, "thunk-trap")?40.call_async(&mut store, ())41.await42.unwrap_err();43assert_eq!(err.downcast::<Trap>()?, Trap::UnreachableCodeReached);4445Ok(())46}4748/// Handle an import function, created using component::Linker::func_wrap_async.49#[tokio::test]50#[cfg_attr(miri, ignore)]51async fn smoke_func_wrap() -> Result<()> {52let component = r#"53(component54(type $f (func))55(import "i" (func $f))5657(core module $m58(import "imports" "i" (func $i))59(func (export "thunk") call $i)60)6162(core func $f (canon lower (func $f)))63(core instance $i (instantiate $m64(with "imports" (instance65(export "i" (func $f))66))67))68(func (export "thunk")69(canon lift (core func $i "thunk"))70)71)72"#;7374let engine = super::async_engine();75let component = Component::new(&engine, component)?;76let mut store = Store::new(&engine, ());77let mut linker = Linker::new(&engine);78let mut root = linker.root();79root.func_wrap_async("i", |_: StoreContextMut<()>, _: ()| {80Box::new(async { Ok(()) })81})?;8283let instance = linker.instantiate_async(&mut store, &component).await?;8485let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?;8687thunk.call_async(&mut store, ()).await?;88thunk.post_return_async(&mut store).await?;8990Ok(())91}9293// This test stresses TLS management in combination with the `realloc` option94// for imported functions. This will create an async computation which invokes a95// component that invokes an imported function. The imported function returns a96// list which will require invoking malloc.97//98// As an added stressor all polls are sprinkled across threads through99// `execute_across_threads`. Yields are injected liberally by configuring 1100// fuel consumption to trigger a yield.101//102// Overall a yield should happen during malloc which should be an "interesting103// situation" with respect to the runtime.104#[tokio::test]105#[cfg_attr(miri, ignore)]106async fn resume_separate_thread() -> Result<()> {107let mut config = wasmtime_test_util::component::config();108config.async_support(true);109config.consume_fuel(true);110let engine = Engine::new(&config)?;111let component = format!(112r#"113(component114(import "yield" (func $yield (result (list u8))))115(core module $libc116(memory (export "memory") 1)117{REALLOC_AND_FREE}118)119(core instance $libc (instantiate $libc))120121(core func $yield122(canon lower123(func $yield)124(memory $libc "memory")125(realloc (func $libc "realloc"))126)127)128129(core module $m130(import "" "yield" (func $yield (param i32)))131(import "libc" "memory" (memory 0))132(func $start133i32.const 8134call $yield135)136(start $start)137)138(core instance (instantiate $m139(with "" (instance (export "yield" (func $yield))))140(with "libc" (instance $libc))141))142)143"#144);145let component = Component::new(&engine, component)?;146let mut linker = Linker::new(&engine);147linker148.root()149.func_wrap_async("yield", |_: StoreContextMut<()>, _: ()| {150Box::new(async {151tokio::task::yield_now().await;152Ok((vec![1u8, 2u8],))153})154})?;155156execute_across_threads(async move {157let mut store = Store::new(&engine, ());158store.set_fuel(u64::MAX).unwrap();159store.fuel_async_yield_interval(Some(1)).unwrap();160linker.instantiate_async(&mut store, &component).await?;161Ok::<_, anyhow::Error>(())162})163.await?;164Ok(())165}166167// This test is intended to stress TLS management in the component model around168// the management of the `realloc` function. This creates an async computation169// representing the execution of a component model function where entry into the170// component uses `realloc` and then the component runs. This async computation171// is then polled iteratively with another "wasm activation" (in this case a172// core wasm function) on the stack. The poll-per-call should work and nothing173// should in theory have problems here.174//175// As an added stressor all polls are sprinkled across threads through176// `execute_across_threads`. Yields are injected liberally by configuring 1177// fuel consumption to trigger a yield.178//179// Overall a yield should happen during malloc which should be an "interesting180// situation" with respect to the runtime.181#[tokio::test]182#[cfg_attr(miri, ignore)]183async fn poll_through_wasm_activation() -> Result<()> {184let mut config = wasmtime_test_util::component::config();185config.async_support(true);186config.consume_fuel(true);187let engine = Engine::new(&config)?;188let component = format!(189r#"190(component191(core module $m192{REALLOC_AND_FREE}193(memory (export "memory") 1)194(func (export "run") (param i32 i32)195)196)197(core instance $i (instantiate $m))198(func (export "run") (param "x" (list u8))199(canon lift (core func $i "run")200(memory $i "memory")201(realloc (func $i "realloc"))))202)203"#204);205let component = Component::new(&engine, component)?;206let linker = Linker::new(&engine);207208let invoke_component = {209let engine = engine.clone();210async move {211let mut store = Store::new(&engine, ());212store.set_fuel(u64::MAX).unwrap();213store.fuel_async_yield_interval(Some(1)).unwrap();214let instance = linker.instantiate_async(&mut store, &component).await?;215let func = instance.get_typed_func::<(Vec<u8>,), ()>(&mut store, "run")?;216func.call_async(&mut store, (vec![1, 2, 3],)).await?;217Ok::<_, anyhow::Error>(())218}219};220221execute_across_threads(async move {222let mut store = Store::new(&engine, Some(Box::pin(invoke_component)));223let poll_once = wasmtime::Func::wrap_async(&mut store, |mut cx, _: ()| {224let invoke_component = cx.data_mut().take().unwrap();225Box::new(async move {226match PollOnce::new(invoke_component).await {227Ok(result) => {228result?;229Ok(1)230}231Err(future) => {232*cx.data_mut() = Some(future);233Ok(0)234}235}236})237});238let poll_once = poll_once.typed::<(), i32>(&mut store)?;239while poll_once.call_async(&mut store, ()).await? != 1 {240// loop around to call again241}242Ok::<_, anyhow::Error>(())243})244.await?;245Ok(())246}247248/// Test async drop method for host resources.249#[tokio::test]250#[cfg_attr(miri, ignore)]251async fn drop_resource_async() -> Result<()> {252use std::sync::Arc;253use std::sync::Mutex;254255let engine = super::async_engine();256let c = Component::new(257&engine,258r#"259(component260(import "t" (type $t (sub resource)))261262(core func $drop (canon resource.drop $t))263264(core module $m265(import "" "drop" (func $drop (param i32)))266(func (export "f") (param i32)267(call $drop (local.get 0))268)269)270(core instance $i (instantiate $m271(with "" (instance272(export "drop" (func $drop))273))274))275276(func (export "f") (param "x" (own $t))277(canon lift (core func $i "f")))278)279"#,280)?;281282struct MyType;283284let mut store = Store::new(&engine, ());285let mut linker = Linker::new(&engine);286287let drop_status = Arc::new(Mutex::new("not dropped"));288let ds = drop_status.clone();289290linker291.root()292.resource_async("t", ResourceType::host::<MyType>(), move |_, _| {293let ds = ds.clone();294Box::new(async move {295*ds.lock().unwrap() = "before yield";296tokio::task::yield_now().await;297*ds.lock().unwrap() = "after yield";298Ok(())299})300})?;301let i = linker.instantiate_async(&mut store, &c).await?;302let f = i.get_typed_func::<(Resource<MyType>,), ()>(&mut store, "f")?;303304execute_across_threads(async move {305let resource = Resource::new_own(100);306f.call_async(&mut store, (resource,)).await?;307f.post_return_async(&mut store).await?;308Ok::<_, anyhow::Error>(())309})310.await?;311312assert_eq!("after yield", *drop_status.lock().unwrap());313314Ok(())315}316317318