Skip to content

Commit 12fc764

Browse files
authored
Integrate wasi-keyvalue into wasmtime-cli (#9032)
* Integrate wasi-keyvalue into wasmtime-cli * fix miri check
1 parent f5e5b29 commit 12fc764

File tree

14 files changed

+334
-32
lines changed

14 files changed

+334
-32
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ default = [
360360
"wasi-threads",
361361
"wasi-http",
362362
"wasi-runtime-config",
363+
"wasi-keyvalue",
363364

364365
# Most features of Wasmtime are enabled by default.
365366
"wat",
@@ -406,6 +407,7 @@ wasi-nn = ["dep:wasmtime-wasi-nn"]
406407
wasi-threads = ["dep:wasmtime-wasi-threads", "threads"]
407408
wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper"]
408409
wasi-runtime-config = ["dep:wasmtime-wasi-runtime-config"]
410+
wasi-keyvalue = ["dep:wasmtime-wasi-keyvalue", "wasmtime-wasi-keyvalue/redis"]
409411
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
410412
component-model = [
411413
"wasmtime/component-model",

crates/cli-flags/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ wasmtime_option_group! {
286286
pub http: Option<bool>,
287287
/// Enable support for WASI runtime config API (experimental)
288288
pub runtime_config: Option<bool>,
289+
/// Enable support for WASI key-value API (experimental)
290+
pub keyvalue: Option<bool>,
289291
/// Inherit environment variables and file descriptors following the
290292
/// systemd listen fd specification (UNIX only)
291293
pub listenfd: Option<bool>,
@@ -324,7 +326,18 @@ wasmtime_option_group! {
324326
/// This option can be further overwritten with `--env` flags.
325327
pub inherit_env: Option<bool>,
326328
/// Pass a wasi runtime config variable to the program.
327-
pub runtime_config_var: Vec<WasiRuntimeConfigVariable>,
329+
pub runtime_config_var: Vec<KeyValuePair>,
330+
/// Preset data for the In-Memory provider of WASI key-value API.
331+
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
332+
/// Grant access to the given Redis host for the Redis provider of WASI
333+
/// key-value API.
334+
pub keyvalue_redis_host: Vec<String>,
335+
/// Sets the connection timeout parameter for the Redis provider of WASI
336+
/// key-value API.
337+
pub keyvalue_redis_connection_timeout: Option<Duration>,
338+
/// Sets the response timeout parameter for the Redis provider of WASI
339+
/// key-value API.
340+
pub keyvalue_redis_response_timeout: Option<Duration>,
328341
}
329342

330343
enum Wasi {
@@ -339,7 +352,7 @@ pub struct WasiNnGraph {
339352
}
340353

341354
#[derive(Debug, Clone, PartialEq)]
342-
pub struct WasiRuntimeConfigVariable {
355+
pub struct KeyValuePair {
343356
pub key: String,
344357
pub value: String,
345358
}

crates/cli-flags/src/opt.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! specifying options in a struct-like syntax where all other boilerplate about
66
//! option parsing is contained exclusively within this module.
77
8-
use crate::{WasiNnGraph, WasiRuntimeConfigVariable};
8+
use crate::{KeyValuePair, WasiNnGraph};
99
use anyhow::{bail, Result};
1010
use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
1111
use clap::error::{Error, ErrorKind};
@@ -397,12 +397,12 @@ impl WasmtimeOptionValue for WasiNnGraph {
397397
}
398398
}
399399

400-
impl WasmtimeOptionValue for WasiRuntimeConfigVariable {
400+
impl WasmtimeOptionValue for KeyValuePair {
401401
const VAL_HELP: &'static str = "=<name>=<val>";
402402
fn parse(val: Option<&str>) -> Result<Self> {
403403
let val = String::parse(val)?;
404404
let mut parts = val.splitn(2, "=");
405-
Ok(WasiRuntimeConfigVariable {
405+
Ok(KeyValuePair {
406406
key: parts.next().unwrap().to_string(),
407407
value: match parts.next() {
408408
Some(part) => part.into(),
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use test_programs::keyvalue::wasi::keyvalue;
2+
use test_programs::proxy;
3+
use test_programs::wasi::http::types::{
4+
Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
5+
};
6+
7+
struct T;
8+
9+
proxy::export!(T);
10+
11+
impl proxy::exports::wasi::http::incoming_handler::Guest for T {
12+
fn handle(_: IncomingRequest, outparam: ResponseOutparam) {
13+
let fields = Fields::new();
14+
let resp = OutgoingResponse::new(fields);
15+
let body = resp.body().expect("outgoing response");
16+
17+
ResponseOutparam::set(outparam, Ok(resp));
18+
19+
let out = body.write().expect("outgoing stream");
20+
let bucket = keyvalue::store::open("").unwrap();
21+
let data = bucket.get("hello").unwrap().unwrap();
22+
out.blocking_write_and_flush(&data)
23+
.expect("writing response");
24+
25+
drop(out);
26+
OutgoingBody::finish(body, None).expect("outgoing-body.finish");
27+
}
28+
}
29+
30+
fn main() {}

crates/test-programs/src/bin/keyvalue_main.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
use test_programs::keyvalue::wasi::keyvalue::{atomics, batch, store};
22

33
fn main() {
4-
let identifier = std::env::var_os("IDENTIFIER")
5-
.unwrap()
6-
.into_string()
7-
.unwrap();
4+
let identifier = std::env::var("IDENTIFIER").unwrap_or("".to_string());
85
let bucket = store::open(&identifier).unwrap();
96

107
if identifier != "" {

crates/wasi-keyvalue/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ workspace = true
1313
[dependencies]
1414
anyhow = { workspace = true }
1515
wasmtime = { workspace = true, features = ["runtime", "async", "component-model"] }
16+
wasmtime-wasi = { workspace = true }
1617
async-trait = { workspace = true }
1718
url = { workspace = true }
1819
redis = { workspace = true, optional = true, features = ["tokio-comp"] }
1920

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

2525
[features]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
wasmtime::component::bindgen!({
2+
path: "wit",
3+
world: "wasi:keyvalue/imports",
4+
trappable_imports: true,
5+
async: true,
6+
with: {
7+
"wasi:keyvalue/store/bucket": crate::Bucket,
8+
},
9+
trappable_error_type: {
10+
"wasi:keyvalue/store/error" => crate::Error,
11+
},
12+
});
13+
14+
pub(crate) mod sync {
15+
wasmtime::component::bindgen!({
16+
path: "wit",
17+
world: "wasi:keyvalue/imports",
18+
trappable_imports: true,
19+
with: {
20+
"wasi:keyvalue/store/bucket": crate::Bucket,
21+
},
22+
trappable_error_type: {
23+
"wasi:keyvalue/store/error" => crate::Error,
24+
},
25+
});
26+
}

crates/wasi-keyvalue/src/lib.rs

Lines changed: 108 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
//! let mut linker = Linker::<Ctx>::new(&engine);
4242
//! wasmtime_wasi::add_to_linker_async(&mut linker)?;
4343
//! // add `wasi-runtime-config` world's interfaces to the linker
44-
//! wasmtime_wasi_keyvalue::add_to_linker(&mut linker, |h: &mut Ctx| {
44+
//! wasmtime_wasi_keyvalue::add_to_linker_async(&mut linker, |h: &mut Ctx| {
4545
//! WasiKeyValue::new(&h.wasi_keyvalue_ctx, &mut h.table)
4646
//! })?;
4747
//!
@@ -68,29 +68,17 @@
6868
6969
#![deny(missing_docs)]
7070

71+
mod bindings;
7172
mod provider;
72-
mod generated {
73-
wasmtime::component::bindgen!({
74-
path: "wit",
75-
world: "wasi:keyvalue/imports",
76-
trappable_imports: true,
77-
async: true,
78-
with: {
79-
"wasi:keyvalue/store/bucket": crate::Bucket,
80-
},
81-
trappable_error_type: {
82-
"wasi:keyvalue/store/error" => crate::Error,
83-
},
84-
});
85-
}
8673

87-
use self::generated::wasi::keyvalue;
74+
use self::bindings::{sync::wasi::keyvalue as keyvalue_sync, wasi::keyvalue};
8875
use anyhow::Result;
8976
use async_trait::async_trait;
9077
use std::collections::HashMap;
9178
use std::fmt::Display;
9279
use url::Url;
9380
use wasmtime::component::{Resource, ResourceTable, ResourceTableError};
81+
use wasmtime_wasi::runtime::in_tokio;
9482

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

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

417+
impl keyvalue_sync::store::Host for WasiKeyValue<'_> {
418+
fn open(&mut self, identifier: String) -> Result<Resource<Bucket>, Error> {
419+
in_tokio(async { keyvalue::store::Host::open(self, identifier).await })
420+
}
421+
422+
fn convert_error(&mut self, err: Error) -> Result<keyvalue_sync::store::Error> {
423+
match err {
424+
Error::NoSuchStore => Ok(keyvalue_sync::store::Error::NoSuchStore),
425+
Error::AccessDenied => Ok(keyvalue_sync::store::Error::AccessDenied),
426+
Error::Other(e) => Ok(keyvalue_sync::store::Error::Other(e)),
427+
}
428+
}
429+
}
430+
431+
impl keyvalue_sync::store::HostBucket for WasiKeyValue<'_> {
432+
fn get(&mut self, bucket: Resource<Bucket>, key: String) -> Result<Option<Vec<u8>>, Error> {
433+
in_tokio(async { keyvalue::store::HostBucket::get(self, bucket, key).await })
434+
}
435+
436+
fn set(&mut self, bucket: Resource<Bucket>, key: String, value: Vec<u8>) -> Result<(), Error> {
437+
in_tokio(async { keyvalue::store::HostBucket::set(self, bucket, key, value).await })
438+
}
439+
440+
fn delete(&mut self, bucket: Resource<Bucket>, key: String) -> Result<(), Error> {
441+
in_tokio(async { keyvalue::store::HostBucket::delete(self, bucket, key).await })
442+
}
443+
444+
fn exists(&mut self, bucket: Resource<Bucket>, key: String) -> Result<bool, Error> {
445+
in_tokio(async { keyvalue::store::HostBucket::exists(self, bucket, key).await })
446+
}
447+
448+
fn list_keys(
449+
&mut self,
450+
bucket: Resource<Bucket>,
451+
cursor: Option<u64>,
452+
) -> Result<keyvalue_sync::store::KeyResponse, Error> {
453+
in_tokio(async {
454+
let resp = keyvalue::store::HostBucket::list_keys(self, bucket, cursor).await?;
455+
Ok(keyvalue_sync::store::KeyResponse {
456+
keys: resp.keys,
457+
cursor: resp.cursor,
458+
})
459+
})
460+
}
461+
462+
fn drop(&mut self, bucket: Resource<Bucket>) -> Result<()> {
463+
keyvalue::store::HostBucket::drop(self, bucket)
464+
}
465+
}
466+
467+
impl keyvalue_sync::atomics::Host for WasiKeyValue<'_> {
468+
fn increment(
469+
&mut self,
470+
bucket: Resource<Bucket>,
471+
key: String,
472+
delta: u64,
473+
) -> Result<u64, Error> {
474+
in_tokio(async { keyvalue::atomics::Host::increment(self, bucket, key, delta).await })
475+
}
476+
}
477+
478+
impl keyvalue_sync::batch::Host for WasiKeyValue<'_> {
479+
fn get_many(
480+
&mut self,
481+
bucket: Resource<Bucket>,
482+
keys: Vec<String>,
483+
) -> Result<Vec<Option<(String, Vec<u8>)>>, Error> {
484+
in_tokio(async { keyvalue::batch::Host::get_many(self, bucket, keys).await })
485+
}
486+
487+
fn set_many(
488+
&mut self,
489+
bucket: Resource<Bucket>,
490+
key_values: Vec<(String, Vec<u8>)>,
491+
) -> Result<(), Error> {
492+
in_tokio(async { keyvalue::batch::Host::set_many(self, bucket, key_values).await })
493+
}
494+
495+
fn delete_many(&mut self, bucket: Resource<Bucket>, keys: Vec<String>) -> Result<(), Error> {
496+
in_tokio(async { keyvalue::batch::Host::delete_many(self, bucket, keys).await })
497+
}
498+
}
499+
500+
/// Add all the `wasi-keyvalue` world's interfaces to a [`wasmtime::component::Linker`].
501+
///
502+
/// This function will add the `sync` variant of all interfaces into the
503+
/// `Linker` provided. For embeddings with async support see
504+
/// [`add_to_linker_async`] instead.
505+
pub fn add_to_linker_sync<T>(
506+
l: &mut wasmtime::component::Linker<T>,
507+
f: impl Fn(&mut T) -> WasiKeyValue<'_> + Send + Sync + Copy + 'static,
508+
) -> Result<()> {
509+
keyvalue_sync::store::add_to_linker_get_host(l, f)?;
510+
keyvalue_sync::atomics::add_to_linker_get_host(l, f)?;
511+
keyvalue_sync::batch::add_to_linker_get_host(l, f)?;
512+
Ok(())
513+
}
514+
424515
#[cfg(test)]
425516
mod tests {
426517
#[test]

crates/wasi-keyvalue/src/provider/inmemory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{generated::wasi::keyvalue::store::KeyResponse, to_other_error, Error, Host};
1+
use crate::{bindings::wasi::keyvalue::store::KeyResponse, to_other_error, Error, Host};
22
use async_trait::async_trait;
33
use std::collections::HashMap;
44
use std::sync::{Arc, Mutex};

crates/wasi-keyvalue/src/provider/redis.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{generated::wasi::keyvalue::store::KeyResponse, Error, Host};
1+
use crate::{bindings::wasi::keyvalue::store::KeyResponse, Error, Host};
22
use anyhow::Result;
33
use async_trait::async_trait;
44
use redis::{aio::MultiplexedConnection, AsyncCommands, RedisError};
@@ -91,7 +91,7 @@ impl Host for Redis {
9191
for (key, value) in key_values {
9292
pipe.set(key, value).ignore();
9393
}
94-
pipe.query_async(&mut self.conn).await?;
94+
let _: () = pipe.query_async(&mut self.conn).await?;
9595
Ok(())
9696
}
9797

@@ -100,7 +100,7 @@ impl Host for Redis {
100100
for key in keys {
101101
pipe.del(key).ignore();
102102
}
103-
pipe.query_async(&mut self.conn).await?;
103+
let _: () = pipe.query_async(&mut self.conn).await?;
104104
Ok(())
105105
}
106106
}

0 commit comments

Comments
 (0)