Files
wasmeld/docs/design/wasmeld-runtime-mvp.md
T
Maofeng ad7d8cdd72 docs: document Wasmeld architecture and workflows
- describe resident Runtime boundaries and sandbox guarantees
- specify component archive and host capability contracts
- document Console ownership and persistence behavior
- explain versioned WIT Registry, lock files, and path replace
- provide startup, packaging, publication, and dependency commands
2026-07-27 05:03:24 +08:00

320 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Wasmeld Runtime 架构
**状态:** Draft
**日期:** 2026-07-22
**范围:** 仅实现 Wasmeld Runtime
## 1. 范围
本阶段只解决一件事:在一个 Rust 宿主进程中,安全地加载并常驻运行内部 Rust 编译的 WebAssembly Component。
需要具备:
- Rust Component 的构建、注册、加载、调用、停止与重载。
- 同一实例跨多次调用保留内存。
- 同一实例不发生并发重入。
- Wasmtime 资源限制与最小 sandbox。
- 统一且不含业务语义的 Component 调用契约。
当前不做 HTTP、客户、鉴权、数据库、网络、消息队列、业务状态持久化、分布式部署或发布治理。
## 2. 核心结构
~~~text
Rust Component source
|
| cargo build --target wasm32-wasip2 --release
v
component.wasm + manifest.toml
|
v
Component Registry
|
v
Wasmeld Runtime (one Rust process)
|
+-- Engine
+-- compiled Component cache
+-- Service Manager
|
+-- ServiceActor
+-- bounded mailbox
+-- Store<HostState>
+-- restricted Linker
+-- Component Instance
~~~
Runtime 是一个 Rust 库或单独进程,不提供网络协议。对内只暴露:
~~~text
register(manifest, artifact) -> ServiceKey
start(service_key, init_config) -> ActorHandle
invoke(actor_handle, bytes) -> bytes or RuntimeError
stop(actor_handle)
reload(next_manifest, next_artifact, init_config) -> ActorHandle
~~~
调用方未来可以是 HTTP Adapter、CLI 或测试程序;Runtime 本身不关心调用来源。
## 3. 核心对象
| 对象 | 责任 |
| --- | --- |
| Component Artifact | 一个不可变的 component.wasm 与最小 manifest。 |
| Service Revision | Artifact 的 id + revision。 |
| Engine | 进程级共享的 Wasmtime 编译和执行引擎。 |
| Component Cache | 缓存编译后的 Component,不保存实例内存。 |
| Service Manager | 管理 Service Revision 与 ActorHandle 的映射。 |
| ServiceActor | 独占 Store、Instance、HostState 与有界邮箱。 |
| ActorHandle | 向 Actor 发送调用,不能接触 Store 或 Instance。 |
| HostState | Store 专有的运行时状态,不包含业务数据。 |
V1 采用:
~~~text
one Service Revision -> one ServiceActor -> one Store + one Instance
~~~
多个 Actor、分片和水平扩展属于后续能力。
## 4. 制品与注册
内部服务构建为 Component
~~~bash
cargo build --target wasm32-wasip2 --release
~~~
最小制品:
~~~text
component.wasm
manifest.toml
~~~
manifest 只包含:
~~~text
id
revision
component path
expected WIT world
memory limit
fuel per call
deadline
mailbox capacity
input/output byte limits
~~~
注册规则:
- ServiceKey 由 id 与 revision 构成,不允许覆盖。
- Runtime 校验 manifest 的 WIT world 格式与资源策略;实例化时由 typed binding 校验基础导出契约。
- V1 只接受本地受信任制品;Registry、签名、SBOM 与远程下载后置。
- 已编译 Component 可缓存,但 Actor 的内存只能存在于 Store + Instance 中。
## 5. 最小 WIT 契约
V1 不定义任何业务协议。输入输出都是有长度上限的 opaque bytes。
~~~wit
package wasmeld:service@0.1.0;
world service-component {
variant service-error {
init-failed(string),
call-failed(string),
}
export init: func(config: list<u8>) -> result<_, service-error>;
export invoke: func(input: list<u8>) -> result<list<u8>, service-error>;
}
~~~
规则:
- init 仅在一个新 Instance 上调用一次。
- invoke 是唯一的普通调用入口。
- Component 返回 service-error 后,Actor 可继续使用。
- trap、资源超限或中断后,整个 Store + Instance 必须丢弃,不能继续调用。
- 基础 world 不定义宿主能力 import。组件专属 World 通过独立 interface 按需 import
平台能力,详见 `wasmeld-capabilities.md`。普通 Rust `wasm32-wasip2` 产物会隐式导入标准 WASI P2
接口,因此 Runtime 提供受限的 WASI 兼容层,而不是把它们暴露为业务 ABI。
- Runtime 在注册时只允许以下兼容 import:`wasi:cli/{environment,exit,stdin,stdout,stderr}`
`wasi:clocks/wall-clock``wasi:filesystem/{preopens,types}``wasi:io/{error,streams}`
`wasi:io/poll`、WASI monotonic clock、随机数、socket、HTTP 和未知平台 import 都会被拒绝。
- V1 提供独立的 `wasmeld:clock/monotonic-clock@0.1.0` 平台能力。只有显式导入
该 interface 的组件会获得对应 Linker 实现。
- health、shutdown、日志、文件、网络和标准 I/O 都不属于基础 ABI。wall clock 仅作为
Rust/WASI 兼容层的一部分存在,不用于传递业务能力;随机数不在 V1 白名单内。
## 6. 常驻 Actor
~~~text
Caller
-> ActorHandle
-> bounded Sender<Invocation>
-> dedicated Wasm worker
-> Store + Instance + HostState
~~~
一个 Actor 的 worker 独占 Store。不能使用 Arc<Mutex<Store>> 共享 Store,也不能让同一 Instance 重入。
处理流程:
1. ActorHandle 提交 Invocation。
2. Actor 从 FIFO mailbox 取出一条消息。
3. Actor 在同一 Store + Instance 上调用 service.invoke。
4. Actor 通过 oneshot response 返回结果。
5. 下一条消息才可进入该 Instance。
这保证 Rust heap、Wasm linear memory 和全局变量在多次调用间常驻,同时保证状态修改串行。
实现约束:
- Wasm 调用放在专用 worker 线程或线程池,不能阻塞 Tokio 的通用 worker。
- V1 不使用 Wasmtime async 或 Wasm threads。为兼容 Rust 的 `wasm32-wasip2` 组件,
链接同步、受限的 WASI P2 白名单实现。
- mailbox 满时立即返回 RuntimeOverloaded。
- 调用 deadline 到期且尚未进入 Instance 时直接取消。
- Component 不得在 invoke 之外启动后台循环。
Actor 内存是易失的。Runtime 重启、Actor fault、stop 或 reload 后,下一实例重新执行 init,不恢复旧内存。
## 7. 生命周期
~~~text
Registered -> Starting -> Ready -> Draining -> Stopped
|
+-> Failed
~~~
- register:保存 Artifact 与 manifest。
- start:取得已编译 Component,创建 Store、受限 WASI Linker 与 Instance,然后调用 init。
- ready:接收 invoke。
- draining:拒绝新调用,等待当前调用完成。
- stopped:释放 Store 和 Instance。
- failedtrap、fuel 耗尽、deadline 或 Runtime 错误后立即释放 Store 和 Instance。
故障后:
~~~text
fault
-> mark Actor failed
-> reject queued calls
-> drop Store + Instance
-> optionally create a new Actor
~~~
自动重建可配置,但 V1 不应隐藏故障或声称保留了旧内存。
reload 的最小流程:
1. 注册新的 Service Revision。
2. start 新 Actor,确保 init 成功。
3. 调用方切换到新的 ActorHandle。
4. 调用方显式 stop 旧 Actor 并释放。
V1 不做请求级流量切换或状态迁移。
## 8. Sandbox 与资源限制
V1 Linker 注册受限 WASI P2 兼容实现,并按 Component 的真实 imports 注册已批准的
平台能力。每个 Actor 都有独立
`WasiCtx`,并且显式禁止 TCP、UDP、DNS 和所有 socket 地址。默认配置还保证无环境变量、
参数、工作目录或预打开目录,stdin 关闭,stdout/stderr 丢弃。因此 Component 默认没有:
- 文件系统、环境变量、命令行参数或标准 I/O 继承。
- 网络、DNS、socket 或原始系统调用。
- 数据库、密钥、配置文件、宿主目录或宿主进程能力。
每个 Actor 必须显式设置:
| 限制 | 目的 |
| --- | --- |
| linear memory | 阻止无限内存增长。 |
| tables / instances / memories | 限制 Wasm 对象数量。 |
| fuel per invocation | 限制确定性的计算工作量。 |
| epoch deadline | 限制 wall-clock 执行时间。 |
| input/output bytes | 限制单次数据传输。 |
| mailbox capacity | 实现背压,避免无限排队。 |
fuel 和 epoch 只约束 Wasm 执行。未来一旦加入 Host I/O,必须单独定义 timeout、取消和连接资源策略。
Runtime 的 epoch ticker 上限为 10 ms,且服务 deadline 不能小于该 tick,以避免配置使
wall-clock deadline 失效。ActorHandle 持有 ticker 的生命周期引用,因此 Runtime 对象释放后,
仍被调用方持有的 ActorHandle 不会失去 epoch 中断能力。
Wasm Store 隔离 Component 内存和可见 import,但不是完整 OS 隔离。V1 以第一方代码为前提;进程、Pod、gVisor 或 Kata 隔离在实际需要时再加入。
## 9. 最小代码结构
~~~text
wit/
service/
package.wit
clock/
package.wit
crates/
wasmeld-runtime/
bindings.rs
error.rs
manifest.rs
runtime.rs
lib.rs
components/
echo/
counter/
fault/
spin/
clock-probe/
tests/
runtime_components.rs
~~~
## 10. 验收测试
测试 Component
- echo:原样返回输入,验证基础加载与调用。
- counterinit 创建计数状态,每次 invoke 递增,验证内存常驻。
- fault:显式 trap,验证旧 Store 会被丢弃且 Actor 可重建。
- spin:不可被优化掉的 CPU 循环,验证 fuel 与 epoch 中断。
- clock-probe:显式导入平台 monotonic-clock,验证能力按需 Link。
- wasi-clock-probe:通过 Rust 标准库导入 WASI monotonic clock,验证注册时拒绝越权。
必须验证:
1. Component 只有在导出匹配预期 WIT world、且其 import 可由受限 WASI Linker 满足时才可启动。
2. 同一 Actor 调用 counter 两次能观察到递增状态。
3. stop、fault 或 Runtime 重启后,counter 从 init 重新开始。
4. 多个并发 invoke 会严格经同一 Actor 串行执行。
5. 无限循环会被 fuel 或 epoch deadline 终止。
6. trap 后旧 Store 被丢弃,重建 Actor 后可再次调用。
7. mailbox 满时立即出现 overload,不无限等待。
8. memory.grow 超过限制会导致 Actor fault。
9. reload 能启动新 revision 的 Actor;旧 Actor 由调用方显式 stop。
## 11. 后续扩展触发条件
| 明确需求 | 再引入的能力 |
| --- | --- |
| 需要网络入口 | HTTP/gRPC Adapter 与 API Gateway。 |
| 需要调用宿主资源 | 独立 Host Capability 与 WIT import。 |
| 需要跨重启保留状态 | 外部状态、checkpoint 与恢复协议。 |
| 需要多个实例 | Actor 池、分片和放置策略。 |
| 需要生产发布治理 | 制品签名、Registry、灰度与回滚。 |
| 需要执行非第一方代码 | 额外进程或 OS sandbox。 |
## 12. 参考资料
- [WebAssembly Component Model](https://component-model.bytecodealliance.org/)
- [Rust Components with wasm32-wasip2](https://component-model.bytecodealliance.org/language-support/building-a-simple-component/rust.html)
- [Wasmtime](https://docs.wasmtime.dev/)
- [Wasmtime interruption with fuel and epochs](https://docs.wasmtime.dev/examples-interrupting-wasm.html)