Skip to content

Adjust the recursive run_concurrent check - #14302

Open
alexcrichton wants to merge 1 commit into
bytecodealliance:mainfrom
alexcrichton:fix-spin
Open

Adjust the recursive run_concurrent check#14302
alexcrichton wants to merge 1 commit into
bytecodealliance:mainfrom
alexcrichton:fix-spin

Conversation

@alexcrichton

Copy link
Copy Markdown
Member

This commit adjust the previous check_recursive_run function found in concurrent.rs to instead be a check of the now-present event_loop_running bool. This allows disparate stores to run recursively as there should be no issue with that but still requires a single store just once and never recursively.

This was discovered in Spin's update to Wasmtime 49 at spinframework/spin#3710 where delegation of an HTTP request from a p3 component (executed with run_concurrent) to a p2 component (instantiated with instantiate_async) started panicking with this recursive check in Wasmtime 49. The cause of this was the refactoring in #14146 where all instantiation now simulates the concurrent event loop where enabled for the start function. Spin executes the components in different stores, however, which is how this commit fixes that case.

This commit adjust the previous `check_recursive_run` function found in
`concurrent.rs` to instead be a check of the now-present
`event_loop_running` bool. This allows disparate stores to run
recursively as there should be no issue with that but still requires
a single store just once and never recursively.

This was discovered in Spin's update to Wasmtime 49 at
spinframework/spin#3710 where delegation of an HTTP request from a p3
component (executed with `run_concurrent`) to a p2 component
(instantiated with `instantiate_async`) started panicking with this
recursive check in Wasmtime 49. The cause of this was the refactoring
in bytecodealliance#14146 where all instantiation now simulates the concurrent event
loop where enabled for the `start` function. Spin executes the
components in different stores, however, which is how this commit fixes
that case.
@alexcrichton
alexcrichton requested a review from dicej September 8, 2026 20:09
@alexcrichton
alexcrichton requested a review from a team as a code owner September 8, 2026 20:09
@alexcrichton

Copy link
Copy Markdown
Member Author

FWIW this program:

use std::any::Any;
use wasmtime::component::{Component, InstancePre, Linker};
use wasmtime::{AsContext, Engine, Result, Store};

type Data = Option<Box<dyn Any + Send + Sync>>;

#[tokio::main]
async fn main() -> Result<()> {
    let engine = Engine::default();
    let component = Component::new(
        &engine,
        r#"
    (component
        (import "a" (func $a async))
        (core module $a
            (import "" "a" (func $a))
            (func (export "run") (call $a))
            (func (export "run2"))

            (func $f)
            (start $f)
        )
        (core func $a (canon lower (func $a)))
        (core instance $i (instantiate $a
            (with "" (instance
                (export "a" (func $a))
            ))
        ))

        (func (export "run") async (canon lift (core func $i "run")))
        (func (export "run2") (canon lift (core func $i "run2")))
    )
    "#,
    )?;

    let mut store = Store::<Data>::new(&engine, None);

    let mut linker = Linker::<Data>::new(&engine);
    linker.root().func_wrap_concurrent("a", |caller, ()| {
        Box::pin(async move {
            let (mut store, pre): (_, InstancePre<Data>) = caller.with(|caller| {
                let store = Store::<Data>::new(caller.as_context().engine(), None);
                let pre: InstancePre<Data> = caller
                    .as_context()
                    .data()
                    .as_ref()
                    .unwrap()
                    .downcast_ref::<InstancePre<Data>>()
                    .unwrap()
                    .clone();
                (store, pre)
            });
            let fut: std::pin::Pin<Box<dyn Future<Output = Result<_>> + Send + '_>> =
                Box::pin(pre.instantiate_async(&mut store));
            fut.await?;
            Ok(())
        })
    })?;

    let instance = linker.instantiate_async(&mut store, &component).await?;
    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
    *store.data_mut() = Some(Box::new(linker.instantiate_pre(&component)?));
    store
        .run_concurrent(async |store| run.call_concurrent(store, ()).await)
        .await??;

    Ok(())
}

runs on 48.0.1 but fails on 49.0.0-rc.1 which is what I was testing with and is a rough reduction of what Spin is doing.

@dicej dicej left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating and fixing this!

@alexcrichton
alexcrichton added this pull request to the merge queue Sep 8, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants