Stream: wasmtime

Topic: Wasmtime:Retaining access to caller memory after end of call


view this post on Zulip Dylan (Apr 01 2025 at 11:55):

Apologies, accidentally put this in the wrong channel:

Hi there. I'm currently writing a WASM interface using Wasmtime wherein the host has a function that will:

  1. read message bytes from the WASM module's memory
  2. spawn a future that will:
    a. action the message represented by those bytes
    b. write some bytes to the WASM module's memory in response
    c. call a WASM export to notify the module of completion

  3. return (before the future has completed)

However the problem is that writing to memory requires a context, and we can't use the caller's context as that is only valid for the lifetime of the original host function call. What should I use instead? Thanks.

view this post on Zulip bjorn3 (Apr 01 2025 at 12:21):

How are you preventing the wasm export from being called by the future while code in the wasm module is already running? You can't enter wasm instances from multiple threads.

view this post on Zulip Dylan (Apr 01 2025 at 12:23):

Yeah I hadn't thought of that. I did some more looking around and it feels like what I want is to wrap the store in a mutex, which will also guarantee single access.

view this post on Zulip Dylan (Apr 01 2025 at 12:23):

Obviously that'll deadlock where I've got access to the caller, so I've essentially done something like:

enum PluginFunctionsWasmInterface<'a, H: crate::Contract> {
    Caller {
        caller: wasmtime::Caller<'a, crate::Address<H>>,
        store: SharedStore<H>,
    },
    Store(&'a mut wasmtime::Store<crate::Address<H>>),
}

impl<'a, H: crate::Contract> wasmtime::AsContext for PluginFunctionsWasmInterface<'a, H> {
    type Data = crate::Address<H>;
    fn as_context(&self) -> wasmtime::StoreContext<'_, Self::Data> {
        match self {
            Self::Caller { caller, .. } => caller.as_context(),
            Self::Store(store) => store.as_context(),
        }
    }
}

impl<'a, H: crate::Contract> wasmtime::AsContextMut for PluginFunctionsWasmInterface<'a, H> {
    fn as_context_mut(&mut self) -> wasmtime::StoreContextMut<'_, Self::Data> {
        match self {
            Self::Caller { caller, .. } => caller.as_context_mut(),
            Self::Store(store) => store.as_context_mut(),
        }
    }
}

view this post on Zulip Alex Crichton (Apr 01 2025 at 14:29):

I don't believe Wasmtime has utilities at this time to help you here and this'll be a Rust/embedding problem you'll have to solve

view this post on Zulip Dylan (Apr 01 2025 at 14:53):

Yeah... I was secretly hoping that wasmtime didn't care/had as much knowledge of threads as WASM itself does (obviously ignoring the WASM threading proposal) but I guess this is sensible from a host perspective as well. For now I'll just deal with my accesses to the WASM module being serialised using that mutex, as that seems to compile at least and there's no obvious deadlocks I can see so long as I use the Caller when available instead of the mutex-guarded Store.

view this post on Zulip Dylan (Apr 02 2025 at 10:34):

Just figured I'd follow up and say I managed to get this working satisfyingly enough :) I did end up using the above solution of "using caller if available, and if not wait until we acquire a mutex to the Store". I was keen not to have a dedicated OS thread at all times for the plugin (mainly to anticipate having lot of plugins), which is why I wanted to get this working so I could reuse the scheduling done by tokio. As a bonus I also made it so the plugin could spawn futures on the host runtime as well, so lots of fun shenanigans here. Fighting internally to get this open-sourced at some point.


Last updated: Apr 09 2025 at 14:04 UTC