Skip to content

Commit

Permalink
Integrate wasi-keyvalue into wasmtime-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
iawia002 committed Jul 30, 2024
1 parent f5e5b29 commit 028d411
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 30 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ default = [
"wasi-threads",
"wasi-http",
"wasi-runtime-config",
"wasi-keyvalue",

# Most features of Wasmtime are enabled by default.
"wat",
Expand Down Expand Up @@ -406,6 +407,7 @@ wasi-nn = ["dep:wasmtime-wasi-nn"]
wasi-threads = ["dep:wasmtime-wasi-threads", "threads"]
wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"]
wasi-runtime-config = ["dep:wasmtime-wasi-runtime-config"]
wasi-keyvalue = ["dep:wasmtime-wasi-keyvalue", "wasmtime-wasi-keyvalue/redis"]
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
component-model = [
"wasmtime/component-model",
Expand Down
17 changes: 15 additions & 2 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ wasmtime_option_group! {
pub http: Option<bool>,
/// Enable support for WASI runtime config API (experimental)
pub runtime_config: Option<bool>,
/// Enable support for WASI key-value API (experimental)
pub keyvalue: Option<bool>,
/// Inherit environment variables and file descriptors following the
/// systemd listen fd specification (UNIX only)
pub listenfd: Option<bool>,
Expand Down Expand Up @@ -324,7 +326,18 @@ wasmtime_option_group! {
/// This option can be further overwritten with `--env` flags.
pub inherit_env: Option<bool>,
/// Pass a wasi runtime config variable to the program.
pub runtime_config_var: Vec<WasiRuntimeConfigVariable>,
pub runtime_config_var: Vec<KeyValuePair>,
/// Preset data for the In-Memory provider of WASI key-value API.
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
/// Grant access to the given Redis host for the Redis provider of WASI
/// key-value API.
pub keyvalue_redis_host: Vec<String>,
/// Sets the connection timeout parameter for the Redis provider of WASI
/// key-value API.
pub keyvalue_redis_connection_timeout: Option<Duration>,
/// Sets the response timeout parameter for the Redis provider of WASI
/// key-value API.
pub keyvalue_redis_response_timeout: Option<Duration>,
}

enum Wasi {
Expand All @@ -339,7 +352,7 @@ pub struct WasiNnGraph {
}

#[derive(Debug, Clone, PartialEq)]
pub struct WasiRuntimeConfigVariable {
pub struct KeyValuePair {
pub key: String,
pub value: String,
}
Expand Down
6 changes: 3 additions & 3 deletions crates/cli-flags/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! specifying options in a struct-like syntax where all other boilerplate about
//! option parsing is contained exclusively within this module.

use crate::{WasiNnGraph, WasiRuntimeConfigVariable};
use crate::{KeyValuePair, WasiNnGraph};
use anyhow::{bail, Result};
use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
use clap::error::{Error, ErrorKind};
Expand Down Expand Up @@ -397,12 +397,12 @@ impl WasmtimeOptionValue for WasiNnGraph {
}
}

impl WasmtimeOptionValue for WasiRuntimeConfigVariable {
impl WasmtimeOptionValue for KeyValuePair {
const VAL_HELP: &'static str = "=<name>=<val>";
fn parse(val: Option<&str>) -> Result<Self> {
let val = String::parse(val)?;
let mut parts = val.splitn(2, "=");
Ok(WasiRuntimeConfigVariable {
Ok(KeyValuePair {
key: parts.next().unwrap().to_string(),
value: match parts.next() {
Some(part) => part.into(),
Expand Down
30 changes: 30 additions & 0 deletions crates/test-programs/src/bin/cli_serve_keyvalue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use test_programs::keyvalue::wasi::keyvalue;
use test_programs::proxy;
use test_programs::wasi::http::types::{
Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
};

struct T;

proxy::export!(T);

impl proxy::exports::wasi::http::incoming_handler::Guest for T {
fn handle(_: IncomingRequest, outparam: ResponseOutparam) {
let fields = Fields::new();
let resp = OutgoingResponse::new(fields);
let body = resp.body().expect("outgoing response");

ResponseOutparam::set(outparam, Ok(resp));

let out = body.write().expect("outgoing stream");
let bucket = keyvalue::store::open("").unwrap();
let data = bucket.get("hello").unwrap().unwrap();
out.blocking_write_and_flush(&data)
.expect("writing response");

drop(out);
OutgoingBody::finish(body, None).expect("outgoing-body.finish");
}
}

fn main() {}
5 changes: 1 addition & 4 deletions crates/test-programs/src/bin/keyvalue_main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use test_programs::keyvalue::wasi::keyvalue::{atomics, batch, store};

fn main() {
let identifier = std::env::var_os("IDENTIFIER")
.unwrap()
.into_string()
.unwrap();
let identifier = std::env::var("IDENTIFIER").unwrap_or("".to_string());
let bucket = store::open(&identifier).unwrap();

if identifier != "" {
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi-keyvalue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ workspace = true
[dependencies]
anyhow = { workspace = true }
wasmtime = { workspace = true, features = ["runtime", "async", "component-model"] }
wasmtime-wasi = { workspace = true }
async-trait = { workspace = true }
url = { workspace = true }
redis = { workspace = true, optional = true, features = ["tokio-comp"] }

[dev-dependencies]
test-programs-artifacts = { workspace = true }
wasmtime-wasi = { workspace = true }
tokio = { workspace = true, features = ["macros"] }

[features]
Expand Down
26 changes: 26 additions & 0 deletions crates/wasi-keyvalue/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
wasmtime::component::bindgen!({
path: "wit",
world: "wasi:keyvalue/imports",
trappable_imports: true,
async: true,
with: {
"wasi:keyvalue/store/bucket": crate::Bucket,
},
trappable_error_type: {
"wasi:keyvalue/store/error" => crate::Error,
},
});

pub(crate) mod sync {
wasmtime::component::bindgen!({
path: "wit",
world: "wasi:keyvalue/imports",
trappable_imports: true,
with: {
"wasi:keyvalue/store/bucket": crate::Bucket,
},
trappable_error_type: {
"wasi:keyvalue/store/error" => crate::Error,
},
});
}
125 changes: 108 additions & 17 deletions crates/wasi-keyvalue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
//! let mut linker = Linker::<Ctx>::new(&engine);
//! wasmtime_wasi::add_to_linker_async(&mut linker)?;
//! // add `wasi-runtime-config` world's interfaces to the linker
//! wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| {
//! wasmtime_wasi_keyvalue::add_to_linker_async(&mut linker, |h: &mut Ctx| {
//! WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table)
//! })?;
//!
Expand All @@ -68,29 +68,17 @@

#![deny(missing_docs)]

mod bindings;
mod provider;
mod generated {
wasmtime::component::bindgen!({
path: "wit",
world: "wasi:keyvalue/imports",
trappable_imports: true,
async: true,
with: {
"wasi:keyvalue/store/bucket": crate::Bucket,
},
trappable_error_type: {
"wasi:keyvalue/store/error" => crate::Error,
},
});
}

use self::generated::wasi::keyvalue;
use self::bindings::{sync::wasi::keyvalue as keyvalue_sync, wasi::keyvalue};
use anyhow::Result;
use async_trait::async_trait;
use std::collections::HashMap;
use std::fmt::Display;
use url::Url;
use wasmtime::component::{Resource, ResourceTable, ResourceTableError};
use wasmtime_wasi::runtime::in_tokio;

#[doc(hidden)]
pub enum Error {
Expand Down Expand Up @@ -411,7 +399,12 @@ impl keyvalue::batch::Host for WasiKeyValue<'_> {
}

/// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`].
pub fn add_to_linker<T: Send>(
///
/// This function will add the `async` variant of all interfaces into the
/// `Linker` provided. By `async` this means that this function is only
/// compatible with [`Config::async_support(true)`][wasmtime::Config::async_support].
/// For embeddings with async support disabled see [`add_to_linker_sync`] instead.
pub fn add_to_linker_async<T: Send>(
l: &mut wasmtime::component::Linker<T>,
f: impl Fn(&mut T) -> WasiKeyValue<'_> + Send + Sync + Copy + 'static,
) -> Result<()> {
Expand All @@ -421,6 +414,104 @@ pub fn add_to_linker<T: Send>(
Ok(())
}

impl keyvalue_sync::store::Host for WasiKeyValue<'_> {
fn open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error> {
in_tokio(async { keyvalue::store::Host::open(self, identifier).await })
}

fn convert_error(&mut self, err: Error) -> Result<keyvalue_sync::store::Error> {
match err {
Error::NoSuchStore => Ok(keyvalue_sync::store::Error::NoSuchStore),
Error::AccessDenied => Ok(keyvalue_sync::store::Error::AccessDenied),
Error::Other(e) => Ok(keyvalue_sync::store::Error::Other(e)),
}
}
}

impl keyvalue_sync::store::HostBucket for WasiKeyValue<'_> {
fn get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error> {
in_tokio(async { keyvalue::store::HostBucket::get(self, bucket, key).await })
}

fn set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error> {
in_tokio(async { keyvalue::store::HostBucket::set(self, bucket, key, value).await })
}

fn delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error> {
in_tokio(async { keyvalue::store::HostBucket::delete(self, bucket, key).await })
}

fn exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error> {
in_tokio(async { keyvalue::store::HostBucket::exists(self, bucket, key).await })
}

fn list_keys(
&mut self,
bucket: Resource<Bucket>,
cursor: Option<u64>,
) -> Result<keyvalue_sync::store::KeyResponse, Error> {
in_tokio(async {
let resp = keyvalue::store::HostBucket::list_keys(self, bucket, cursor).await?;
Ok(keyvalue_sync::store::KeyResponse {
keys: resp.keys,
cursor: resp.cursor,
})
})
}

fn drop(&mut self, bucket: Resource<Bucket>) -> Result<()> {
keyvalue::store::HostBucket::drop(self, bucket)
}
}

impl keyvalue_sync::atomics::Host for WasiKeyValue<'_> {
fn increment(
&mut self,
bucket: Resource<Bucket>,
key: String,
delta: u64,
) -> Result<u64, Error> {
in_tokio(async { keyvalue::atomics::Host::increment(self, bucket, key, delta).await })
}
}

impl keyvalue_sync::batch::Host for WasiKeyValue<'_> {
fn get_many(
&mut self,
bucket: Resource<Bucket>,
keys: Vec<String>,
) -> Result<Vec<Option<(String, Vec<u8>)>>, Error> {
in_tokio(async { keyvalue::batch::Host::get_many(self, bucket, keys).await })
}

fn set_many(
&mut self,
bucket: Resource<Bucket>,
key_values: Vec<(String, Vec<u8>)>,
) -> Result<(), Error> {
in_tokio(async { keyvalue::batch::Host::set_many(self, bucket, key_values).await })
}

fn delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error> {
in_tokio(async { keyvalue::batch::Host::delete_many(self, bucket, keys).await })
}
}

/// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`].
///
/// This function will add the `sync` variant of all interfaces into the
/// `Linker` provided. For embeddings with async support see
/// [`add_to_linker_async`] instead.
pub fn add_to_linker_sync<T>(
l: &mut wasmtime::component::Linker<T>,
f: impl Fn(&mut T) -> WasiKeyValue<'_> + Send + Sync + Copy + 'static,
) -> Result<()> {
keyvalue_sync::store::add_to_linker_get_host(l, f)?;
keyvalue_sync::atomics::add_to_linker_get_host(l, f)?;
keyvalue_sync::batch::add_to_linker_get_host(l, f)?;
Ok(())
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi-keyvalue/src/provider/inmemory.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{generated::wasi::keyvalue::store::KeyResponse, to_other_error, Error, Host};
use crate::{bindings::wasi::keyvalue::store::KeyResponse, to_other_error, Error, Host};
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi-keyvalue/src/provider/redis.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{generated::wasi::keyvalue::store::KeyResponse, Error, Host};
use crate::{bindings::wasi::keyvalue::store::KeyResponse, Error, Host};
use anyhow::Result;
use async_trait::async_trait;
use redis::{aio::MultiplexedConnection, AsyncCommands, RedisError};
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi-keyvalue/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async fn run_wasi(path: &str, ctx: Ctx) -> Result<()> {

let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker_async(&mut linker)?;
wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| {
wasmtime_wasi_keyvalue::add_to_linker_async(&mut linker, |h: &mut Ctx| {
WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table)
})?;

Expand Down
Loading

0 comments on commit 028d411

Please sign in to comment.