feat(runtime): add resident Wasmtime actor runtime
- define versioned service and clock WIT contracts - enforce import allowlists, fuel, epoch, memory, I/O, and mailbox limits - keep one Store and Component Instance resident per serial Actor - add echo, counter, fault, spin, and capability probe components - cover lifecycle, concurrency, sandbox, and fault recovery behavior
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "clock-probe-component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.wasmeld]
|
||||
id = "clock-probe"
|
||||
world = "component:clock-probe/clock-probe-component@0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen.workspace = true
|
||||
@@ -0,0 +1,24 @@
|
||||
mod bindings {
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "clock-probe-component",
|
||||
generate_all,
|
||||
});
|
||||
}
|
||||
|
||||
struct ClockProbe;
|
||||
|
||||
impl bindings::Guest for ClockProbe {
|
||||
fn init(_config: Vec<u8>) -> Result<(), bindings::ServiceError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||
let now = bindings::wasmeld::clock::monotonic_clock::now();
|
||||
let mut output = now.to_le_bytes().to_vec();
|
||||
output.extend_from_slice(&input);
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
bindings::export!(ClockProbe with_types_in bindings);
|
||||
@@ -0,0 +1,11 @@
|
||||
schema_version = 1
|
||||
|
||||
[dependencies]
|
||||
"wasmeld:clock" = "0.1.0"
|
||||
"wasmeld:service" = "0.1.0"
|
||||
|
||||
[replace."wasmeld:clock"]
|
||||
path = "../../wit/clock"
|
||||
|
||||
[replace."wasmeld:service"]
|
||||
path = "../../wit/service"
|
||||
@@ -0,0 +1,15 @@
|
||||
schema_version = 1
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:clock"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/clock"
|
||||
sha256 = "93079c69d1b9cb3afc28da6fe4a7e37f724d0a09e780c847f8524d3920a90960"
|
||||
replaced = true
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:service"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/service"
|
||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
||||
replaced = true
|
||||
@@ -0,0 +1,6 @@
|
||||
package component:clock-probe@0.1.0;
|
||||
|
||||
world clock-probe-component {
|
||||
include wasmeld:service/service-component@0.1.0;
|
||||
import wasmeld:clock/monotonic-clock@0.1.0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "counter-component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.wasmeld]
|
||||
id = "counter"
|
||||
world = "component:counter/counter-component@0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen.workspace = true
|
||||
@@ -0,0 +1,28 @@
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
mod bindings {
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "counter-component",
|
||||
});
|
||||
}
|
||||
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct Counter;
|
||||
|
||||
impl bindings::Guest for Counter {
|
||||
fn init(_config: Vec<u8>) -> Result<(), bindings::ServiceError> {
|
||||
// Calls are serialized by the host Actor, so this component intentionally
|
||||
// uses one mutable global to make resident memory observable in tests.
|
||||
COUNTER.store(0, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke(_input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||
let next = COUNTER.fetch_add(1, Ordering::Relaxed).wrapping_add(1);
|
||||
Ok(next.to_le_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
bindings::export!(Counter with_types_in bindings);
|
||||
@@ -0,0 +1,7 @@
|
||||
schema_version = 1
|
||||
|
||||
[dependencies]
|
||||
"wasmeld:service" = "0.1.0"
|
||||
|
||||
[replace."wasmeld:service"]
|
||||
path = "../../wit/service"
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version = 1
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:service"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/service"
|
||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
||||
replaced = true
|
||||
@@ -0,0 +1,5 @@
|
||||
package component:counter@0.1.0;
|
||||
|
||||
world counter-component {
|
||||
include wasmeld:service/service-component@0.1.0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "echo-component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.wasmeld]
|
||||
id = "echo"
|
||||
world = "component:echo/echo-component@0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen.workspace = true
|
||||
@@ -0,0 +1,20 @@
|
||||
mod bindings {
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "echo-component",
|
||||
});
|
||||
}
|
||||
|
||||
struct Echo;
|
||||
|
||||
impl bindings::Guest for Echo {
|
||||
fn init(_config: Vec<u8>) -> Result<(), bindings::ServiceError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
|
||||
bindings::export!(Echo with_types_in bindings);
|
||||
@@ -0,0 +1,7 @@
|
||||
schema_version = 1
|
||||
|
||||
[dependencies]
|
||||
"wasmeld:service" = "0.1.0"
|
||||
|
||||
[replace."wasmeld:service"]
|
||||
path = "../../wit/service"
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version = 1
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:service"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/service"
|
||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
||||
replaced = true
|
||||
@@ -0,0 +1,5 @@
|
||||
package component:echo@0.1.0;
|
||||
|
||||
world echo-component {
|
||||
include wasmeld:service/service-component@0.1.0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "fault-component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.wasmeld]
|
||||
id = "fault"
|
||||
world = "component:fault/fault-component@0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen.workspace = true
|
||||
@@ -0,0 +1,21 @@
|
||||
mod bindings {
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "fault-component",
|
||||
});
|
||||
}
|
||||
|
||||
struct Fault;
|
||||
|
||||
impl bindings::Guest for Fault {
|
||||
fn init(_config: Vec<u8>) -> Result<(), bindings::ServiceError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||
assert_ne!(input, b"fault", "intentional test fault");
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
|
||||
bindings::export!(Fault with_types_in bindings);
|
||||
@@ -0,0 +1,7 @@
|
||||
schema_version = 1
|
||||
|
||||
[dependencies]
|
||||
"wasmeld:service" = "0.1.0"
|
||||
|
||||
[replace."wasmeld:service"]
|
||||
path = "../../wit/service"
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version = 1
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:service"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/service"
|
||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
||||
replaced = true
|
||||
@@ -0,0 +1,5 @@
|
||||
package component:fault@0.1.0;
|
||||
|
||||
world fault-component {
|
||||
include wasmeld:service/service-component@0.1.0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "spin-component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.wasmeld]
|
||||
id = "spin"
|
||||
world = "component:spin/spin-component@0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen.workspace = true
|
||||
@@ -0,0 +1,43 @@
|
||||
mod bindings {
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "spin-component",
|
||||
});
|
||||
}
|
||||
|
||||
static mut SPIN_STATE: u64 = 0;
|
||||
|
||||
struct Spin;
|
||||
|
||||
impl bindings::Guest for Spin {
|
||||
fn init(config: Vec<u8>) -> Result<(), bindings::ServiceError> {
|
||||
if config == b"spin" {
|
||||
burn(u64::MAX);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||
let steps = input
|
||||
.get(..size_of::<u64>())
|
||||
.and_then(|bytes| bytes.try_into().ok())
|
||||
.map(u64::from_le_bytes)
|
||||
.unwrap_or_default();
|
||||
|
||||
burn(steps);
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
// The volatile write makes every loop iteration observable to the optimizer.
|
||||
fn burn(steps: u64) {
|
||||
let mut state = unsafe { core::ptr::read_volatile(&raw const SPIN_STATE) };
|
||||
|
||||
for _ in 0..steps {
|
||||
state = state.rotate_left(7).wrapping_add(0x9e37_79b9_7f4a_7c15);
|
||||
unsafe { core::ptr::write_volatile(&raw mut SPIN_STATE, state) };
|
||||
}
|
||||
}
|
||||
|
||||
bindings::export!(Spin with_types_in bindings);
|
||||
@@ -0,0 +1,7 @@
|
||||
schema_version = 1
|
||||
|
||||
[dependencies]
|
||||
"wasmeld:service" = "0.1.0"
|
||||
|
||||
[replace."wasmeld:service"]
|
||||
path = "../../wit/service"
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version = 1
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:service"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/service"
|
||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
||||
replaced = true
|
||||
@@ -0,0 +1,5 @@
|
||||
package component:spin@0.1.0;
|
||||
|
||||
world spin-component {
|
||||
include wasmeld:service/service-component@0.1.0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "wasi-clock-probe-component"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[package.metadata.wasmeld]
|
||||
id = "wasi-clock-probe"
|
||||
world = "component:wasi-clock-probe/wasi-clock-probe-component@0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen.workspace = true
|
||||
@@ -0,0 +1,21 @@
|
||||
mod bindings {
|
||||
wit_bindgen::generate!({
|
||||
path: "wit",
|
||||
world: "wasi-clock-probe-component",
|
||||
});
|
||||
}
|
||||
|
||||
struct WasiClockProbe;
|
||||
|
||||
impl bindings::Guest for WasiClockProbe {
|
||||
fn init(_config: Vec<u8>) -> Result<(), bindings::ServiceError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||
let _ = std::time::Instant::now();
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
|
||||
bindings::export!(WasiClockProbe with_types_in bindings);
|
||||
@@ -0,0 +1,7 @@
|
||||
schema_version = 1
|
||||
|
||||
[dependencies]
|
||||
"wasmeld:service" = "0.1.0"
|
||||
|
||||
[replace."wasmeld:service"]
|
||||
path = "../../wit/service"
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version = 1
|
||||
|
||||
[[package]]
|
||||
name = "wasmeld:service"
|
||||
version = "0.1.0"
|
||||
source = "path+../../wit/service"
|
||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
||||
replaced = true
|
||||
@@ -0,0 +1,5 @@
|
||||
package component:wasi-clock-probe@0.1.0;
|
||||
|
||||
world wasi-clock-probe-component {
|
||||
include wasmeld:service/service-component@0.1.0;
|
||||
}
|
||||
Reference in New Issue
Block a user