xpepermint opened issue #3638:
It's difficult to understand the whole codebase so I'd better ask experts here to enlighten me. If I understand all these
async
logic provided bywasmtime
, exported WASM functions actually work as async functions when async support is enabled.So let's see an example (host and module pseudo):
// host #[tokio::main(flavor = "current_thread")] pub async fn main() { let mut config = Config::new(); config.async_support(true); let engine = Engine::new(&config).unwrap(); let module = Module::from_file(&engine, "./target/wasm32-wasi/xxx.wasm").unwrap(); ... let mut linker = Linker::new(&engine); linker.func_wrap1_async("host", "process", |_caller, x: i32| Box::new(async move { sleep(Duration::from_millis(5000)).await; Ok(x * 100) })).unwrap(); wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap(); ... for _ in 0..10 { tokio::spawn(async move { ... let mut store = Store::new(&engine, wasi); let instance = linker.instantiate_async(&mut store, &module).await.unwrap(); let run = instance_echo.get_typed_func::<(), (), _>(&mut store, "run").unwrap(); alloc.call_async(&mut store, ()).await.unwrap(); localasync().await; println!("Done"); }); } } async fn localasync() { ... }
// module #[link(wasm_import_module = "host")] extern "C" { #[link_name = "process"] fn process(a: i32) -> i32; } #[no_mangle] extern "C" fn run() { process(1); // can this be threated as safe/true async? process(2); // can this be threated as safe/true async? println!("Done"); }
Will
run
andprocess
be spawned in a dedicated thread where they will block or will the callable sequence of host functions insiderun
be somehow magically converted to "safe" non-blocking async sequence? I'd like to understand this in-depth so I'd appreciate someone explaining the behind-the-scene execution flow.
bjorn3 commented on issue #3638:
As far as I know it runs the wasm on the current thread, but on a different stack. When the wasm calls into an async function for which polling results in
Poll::Pending
, it switched back to the original thread and then returnsPoll::Pending
from the poll method of the original call into the wasm code,
xpepermint commented on issue #3638:
@bjorn3 that's how I understand it as well. I'm always afraid of what I don't see/understand thus I"d just like to make sure this approach is not blocking. So it shouldn't block, no meter what you do there, right? I wonder how this separate stack execution works.
xpepermint edited a comment on issue #3638:
@bjorn3 that's how I understand it as well. I'm always afraid of what I don't see/understand thus I"d just like to make sure this approach is not blocking. So it shouldn't block, no meter what you do there, right? I wonder how this separate stack execution works. Isn't there a stack per process but you can have multiple threads or is it that you have main and sub-thread or smth?
xpepermint edited a comment on issue #3638:
@bjorn3 that's how I understand it as well. I'm always afraid of what I don't see/understand thus I"d just like to make sure this approach is not blocking. So it shouldn't block, no meter what you do there, right? I wonder how this separate stack execution works. Isn't there a stack per process but you can have multiple threads or is it that you have main and sub-thread which is referred to as a "separate stack" or smth?
alexcrichton commented on issue #3638:
Wasmtime doesn't spawn any tasks or threads internally, all async happens in the original async task which wasmtime was invoked on. As a practical detail-specific implementation the WebAssembly must be able to be suspended because the host function may not be ready when invoked (as is the case in your sleep example). The Wasm itself uses stack switching to handle that but you're insulated from that in the sense that it's handled by Wasmtime and there's nothing you need to do about it.
Another way to think about this is that the
poll
function for the future returned fromcall_async
will execute WebAssembly internally up to the point that a host future needs to block and returnsNotReady
, then thepoll
function propagates that signal.
xpepermint commented on issue #3638:
@alexcrichton thank you, that's what I was looking for. So basically wasm implementation is smart enough, won't block threads, and will rather switch between stacks. I also realized that the so-called "stack switching" relates to underlying Crainlift and not OS (e.g Linux).
xpepermint closed issue #3638:
It's difficult to understand the whole codebase so I'd better ask experts here to enlighten me. If I understand all these
async
logic provided bywasmtime
, exported WASM functions actually work as async functions when async support is enabled.So let's see an example (host and module pseudo):
// host #[tokio::main(flavor = "current_thread")] pub async fn main() { let mut config = Config::new(); config.async_support(true); let engine = Engine::new(&config).unwrap(); let module = Module::from_file(&engine, "./target/wasm32-wasi/xxx.wasm").unwrap(); ... let mut linker = Linker::new(&engine); linker.func_wrap1_async("host", "process", |_caller, x: i32| Box::new(async move { sleep(Duration::from_millis(5000)).await; Ok(x * 100) })).unwrap(); wasmtime_wasi::add_to_linker(&mut linker, |s| s).unwrap(); ... for _ in 0..10 { tokio::spawn(async move { ... let mut store = Store::new(&engine, wasi); let instance = linker.instantiate_async(&mut store, &module).await.unwrap(); let run = instance_echo.get_typed_func::<(), (), _>(&mut store, "run").unwrap(); alloc.call_async(&mut store, ()).await.unwrap(); localasync().await; println!("Done"); }); } } async fn localasync() { ... }
// module #[link(wasm_import_module = "host")] extern "C" { #[link_name = "process"] fn process(a: i32) -> i32; } #[no_mangle] extern "C" fn run() { process(1); // can this be threated as safe/true async? process(2); // can this be threated as safe/true async? println!("Done"); }
Will
run
andprocess
be spawned in a dedicated thread where they will block or will the callable sequence of host functions insiderun
be somehow magically converted to "safe" non-blocking async sequence? I'd like to understand this in-depth so I'd appreciate someone explaining the behind-the-scene execution flow.
Last updated: Dec 23 2024 at 12:05 UTC