Skip to main content

Module _7_async

Module _7_async 

Source
Available on crate feature component-model and crate feature runtime and (docsrs) only.
Expand description

Example of generating async bindings for imported resources in a world.

Notable differences from _4_imported_resources are:

  • async functions are used
  • enabled async in bindgen! macro
  • flags configured for individual resource functions
  • *_async entry points are required, such as instantiate_async, awaiting call_* functions, and ResourceAny::resource_drop_async

See wasi_async_example for async function calls on a host.

use wasmtime::{
    Config, Engine, Result, Store,
    component::{bindgen, Component, HasSelf, Linker, ResourceTable},
};

bindgen!({
    inline: r#"
package example:resource-async;

interface logging {
    enum level {
        debug,
        info,
        warn,
        error,
    }

    resource logger {
        constructor(max-level: level);
        get-max-level: func() -> level;
        set-max-level: func(level: level);
        /// May block if the host's log buffer is full (i.e. backpressure).
        log: async func(level: level, msg: string);
    }
}

world app {
    import logging;
    /// Because `logging.logger.log` is async, `run` must also be async; otherwise,
    /// a trap will occur at runtime if `run` calls `log` and it blocks.
    export run: async func();
}
"#,

    imports: {

        // This is the notation that's used to flag individual methods:
        //
        // "example:resource-async/logging.[constructor]logger": trappable,
        // "example:resource-async/logging.[method]logger.get-max-level": trappable,
        // "example:resource-async/logging.[method]logger.set-max-level": trappable,
        // "example:resource-async/logging.[method]logger.log": trappable,
        //
        // Note that if one resource method specifies a flag, the remaining resource methods
        // are no longer covered by e.g. `default: trappable`, and must be flagged
        // individually.
        default: trappable,
    },

    with: {
         // Specify that our host resource is going to point to the `MyLogger`
        // which is defined just below this macro.
        "example:resource-async/logging.logger": MyLogger,
    },
});

/// A sample host-defined type which contains arbitrary host-defined data.
///
/// In this case this is relatively simple but there's no restrictions on what
/// this type can hold other than that it must be `'static + Send`.
pub struct MyLogger {
    pub max_level: example::resource_async::logging::Level,
}

use self::example::resource_async::logging::{self as bindings, Level};

#[derive(Default)]
struct State {
    // Manages the mapping of `MyLogger` structures to `Resource<MyLogger>`.
    table: ResourceTable,
}

// This separate `HostLogger` trait serves to act as a namespace for just
// the `logger`-related resource methods.
impl bindings::HostLogger for State {
    fn drop(
        &mut self,
        rep: wasmtime::component::Resource<bindings::Logger>,
    ) -> wasmtime::Result<()> {
        self.table.delete(rep)?;
        // ... custom destruction logic here if necessary, otherwise
        // a `Drop for MyLogger` would also work.
        Ok(())
    }

    fn get_max_level(
        &mut self,
        self_: wasmtime::component::Resource<MyLogger>,
    ) -> wasmtime::Result<bindings::Level> {
        let logger = self.table.get(&self_)?;
        Ok(logger.max_level)
    }

    // A `constructor` in WIT maps to a `new` function in Rust.
    fn new(
        &mut self,
        max_level: bindings::Level,
    ) -> wasmtime::Result<wasmtime::component::Resource<bindings::Logger>> {
        let id = self.table.push(MyLogger { max_level })?;
        Ok(id)
    }

    fn set_max_level(
        &mut self,
        self_: wasmtime::component::Resource<MyLogger>,
        level: bindings::Level,
    ) -> wasmtime::Result<()> {
        let logger = self.table.get_mut(&self_)?;
        logger.max_level = level;
        Ok(())
    }
}

impl<T> bindings::HostLoggerWithStore<T> for HasSelf<State> {
    async fn log(
        accessor: &wasmtime::component::Accessor<T, Self>,
        self_: wasmtime::component::Resource<MyLogger>,
        level: Level,
        msg: String,
    ) -> wasmtime::Result<()> {
        // Do something that blocks
        tokio::time::sleep(std::time::Duration::from_secs(5)).await;

        accessor.with(|mut access| {
            let state = access.get();
            let logger = state.table.get(&self_)?;
            if (level as u32) <= (logger.max_level as u32) {
                println!("{msg}");
            }
            Ok(())
        })
    }
}

// There are no free-functions on `interface logging`, so this is an empty
// impl that satisfies the trait bounds required by the linker.
impl bindings::Host for State {}

#[tokio::main]
async fn main() -> Result<()> {
    let mut config = Config::new();
    config.wasm_component_model_async(true);

    let engine = Engine::new(&config)?;
    let component = Component::from_file(&engine, "/path/to/component")?;
    let mut store = Store::new(&engine, State::default());

    let mut linker = Linker::<State>::new(&engine);
    bindings::add_to_linker::<_, HasSelf<State>>(&mut linker, |state| state)?;

    // If you try to use the `App::instantiate` method, you'll get a runtime error that
    // looks something like:
    // "Error: store configuration requires that `*_async` functions are used instead"
    let bindings = App::instantiate_async(&mut store, &component, &linker).await?;
    let bindings = crate::App::instantiate_async(&mut store, &component, &linker).await?;
    store
      .run_concurrent(async |accessor| bindings.call_run(&accessor).await)
      .await??;
    Ok(())
}

Modules§

example

Structs§

App
Auto-generated bindings for an instance a component which implements the world app.
AppIndices
Auto-generated bindings for index of the exports of app.
AppPre
Auto-generated bindings for a pre-instantiated version of a component which implements the world app.
MyLogger
A sample host-defined type which contains arbitrary host-defined data.