dicej / isyswasfa

I sync, you sync, we all sync for async!
39 stars 0 forks source link

wasm trap: cannot enter component instance #4

Open redoC-A2k opened 4 months ago

redoC-A2k commented 4 months ago

I modified the round trip example like follows but I am getting error - wasm trap: cannot enter component instance

  1. I added below two static variables -

    // test/src/lib.rs
    static mut ROUND_TRIP: Option<Arc<round_trip::RoundTrip>> = None;
    // pub static mut STORE: Option<Mutex<Store<Host>>> = None;
    static mut STORE: Option<Store<Ctx>> = None;    
  2. Modified async fn round_trip

    // test/src/lib.rs
    async fn round_trip(component_bytes: &[u8]) -> Result<()> {
        let mut config = Config::new();
        config.wasm_component_model(true);
        config.async_support(true);
    
        let engine = Engine::new(&config)?;
    
        let component = Component::new(&engine, component_bytes)?;
    
        let mut linker = Linker::new(&engine);
    
        command::add_to_linker(&mut linker)?;
        isyswasfa_host::add_to_linker(&mut linker)?;
    
        round_trip::RoundTrip::add_to_linker(&mut linker, |ctx| ctx)?;
    
        let mut store = Store::new(
            &engine,
            Ctx {
                wasi: WasiCtxBuilder::new().inherit_stdio().build(),
                isyswasfa: IsyswasfaCtx::new(),
                send_request: None,
            },
        );
    
        let (round_trip, instance) =
            round_trip::RoundTrip::instantiate_async(&mut store, &component, &linker).await?;
    
        unsafe {
            ROUND_TRIP = Some(Arc::new(round_trip));
            STORE = Some(store);
        }
        let mut store;
        let round_trip;
        unsafe {
            store = STORE.as_mut().unwrap();
            round_trip = ROUND_TRIP.clone().unwrap();
        }
    
        isyswasfa_host::load_poll_funcs(&mut store, component_bytes, &instance)?;
        let value;
        {
            value = round_trip
                .component_test_baz()
                .call_foo(&mut store, "hello, world!");
        }
    
        println!("test - {}", &value.await?);
    
        // assert_eq!(
        //     "hello, world! - entered guest - entered host - exited host - exited guest",
        //     &value
        // );
    
        Ok(())
    }
  3. Modified wit file for round_trip

    
    interface baz {
    foo: func(s: string) -> string;
    }

interface demo { boo: func(s: string) -> string; }

world round-trip { import wasi:clocks/monotonic-clock@0.2.0; import baz; export demo; export baz; }

4. Modified foo implementation for foo in host
```rust
// test/src/lib.rs
    impl round_trip::component::test::baz::Host for Ctx {
        fn foo(
            &mut self,
            s: String,
        ) -> wasmtime::Result<
            impl Future<Output = impl FnOnce(&mut Self) -> wasmtime::Result<String> + 'static>
                + Send
                + 'static,
        > {
            Ok(async move {
                println!("In closure ");
                let mut store;
                let round_trip;
                unsafe {
                    store = STORE.as_mut().unwrap();
                    round_trip = ROUND_TRIP.clone().unwrap();
                }
                let value = round_trip
                    .component_test_demo()
                    .call_boo(&mut store, "In host from guest")
                    .await
                    .expect("Unable to await ");
                move |_: &mut Self| {
                    Ok(format!(
                        "{s} - entered host - exited host with value {value}"
                    ))
                }
            })
        }
  1. Modified guest implementation for wit file

    
    // test/rust-cases/round-trip/src/lib.rs
    #[async_trait(?Send)]
    impl Baz for Component {
    async fn foo(s: String) -> String {
        println!("called subscribe duration");
        monotonic_clock::subscribe_duration(1000_000_000).await;
    
        format!(
            "{} - exited guest",
            baz::foo(&format!("{s} - entered guest")).await
        )
    }
    }

[async_trait(?Send)]

impl demo::Guest for Component { async fn boo(s : String) -> String { println!("Guest run now"); format!("{s} - exited guest again") } }


But while running above I get error - 
```bash
called subscribe duration
test test::round_trip_rust ... FAILED

successes:

successes:

failures:

---- test::round_trip_rust stdout ----
In closure 
thread 'test::round_trip_rust' panicked at test/src/lib.rs:255:22:
Unable to await : wasm trap: cannot enter component instance
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I Hope you have understood what I want to achieve . Basically what I want is to again enter the component (which is in execution) and run some code then return the result of that to the first function .

redoC-A2k commented 4 months ago

You can also have a look here - https://github.com/bytecodealliance/wasmtime/issues/8670#issuecomment-2124871948 That's my actual use case .

dicej commented 4 months ago

Thanks for reporting this, @redoC-A2k. Yes, I understand what you're trying to do -- thanks for the thorough explanation.

The component model currently does not allow calling a guest export from a host function called by the guest, and isyswasfa does not relax that restriction, unfortunately. Also, isyswasfa does not currently support the host calling more than one guest export concurrently, even at the top level of the program; although that's just a limitation of the current implementation rather than a fundamental restriction.

I think we could support this use case if we were to update the isyswasfa implementation to allow multiple concurrent calls to guest exports. I don't think that would be terribly difficult, but I haven't tried to tackle it yet because it's challenging to do in an ergonomic way. Currently, the generated host bindings for functions return a Future which owns the an exclusive reference to the Wasmtime Store. We'd need to change that to something that only requires an exclusive reference when polled, i.e. something similar to a Future, but not the same thing.

Anyway, I'm sure we could find some way to make that work, and if we did, we could support your use case by allowing the host embedder to make a concurrent call to a guest export such that the actual call into the guest is deferred to the top-level event loop. That would allow you to make the call from the host import without violating the reentrancy rule, but still allow the guest to "wait for itself".

That said, I don't expect I'll implement all that in isyswasfa since I've shifted focus to upstreaming component model async support to Wasmtime. I'll keep this use case in mind for that upstream work, though.