Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Update to wasmtime 13 #1763

Merged
merged 2 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
rylev marked this conversation as resolved.
Show resolved Hide resolved
// 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
rylev marked this conversation as resolved.
Show resolved Hide resolved
)
.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