Skip to content

Update command trigger to WASI P3#87

Open
itowlson wants to merge 3 commits intospinframework:mainfrom
itowlson:p3
Open

Update command trigger to WASI P3#87
itowlson wants to merge 3 commits intospinframework:mainfrom
itowlson:p3

Conversation

@itowlson
Copy link
Copy Markdown
Contributor

@itowlson itowlson commented Apr 13, 2026

Once again I'm afraid I need to rope in the WASI experts...

The hello CLI sample works fine, but I am having trouble with the async one, which does some outbound HTTP. I have this compiling, but when I run it, it fails with:

Error: component imports instance `wasi:http/types@0.3.0-rc-2026-03-15`, but a matching implementation was not found in the linker

Caused by:
    0: instance export `fields` has the wrong type
    1: resource implementation is missing

The host is linking to Spin main (for the WASI implementation), and the guest is linking to Rust SDK main (for the HTTP client implementation). We have these working together elsewhere, so I am not sure why they are mismatching here. Is it because I'm using wasmtime_wasi::p3::bindings::Command::new to access the instance entry point, and that has its own copy of the HTTP bindings or something? That doesn't feel right...

So... sorry to do this to you again, but could you point me at what I have wrong please? Thanks!

@itowlson itowlson requested review from dicej and fibonacci1729 April 13, 2026 03:46
Copy link
Copy Markdown

@dicej dicej left a comment

Choose a reason for hiding this comment

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

Error: component imports instance wasi:http/types@0.3.0-rc-2026-03-15, but a matching implementation was not found in the linker

Caused by:
0: instance export fields has the wrong type
1: resource implementation is missing

This indicates that neither wasmtime_wasi_http::p3::add_to_linker nor any of the more specific add_to_linker functions provided by that crate have been called for the component's linker. Normally, that's spin_factor_outbound_http's job.

I always get lost in the layers of dependency injection in Spin factors, so I don't know offhand why the outbound HTTP factor might not have been injected here, nor why it was injected fine in the p2-based version of this trigger, but that's my guess as to what's happening.

Comment thread examples/async/src/main.rs Outdated
let res: Response = send(req).await.unwrap();
println!("Your IP is: {}", String::from_utf8_lossy(res.body()));
});
#[tokio::main(flavor = "current_thread")]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm surprised to see this here; neither Tokio nor the Rust compiler support WASIp3 yet, and thus they don't know how transform an async fn main declaration into the wasi:cli/run#run export that WASIp3 requires.

The only way I'm aware of currently to export wasi:cli/run#run is to use the wit-bindgen-generated bindings directly (or write the bindings by hand, if you're feeling ambitious).

Copy link
Copy Markdown
Contributor Author

@itowlson itowlson Apr 13, 2026

Choose a reason for hiding this comment

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

Hah, then presumably it doesn't work and it's just being disguised by the HTTP import failure happening first!

If there's no idiomatic way to write an async main, is there a way to call async APIs from sync main? I don't think WIT spawn works, and the usual Tokio block-on methods will presumably rely on the Tokio scheduler...?

ETA: no, because sync main becomes a sync export, and calling async APIs from a sync entry point is FORBIDDEN

so... should we hold off on moving Command to async?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We could implement our own e.g. #[spin::main] macro which transforms async main(){...} into an export of wasi:cli/run, analogous to what we do for HTTP. Or embrace the non-idiomatic-but-servicable approach of using the wit-bindgen-generated bindings directly for the export (and still use the sdk for the imports). Or hold off updating this trigger. All sound reasonable to me.

Copy link
Copy Markdown
Contributor

@fibonacci1729 fibonacci1729 Apr 14, 2026

Choose a reason for hiding this comment

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

I'd be fine to just implement wasi:cli/command directly. E.g.

// lib.rs

use spin_sdk::{wasip3, http::{EmptyBody, body::IncomingBodyExt, Request, send}};

wasip3::cli::command::export!(Job);

struct Job;

impl wasip3::exports::cli::run::Guest for Job {
    async fn run() -> Result<(), ()> {
        match do_job().await {
            Ok(()) => Ok(()),
            Err(e) => {
                eprintln!("{e}");
                Err(())
            }
        }
    }
}

async fn do_job() -> anyhow::Result<()> {
    let request = Request::get("https://myip.fermyon.app").body(EmptyBody::new())?;
    let response = send(request).await?;
    let response_bytes = response.into_body().bytes().await?;
    let response_text = String::from_utf8_lossy(&response_bytes.as_ref());
    println!("Your IP is: {}", response_text);
    Ok(())
}

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.

This would require changing main.rs -> lib.rs and adding [lib] crate-type = ["cdylib"] to Cargo.toml.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That would make me sad, because we lose the idiomatic fn main() of Rust commands. I accept that "it works" is more important than "Ivan is sad" though!

Copy link
Copy Markdown
Contributor

@fibonacci1729 fibonacci1729 Apr 14, 2026

Choose a reason for hiding this comment

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

I think it's the best we can do until presumably wasi p3 is wired up to std? a la a wasm32-wasip3 target,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated! Thanks @dicej and @fibonacci1729!

Signed-off-by: itowlson <ivan.towlson@fermyon.com>
@itowlson itowlson marked this pull request as ready for review April 14, 2026 21:25
Copy link
Copy Markdown
Contributor

@fibonacci1729 fibonacci1729 left a comment

Choose a reason for hiding this comment

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

Looks good. Can you check the READMEs to ensure theyre up to date?

Comment thread examples/async/Cargo.toml Outdated
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
@itowlson itowlson marked this pull request as draft April 14, 2026 21:40
@itowlson
Copy link
Copy Markdown
Contributor Author

Ugh sorry

@itowlson
Copy link
Copy Markdown
Contributor Author

Okay I think for this one we need it (at least for now) to support both P2 and P3, because the way I have done it, Rust commands (fn main()) don't work (because they export the P2 form). And I kinda think they should.

Signed-off-by: itowlson <ivan.towlson@fermyon.com>
@itowlson itowlson marked this pull request as ready for review April 14, 2026 21:53
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.

3 participants