Skip to content

assertion failed: state.guest_thread.is_none() #12098

@if0ne

Description

@if0ne

Hi there! I want to link wasm components at runtime. How can I do this with version 39.0.0?

Test Case

Here is the test code:

use std::{collections::HashMap, sync::Arc};

use wasmtime::{
    Config,
    component::{Linker, types::ComponentItem},
};
use wasmtime_wasi::p2::bindings::CommandPre;

const SERVICE: &[u8] = include_bytes!("../cron-service.wasm");
const COMPONENT: &[u8] = include_bytes!("../cron_component.wasm");

struct Ctx {
    id: String,
    ctx: wasmtime_wasi::WasiCtx,
    table: wasmtime_wasi::ResourceTable,
}

impl wasmtime_wasi::WasiView for Ctx {
    fn ctx(&mut self) -> wasmtime_wasi::WasiCtxView<'_> {
        wasmtime_wasi::WasiCtxView {
            ctx: &mut self.ctx,
            table: &mut self.table,
        }
    }
}

pub struct Component {
    component: wasmtime::component::Component,
    linker: Linker<Ctx>,
}

impl Component {
    pub fn new(engine: wasmtime::Engine, bytes: &[u8]) -> Self {
        let component = wasmtime::component::Component::new(&engine, bytes).unwrap();
        let mut linker: Linker<Ctx> = wasmtime::component::Linker::new(&engine);
        wasmtime_wasi::p2::add_to_linker_async(&mut linker).unwrap();
        Self { component, linker }
    }
}

#[tokio::main]
async fn main() {
    let engine = wasmtime::Engine::new(
        &Config::new()
            .async_support(true)
            .wasm_component_model_async(true),
    )
    .unwrap();

    let mut service = Component::new(engine.clone(), SERVICE);
    let mut component = Component::new(engine.clone(), COMPONENT);

    resolve_dependencies(&engine, &mut service, &[&mut component]).await;

    let pre = service.linker.instantiate_pre(&service.component).unwrap();
    let cmd_pre = CommandPre::new(pre).unwrap();

    let mut store = wasmtime::Store::new(
        &engine,
        Ctx {
            id: "TEST".to_string(),
            ctx: wasmtime_wasi::WasiCtx::builder().build(),
            table: wasmtime_wasi::ResourceTable::new(),
        },
    );

    let cmd = cmd_pre.instantiate_async(&mut store).await.unwrap();
    cmd.wasi_cli_run()
        .call_run(&mut store)
        .await
        .unwrap()
        .unwrap();
}

async fn resolve_dependencies(
    engine: &wasmtime::Engine,
    component: &mut Component,
    others: &[&mut Component],
) {
    let mut exported_interfaces = HashMap::new();
    for (idx, other) in others.iter().enumerate() {
        for (export_name, export_item) in other.component.component_type().exports(&engine) {
            if matches!(export_item, ComponentItem::ComponentInstance(_)) {
                exported_interfaces.insert(export_name.to_string(), idx);
            }
        }
    }

    let instance_cache: Arc<tokio::sync::RwLock<Option<(String, wasmtime::component::Instance)>>> =
        Arc::default();

    for (import_name, import_item) in component.component.component_type().imports(engine) {
        if let ComponentItem::ComponentInstance(import_instance) = import_item
            && let Some(exporter_idx) = exported_interfaces.get(import_name).copied()
        {
            let exporter = &others[exporter_idx];

            let (_, export_instance_idx) =
                exporter.component.get_export(None, import_name).unwrap();

            let mut linker_instance = component.linker.instance(import_name).unwrap();

            let pre = exporter
                .linker
                .instantiate_pre(&exporter.component)
                .unwrap();

            for (import_export_name, import_export_ty) in import_instance.exports(&engine) {
                if let ComponentItem::ComponentFunc(_) = import_export_ty {
                    let (_, exported_func_idx) = exporter
                        .component
                        .get_export(Some(&export_instance_idx), import_export_name)
                        .unwrap();

                    let pre = pre.clone();
                    let instance_cache = instance_cache.clone();
                    linker_instance
                        .func_new_async(import_export_name, move |mut store, _, params, results| {
                            let pre = pre.clone();
                            let instance_cache = instance_cache.clone();
                            let id = store.data().id.clone();
                            Box::new(async move {
                                if let Some((store_id, instance)) =
                                    instance_cache.read().await.clone()
                                {
                                    if store_id == id {
                                        let func = instance
                                            .get_func(&mut store, exported_func_idx)
                                            .unwrap();
                                        func.call_async(&mut store, params, results).await?;
                                        func.post_return_async(&mut store).await?;
                                        return Ok(());
                                    }
                                }

                                let new_instance = pre.instantiate_async(&mut store).await?;
                                *instance_cache.write().await = Some((id, new_instance.clone()));

                                let func = new_instance
                                    .get_func(&mut store, exported_func_idx)
                                    .unwrap();

                                func.call_async(&mut store, params, results).await.unwrap();
                                func.post_return_async(&mut store).await.unwrap();

                                Ok(())
                            })
                        })
                        .unwrap();
                }
            }
        }
    }
}

Component code:

wit_bindgen::generate!({
    world: "component",
    async: true,
});

struct Component;

impl exports::wasmcloud::example::cron::Guest for Component {
    async fn invoke() -> Result<(), String> {
        eprintln!("Hello from the cron-component!");
        Ok(())
    }
}

export!(Component);

Service code:

wit_bindgen::generate!({
    world: "service",
    async: true,
});

#[tokio::main(flavor = "current_thread")]
async fn main() {
    eprintln!("Starting cron-service with 1 second intervals...");
    loop {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        let _ = wasmcloud::example::cron::invoke().await;
    }
}

Shared wit file:

package wasmcloud:example@0.0.1;

interface cron {
    invoke: func() -> result<_, string>;
}

world service {
    import cron;
}

world component {
    export cron;
}

Steps to Reproduce

Expected Results

No panic

Actual Results

Panic:

cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/test_wt`

thread 'main' panicked at /home/pagafonov/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wasmtime-39.0.1/src/runtime/component/concurrent.rs:4913:5:
assertion failed: state.guest_thread.is_none()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Versions and Environment

tokio = { version = "1.48.0", features = ["full"] }
wasmtime = { version = "39.0.1" }
wasmtime-wasi = "39.0.1"
wit-bindgen = { version = "0.46.0", features = ["async"] }

Extra Info

In version 38, my code works.
If I turn off the component-model-async feature, this code also works.

Metadata

Metadata

Assignees

Labels

bugIncorrect behavior in the current implementation that needs fixingwasm-proposal:component-model-asyncIssues related to the WebAssembly Component Model async proposal

Type

No type

Projects

Status

Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions