Skip to content

Commit

Permalink
Update to wasmtime 13
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Levick <ryan.levick@fermyon.com>
  • Loading branch information
rylev committed Sep 22, 2023
1 parent 75c8110 commit 533b052
Show file tree
Hide file tree
Showing 15 changed files with 1,035 additions and 661 deletions.
690 changes: 435 additions & 255 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ members = ["crates/*", "sdk/rust", "sdk/rust/macro"]

[workspace.dependencies]
tracing = { version = "0.1", features = ["log"] }
wasmtime-wasi = { version = "10.0.1", features = ["tokio"] }
wasi-common-preview1 = { package = "wasi-common", version = "10.0.1" }
wasmtime = { version = "10.0.1", features = ["component-model"] }
spin-componentize = { git = "https://github.com/fermyon/spin-componentize", rev = "75fd5117d77415737e337a7fd15ed3317e2ad7a5" }
wasmtime-wasi = { version = "13.0.0", features = ["tokio"] }
wasi-common-preview1 = { version = "13.0.0", package = "wasi-common" }
wasmtime = { version = "13.0.0", features = ["component-model"] }
spin-componentize = { git = "https://github.com/fermyon/spin-componentize", rev = "0fa79bfc60f20c172213e2a2c34bd0b3168960b0" }

[workspace.dependencies.bindle]
git = "https://github.com/fermyon/bindle"
Expand Down
2 changes: 1 addition & 1 deletion crates/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spin-world = { path = "../world" }
thiserror = "1"
tokio = { version = "1", features = ["rt-multi-thread"] }
vaultrs = "0.6.2"
serde = "1.0.145"
serde = "1.0.188"

[dev-dependencies]
toml = "0.5"
9 changes: 6 additions & 3 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ tracing = { workspace = true }
wasmtime = { workspace = true }
wasmtime-wasi = { workspace = true }
wasi-common-preview1 = { workspace = true }
system-interface = { version = "0.25.1", features = ["cap_std_impls"] }
cap-std = "1.0.13"
system-interface = { version = "0.26.0", features = ["cap_std_impls"] }
cap-std = "2.0.0"
tokio = "1.0"
bytes = "1.0"

[target.'cfg(unix)'.dependencies]
rustix = "0.37.19"

[target.'cfg(windows)'.dependencies]
io-extras = "0.17.1"
io-extras = "0.18.0"

[dev-dependencies]
tempfile = "3"
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
spin-componentize = { workspace = true }
futures = "0.3"
31 changes: 16 additions & 15 deletions crates/core/src/io.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
use std::sync::{Arc, RwLock};

use wasmtime_wasi::preview2::pipe::WritePipe;
use wasmtime_wasi::preview2::{pipe::MemoryOutputPipe, HostOutputStream};

/// An in-memory stdio output buffer.
#[derive(Default)]
pub struct OutputBuffer(Arc<RwLock<Vec<u8>>>);
pub struct OutputBuffer(MemoryOutputPipe);

impl OutputBuffer {
/// Takes the buffered output from this buffer.
pub fn take(&mut self) -> Vec<u8> {
std::mem::take(&mut *self.0.write().unwrap())
/// Clones the buffered output from this buffer.
pub fn contents(&self) -> bytes::Bytes {
self.0.contents()
}

pub(crate) fn writer(&self) -> WritePipe<Vec<u8>> {
WritePipe::from_shared(self.0.clone())
pub(crate) fn writer(&self) -> impl HostOutputStream {
self.0.clone()
}
}

impl Default for OutputBuffer {
fn default() -> Self {
Self(MemoryOutputPipe::new(usize::MAX))
}
}

#[cfg(test)]
mod tests {
use wasmtime_wasi::preview2::OutputStream;

use super::*;

#[tokio::test]
async fn take_what_you_write() {
let mut buf = OutputBuffer::default();
buf.writer().write(b"foo").await.unwrap();
assert_eq!(buf.take(), b"foo");
let buf = OutputBuffer::default();
buf.writer().write(b"foo".to_vec().into()).unwrap();
assert_eq!(buf.contents().as_ref(), b"foo");
}
}
30 changes: 19 additions & 11 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,29 @@ impl Default for Config {
// knobs for each of these settings just yet and instead they're
// generally set to defaults. Environment-variable-based fallbacks are
// supported though as an escape valve for if this is a problem.
//
// NB: much of this will change in Wasmtime 13 as the settings are
// different. Ping @alexcrichton for assistance in updating this if
// needed (and delete this comment after the 13 update).
let mut pooling_config = PoolingAllocationConfig::default();
pooling_config
.instance_count(env("SPIN_WASMTIME_INSTANCE_COUNT", 1_000))
.instance_size(env("SPIN_WASMTIME_INSTANCE_SIZE", (10 * MB) as u32) as usize)
.instance_tables(env("SPIN_WASMTIME_INSTANCE_TABLES", 2))
.instance_table_elements(env("SPIN_WASMTIME_INSTANCE_TABLE_ELEMENTS", 30_000))
.instance_memories(env("SPIN_WASMTIME_INSTANCE_MEMORIES", 1))
.total_component_instances(env("SPIN_WASMTIME_INSTANCE_COUNT", 1_000))
// This number accounts for internal data structures that Wasmtime allocates for each instance.
// Instance allocation is proportional to the number of "things" in a wasm module like functions,
// globals, memories, etc. Instance allocations are relatively small and are largely inconsequential
// compared to other runtime state, but a number needs to be chosen here so a relatively large threshold
// of 10MB is arbitrarily chosen. It should be unlikely that any reasonably-sized module hits this limit.
.max_component_instance_size(
env("SPIN_WASMTIME_INSTANCE_SIZE", (10 * MB) as u32) as usize
)
.max_tables_per_component(env("SPIN_WASMTIME_INSTANCE_TABLES", 2))
.table_elements(env("SPIN_WASMTIME_INSTANCE_TABLE_ELEMENTS", 30_000))
// The number of memories an instance can have effectively limits the number of inner components
// a composed component can have (since each inner component has its own memory). We default to 32 for now, and
// we'll see how often this limit gets reached.
.max_memories_per_component(env("SPIN_WASMTIME_INSTANCE_MEMORIES", 32))
.total_memories(env("SPIN_WASMTIME_TOTAL_MEMORIES", 1_000))
.total_tables(env("SPIN_WASMTIME_TOTAL_TABLES", 2_000))
// Nothing is lost from allowing the maximum size of memory for
// all instance as it's still limited through other the normal
// `StoreLimitsAsync` accounting method too.
.instance_memory_pages(4 * GB / WASM_PAGE_SIZE)
.memory_pages(4 * GB / WASM_PAGE_SIZE)
// These numbers are completely arbitrary at something above 0.
.linear_memory_keep_resident((2 * MB) as usize)
.table_keep_resident((MB / 2) as usize);
Expand Down Expand Up @@ -205,7 +213,7 @@ impl<T: Send + Sync> EngineBuilder<T> {
let engine = wasmtime::Engine::new(&config.inner)?;

let mut linker: Linker<T> = Linker::new(&engine);
wasmtime_wasi::preview2::wasi::command::add_to_linker(&mut linker)?;
wasmtime_wasi::preview2::command::add_to_linker(&mut linker)?;

let mut module_linker = ModuleLinker::new(&engine);
wasmtime_wasi::tokio::add_to_linker(&mut module_linker, |data| match &mut data.wasi {
Expand Down
48 changes: 29 additions & 19 deletions crates/core/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
time::{Duration, Instant},
};
use system_interface::io::ReadReady;
use tokio::io::{AsyncRead, AsyncWrite};
use wasi_common_preview1 as wasi_preview1;
use wasmtime_wasi as wasmtime_wasi_preview1;
use wasmtime_wasi::preview2 as wasi_preview2;
Expand Down Expand Up @@ -156,19 +157,25 @@ impl StoreBuilder {
ctx.set_stdin(Box::new(wasmtime_wasi_preview1::stdio::stdin()))
}
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stdin(wasi_preview2::stdio::stdin())
ctx.inherit_stdin();
}
});
}

/// Sets the WASI `stdin` descriptor to the given [`Read`]er.
pub fn stdin_pipe(&mut self, r: impl Read + ReadReady + Send + Sync + 'static) {
pub fn stdin_pipe(
&mut self,
r: impl AsyncRead + Read + ReadReady + Send + Sync + Unpin + 'static,
) {
self.with_wasi(|wasi| match wasi {
WasiCtxBuilder::Preview1(ctx) => {
ctx.set_stdin(Box::new(wasi_preview1::pipe::ReadPipe::new(r)))
}
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stdin(wasi_preview2::pipe::ReadPipe::new(r))
ctx.stdin(
wasi_preview2::pipe::AsyncReadStream::new(r),
wasi_preview2::IsATTY::No,
);
}
})
}
Expand All @@ -180,7 +187,7 @@ impl StoreBuilder {
ctx.set_stdout(Box::new(wasmtime_wasi_preview1::stdio::stdout()))
}
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stdout(wasi_preview2::stdio::stdout())
ctx.inherit_stdout();
}
});
}
Expand All @@ -199,13 +206,16 @@ impl StoreBuilder {
}

/// Sets the WASI `stdout` descriptor to the given [`Write`]er.
pub fn stdout_pipe(&mut self, w: impl Write + Send + Sync + 'static) {
pub fn stdout_pipe(&mut self, w: impl AsyncWrite + Write + Send + Sync + Unpin + 'static) {
self.with_wasi(|wasi| match wasi {
WasiCtxBuilder::Preview1(ctx) => {
ctx.set_stdout(Box::new(wasi_preview1::pipe::WritePipe::new(w)))
}
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stdout(wasi_preview2::pipe::WritePipe::new(w))
ctx.stdout(
wasi_preview2::pipe::AsyncWriteStream::new(1024 * 1024, w),
wasi_preview2::IsATTY::No,
);
}
})
}
Expand All @@ -220,7 +230,7 @@ impl StoreBuilder {
"`Store::stdout_buffered` only supported with WASI Preview 2"
)),
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stdout(buffer.writer());
ctx.stdout(buffer.writer(), wasi_preview2::IsATTY::No);
Ok(())
}
})?;
Expand All @@ -234,19 +244,22 @@ impl StoreBuilder {
ctx.set_stderr(Box::new(wasmtime_wasi_preview1::stdio::stderr()))
}
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stderr(wasi_preview2::stdio::stderr())
ctx.inherit_stderr();
}
});
}

/// Sets the WASI `stderr` descriptor to the given [`Write`]er.
pub fn stderr_pipe(&mut self, w: impl Write + Send + Sync + 'static) {
pub fn stderr_pipe(&mut self, w: impl AsyncWrite + Write + Send + Sync + Unpin + 'static) {
self.with_wasi(|wasi| match wasi {
WasiCtxBuilder::Preview1(ctx) => {
ctx.set_stderr(Box::new(wasi_preview1::pipe::WritePipe::new(w)))
}
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).set_stderr(wasi_preview2::pipe::WritePipe::new(w))
ctx.stderr(
wasi_preview2::pipe::AsyncWriteStream::new(1024 * 1024, w),
wasi_preview2::IsATTY::No,
);
}
})
}
Expand All @@ -257,7 +270,9 @@ impl StoreBuilder {
for arg in args {
match wasi {
WasiCtxBuilder::Preview1(ctx) => ctx.push_arg(arg)?,
WasiCtxBuilder::Preview2(ctx) => *ctx = std::mem::take(ctx).push_arg(arg),
WasiCtxBuilder::Preview2(ctx) => {
ctx.arg(arg);
}
}
}
Ok(())
Expand All @@ -274,7 +289,7 @@ impl StoreBuilder {
match wasi {
WasiCtxBuilder::Preview1(ctx) => ctx.push_env(k.as_ref(), v.as_ref())?,
WasiCtxBuilder::Preview2(ctx) => {
*ctx = std::mem::take(ctx).push_env(k, v);
ctx.env(k, v);
}
}
}
Expand Down Expand Up @@ -333,12 +348,7 @@ impl StoreBuilder {
};
let file_perms = wasi_preview2::FilePerms::all();

*ctx = std::mem::take(ctx).push_preopened_dir(
cap_std_dir,
dir_perms,
file_perms,
path,
);
ctx.preopened_dir(cap_std_dir, dir_perms, file_perms, path);
}
}
Ok(())
Expand Down Expand Up @@ -432,7 +442,7 @@ impl WasiCtxBuilder {
fn build(self, table: &mut wasi_preview2::Table) -> anyhow::Result<Wasi> {
match self {
WasiCtxBuilder::Preview1(ctx) => Ok(Wasi::Preview1(ctx)),
WasiCtxBuilder::Preview2(b) => b.build(table).map(Wasi::Preview2),
WasiCtxBuilder::Preview2(mut b) => b.build(table).map(Wasi::Preview2),
}
}
}
51 changes: 43 additions & 8 deletions crates/core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use std::{
time::{Duration, Instant},
};

use anyhow::Context;
use spin_core::{
Component, Config, Engine, HostComponent, I32Exit, Store, StoreBuilder, Trap, WasiVersion,
};
use tempfile::TempDir;
use tokio::fs;
use tokio::{fs, io::AsyncWrite};

#[tokio::test(flavor = "multi_thread")]
async fn test_stdio() {
Expand Down Expand Up @@ -194,8 +195,8 @@ async fn run_core_wasi_test_engine<'a>(
update_store: impl FnOnce(&mut Store<()>),
) -> anyhow::Result<String> {
let mut store_builder: StoreBuilder = engine.store_builder(WasiVersion::Preview2);
let mut stdout_buf = store_builder.stdout_buffered()?;
store_builder.stderr_pipe(TestWriter);
let stdout_buf = store_builder.stdout_buffered()?;
store_builder.stderr_pipe(TestWriter(tokio::io::stdout()));
store_builder.args(args)?;

update_store_builder(&mut store_builder);
Expand All @@ -207,16 +208,24 @@ async fn run_core_wasi_test_engine<'a>(
let component = Component::new(engine.as_ref(), &component)?;
let instance_pre = engine.instantiate_pre(&component)?;
let instance = instance_pre.instantiate_async(&mut store).await?;
let func = instance.get_typed_func::<(), (Result<(), ()>,)>(&mut store, "run")?;

let func = {
let mut exports = instance.exports(&mut store);

let mut instance = exports
.instance("wasi:cli/run")
.context("missing the expected 'wasi:cli/run' instance")?;
instance.typed_func::<(), (Result<(), ()>,)>("run")?
};
update_store(&mut store);

func.call_async(&mut store, ())
.await?
.0
.map_err(|()| anyhow::anyhow!("command failed"))?;

let stdout = String::from_utf8(stdout_buf.take())?.trim_end().into();
let stdout = String::from_utf8(stdout_buf.contents().to_vec())?
.trim_end()
.into();
Ok(stdout)
}

Expand Down Expand Up @@ -252,8 +261,7 @@ impl multiplier::imports::Host for Multiplier {
}

// Write with `print!`, required for test output capture
#[derive(Copy, Clone)]
struct TestWriter;
struct TestWriter(tokio::io::Stdout);

impl std::io::Write for TestWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Expand All @@ -265,3 +273,30 @@ impl std::io::Write for TestWriter {
Ok(())
}
}

impl AsyncWrite for TestWriter {
fn poll_write(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
let this = self.get_mut();
std::pin::Pin::new(&mut this.0).poll_write(cx, buf)
}

fn poll_flush(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let this = self.get_mut();
std::pin::Pin::new(&mut this.0).poll_flush(cx)
}

fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let this = self.get_mut();
std::pin::Pin::new(&mut this.0).poll_shutdown(cx)
}
}
4 changes: 2 additions & 2 deletions crates/redis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ doctest = false
anyhow = "1.0"
async-trait = "0.1"
futures = "0.3"
serde = "1"
serde = "1.0.188"
spin-app = { path = "../app" }
spin-core = { path = "../core" }
spin-trigger = { path = "../trigger" }
spin-world = { path = "../world" }
redis = { version = "0.21", features = [ "tokio-comp" ] }
redis = { version = "0.21", features = ["tokio-comp"] }
tracing = { workspace = true }

[dev-dependencies]
Expand Down
Loading

0 comments on commit 533b052

Please # to comment.