Skip to content

broken compatibility with Rust async machinery #66

@rvolosatovs

Description

@rvolosatovs

Not sure if this is a in issue in wit-bindgen, wasmtime or both, but it appears that the Waker functionality does not function correctly in Rust guests when using async exports.

For example:

use futures::channel::mpsc;
use futures::join;
use futures::{SinkExt as _, StreamExt as _};

struct Component;

wasi::cli::command::export!(Component);

impl wasi::exports::cli::run::Guest for Component {
    async fn run() -> Result<(), ()> {
        let (mut tx, mut rx) = mpsc::channel(1);
        join!(
            async {
                _ = rx.next().await;
            },
            async {
                _ = tx.send(()).await;
            }
        );
        Ok(())
    }
}

with Cargo.toml:

[package]
name = "p3-test"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
futures = "0.3"
wasi = { git = "https://github.com/rvolosatovs/wasi", branch = "feat/p3" }

wasi repo is pointing to commit rvolosatovs/wasi@0792066

executed via:

cargo build --release --target wasm32-unknown-unknown && ~/.cargo/bin/wasm-tools component new --skip-validation target/wasm32-unknown-unknown/release/p3_test.wasm -o component.wasm && ~/src/github.com/bytecodealliance/wasmtime/target/debug/wasmtime run component.wasm 

Produces:

Error: failed to run main module `component.wasm`

Caused by:
    wasm trap: async-lifted export failed to produce a result

That's using Wasmtime from #60

Interestingly, causing a task allocation from the guest turns the error into a deadlock (which is how I first encountered the issue and is the reason for the title), for example:

use futures::channel::mpsc;
use futures::join;
use futures::{SinkExt as _, StreamExt as _};
use wasi::wit_stream;

struct Component;

wasi::cli::command::export!(Component);

impl wasi::exports::cli::run::Guest for Component {
    async fn run() -> Result<(), ()> {
        let (mut tx, mut rx) = mpsc::channel(1);
        let (_, stdout_rx) = wit_stream::new();
        wasi::cli::stdout::set_stdout(stdout_rx);
        join!(
            async {
                _ = rx.next().await;
            },
            async {
                _ = tx.send(()).await;
            }
        );
        Ok(())
    }
}

Generally, it appears that there are 2 things happening here:

  1. when the component's "futures unordered" set is empty, the component is unable to await async functions not provided by the runtime
  2. when the component's "futures unordered" set is non-empty, calling Waker::wake in the guest does not awake the task and so the component ends up in a deadlock state

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions