173 lines
12 KiB
Markdown
173 lines
12 KiB
Markdown
# Aster VM
|
||
|
||
Rust 编写的可嵌入运行时。当前实现 Endor 的 VM 基础设施:`EndorObject`、稳定对象分配、代际句柄、显式引用计数、循环回收和 Rust 原生操作分发。VM 从空堆启动,暂不实现 Python 的 type/元类体系,也不定义语言根类和继承规则。
|
||
|
||
尚无词法/语法前端、字节码执行器或渲染器,不承诺 Python 兼容。数据库只保存宿主元数据,不保存或恢复 VM 堆。对象基础层的逐段说明见 [EndorObject](docs/endor-object.md)。
|
||
|
||
## 结构
|
||
|
||
| crate | 职责 |
|
||
| --- | --- |
|
||
| `aster-core` | 零外部依赖的 EndorObject、操作接口、引用计数、循环回收与 VM |
|
||
| `aster-storage` | SQLx PostgreSQL/SQLite 适配、元数据表初始化、读写接口;不依赖内核 |
|
||
| `aster-cli` | `aster bootstrap` 演示,以及宿主元数据读写 |
|
||
| `aster-server` | 最小常驻宿主,提供健康检查与运行时信息,供 Docker 等方式运行 |
|
||
|
||
依赖方向:CLI/Server → Core 和 Storage;Core 与 Storage 互不依赖。未来浏览器宿主的网络、DOM、渲染等能力应留在宿主一侧。
|
||
|
||
## 直接运行
|
||
|
||
使用 Rust 1.91 或更高兼容版本;当前实测版本为 1.91.1,依赖锁定在 `Cargo.lock`。不需要 Docker。首次构建需要访问 crates.io。
|
||
|
||
```sh
|
||
cargo build --workspace --locked
|
||
cargo run -p aster-cli -- bootstrap
|
||
```
|
||
|
||
预期输出含 `runtime=EndorObject type_system=disabled`、`objects_distinct=true heap_objects=2` 和 `released heap_objects=0`。这条命令不读取数据库配置、不打开数据库;即使设置了错误的 `DATABASE_URL` 也不影响运行时演示。
|
||
|
||
```rust
|
||
use aster_core::{Value, Vm};
|
||
|
||
fn main() -> Result<(), aster_core::VmError> {
|
||
let mut vm = Vm::new()?;
|
||
let object = vm.new_object()?; // 分配返回一个宿主持有的强引用。
|
||
vm.set_attr(object, "x", Value::Integer(42))?;
|
||
assert_eq!(vm.get_attr(object, "x")?, Value::Integer(42));
|
||
vm.retain(object)?;
|
||
vm.release(object)?;
|
||
assert_eq!(vm.ref_count(object)?, 1);
|
||
vm.release(object)?; // 最后一个强引用释放,对象立即清理。
|
||
assert_eq!(vm.object_count(), 0);
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
嵌入时将 `aster-core` 加为 Cargo 路径依赖即可。上例与 crate 文档中的可运行示例语义相同。
|
||
|
||
## 宿主存储
|
||
|
||
不设置 `DATABASE_URL` 时,使用 `SQLITE_PATH` 指定的 SQLite 文件,默认 `data/aster.db`,相对于进程工作目录;自动创建父目录及文件。部署时必须把该目录放在持久卷上。`SQLITE_PATH=:memory:` 仅用于临时运行/测试,不持久化。
|
||
|
||
```sh
|
||
cargo run -p aster-cli -- metadata put project 'Aster VM'
|
||
cargo run -p aster-cli -- metadata get project
|
||
SQLITE_PATH=work/custom/aster.db cargo run -p aster-cli -- metadata put stage bootstrap
|
||
```
|
||
|
||
PostgreSQL 通过导出的 `DATABASE_URL=postgres://...` 或 `postgresql://...` 配置,优先于 `SQLITE_PATH`。显式空值、错误协议、连接失败都会返回错误并以非零状态退出,绝不静默降级。`DATABASE_URL` 不接受 SQLite URL;SQLite 使用独立路径配置。
|
||
|
||
连接成功后执行 `schema.sql` 中幂等的 v1 建表语句,提供 `Storage::put/get/ping/close`。读写使用参数绑定,重复键执行更新,缺失键返回 `None`。尚无复杂迁移版本管理。
|
||
|
||
Cargo 程序**不自动加载 `.env`**,请导出需要的环境变量。`.env.example` 是配置示例;Docker Compose 会自动读取复制后的 `.env`。连接 URL 可能含密码,不要贴到日志或提交到 Git。命令行只显示脱敏错误,底层诊断作为 Rust error source 保留给宿主。
|
||
|
||
## 常驻宿主
|
||
|
||
```sh
|
||
cargo run -p aster-server
|
||
curl --fail http://127.0.0.1:8080/health
|
||
curl --fail http://127.0.0.1:8080/bootstrap
|
||
```
|
||
|
||
`ASTER_LISTEN` 默认 `127.0.0.1:8080`。宿主启动时创建 VM、连接数据库、初始化表并写入 `host.version=0.1.0`;初始化失败即退出。`/health` 实际探测数据库,成功返回 200,失联返回 503;`/bootstrap` 返回 `object_model=EndorObject`、`type_system=false`、`heap_objects=0`,多次读取不分配对象。支持 Ctrl-C/Unix SIGTERM 优雅退出。
|
||
|
||
这两个端点不提供执行语言、对象写入或业务 API。服务没有认证层;示例 Compose 端口仅绑定宿主回环地址,外部访问由后续宿主部署决定。
|
||
|
||
## Docker:默认 SQLite
|
||
|
||
```sh
|
||
cp .env.example .env
|
||
docker compose up --build -d
|
||
curl --fail http://127.0.0.1:8080/health
|
||
docker compose exec aster aster metadata put project 'Aster VM'
|
||
docker compose exec aster aster metadata get project
|
||
docker compose down
|
||
docker compose up -d
|
||
docker compose exec aster aster metadata get project
|
||
```
|
||
|
||
容器以 UID 10001 运行,`aster-sqlite` 命名卷挂载到 `/data`,保存数据库和 WAL 文件。`down` 保留卷;`down -v` 会删除持久数据。构建采用多阶段 Dockerfile,运行镜像同时含 `aster-server` 和 `aster`。Compose 的 SQLite 方案不会透传宿主 `DATABASE_URL`,以保证默认方案明确使用 SQLite。
|
||
|
||
## Docker:选择 PostgreSQL
|
||
|
||
先将 `.env` 中的 `POSTGRES_PASSWORD` 换成随机十六进制字符串(可用 `openssl rand -hex 24` 生成)。该值同时用于数据库和连接 URL;使用其他字符时需要自行正确处理 URL 编码。
|
||
|
||
```sh
|
||
docker compose -f docker-compose.postgres.yml up --build -d
|
||
docker compose -f docker-compose.postgres.yml exec aster aster metadata put project 'Aster VM'
|
||
docker compose -f docker-compose.postgres.yml exec aster aster metadata get project
|
||
docker compose -f docker-compose.postgres.yml down
|
||
```
|
||
|
||
这是**独立替代方案**,只传入这一个 Compose 文件,不与默认文件叠加;两个方案不要同时占用同一个宿主端口。PostgreSQL 17 使用 `aster-postgres` 命名卷,数据库不向宿主公开端口。示例内部网络禁用数据库 TLS;跨不可信网络的部署应使用 PostgreSQL TLS 验证配置,例如 URL 参数 `sslmode=verify-full`。更改已有 PostgreSQL 卷的密码需要数据库管理操作,修改 `.env` 不会修改旧卷中的用户密码。
|
||
|
||
## ces-control CI/CD 与持久化部署
|
||
|
||
正式部署选择 PostgreSQL;默认 SQLite Compose 仍保留作为独立运行选项。
|
||
|
||
`.gitea/workflows/ci-deploy.yml` 在 `ces-control` Runner 上响应 `main` push 和手动触发:先在远端 Docker 容器中执行 fmt、workspace 测试、严格 Clippy、真实 PostgreSQL 集成测试和 SQLite/HTTP 冒烟检查,通过后构建生产镜像,再部署。使用独立的临时 PostgreSQL 容器做测试,不连接生产数据库。测试容器与网络在退出时清理,编译缓存仅保留在远端专属 Docker 卷中。
|
||
|
||
正式服务由 `docker-compose.ces-control.yml` 管理,默认仅绑定 `127.0.0.1:18081`,内部连接 PostgreSQL 17。应用/数据库使用 `unless-stopped` 自动重启,日志轮换;数据库不发布端口。部署使用 commit SHA 标记镜像与 release 目录,健康检查或元数据读写失败时尝试恢复上一版应用,保留数据库。
|
||
|
||
| 服务器路径 | 用途 |
|
||
| --- | --- |
|
||
| `/opt/endorphin-aster-vm/releases/<sha>/` | 固定版本的 Compose 与镜像配置 |
|
||
| `/opt/endorphin-aster-vm/current` | 最近健康版本的符号链接 |
|
||
| `/etc/endorphin-aster-vm/production.env` | 首次自动生成的数据库密码和端口,权限 0600 |
|
||
| `/var/lib/endorphin-aster-vm/postgres` | PostgreSQL 持久数据,替换应用/数据库容器后保留 |
|
||
|
||
无需在 Gitea 中设置生产数据库密码。不要删除或重新生成已有数据库对应的 `production.env`;修改密码需先通过 PostgreSQL 管理操作完成同步。首次部署没有可用的旧版可以回退;数据库 schema 的未来升级也需要另行设计兼容性,应用回退不等于回退数据库。
|
||
|
||
在服务器查看/停止当前部署(停止不删除数据):
|
||
|
||
```sh
|
||
docker compose -p endorphin-aster-vm \
|
||
--env-file /etc/endorphin-aster-vm/production.env \
|
||
--env-file /opt/endorphin-aster-vm/current/release.env \
|
||
-f /opt/endorphin-aster-vm/current/compose.yml ps
|
||
curl --fail http://127.0.0.1:18081/health
|
||
curl --fail http://127.0.0.1:18081/bootstrap
|
||
# 同样参数接 stop 停服,接 up -d --wait 恢复。
|
||
```
|
||
|
||
本机不执行上述 CI 测试,也不为其安装 Rust/SQLite/PostgreSQL。正式部署与测试只运行在用户指定的 `ces-control`。该工作流不改动 `autodl-gpu-runner-controller`、rednote 或 TiktokUI。工作流语义依据 [Gitea Actions 文档](https://docs.gitea.com/usage/actions/),环境文件加载依据 [Docker Compose 文档](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/)。
|
||
|
||
## 验证(在用户指定的环境执行)
|
||
|
||
当前工作约定禁止在本地运行测试,也不为本地测试下载依赖。下面的脚本和示例在指定的 ces-control CI/部署环境执行,不代表每次修改后已全部执行。实际结果及此前清理情况见 [验证记录](docs/validation.md)。
|
||
|
||
```sh
|
||
sh scripts/check.sh
|
||
python3 scripts/smoke.py
|
||
```
|
||
|
||
检查脚本运行 fmt、workspace 测试和拒绝所有 warning 的 Clippy;冒烟脚本使用临时 SQLite 文件,验证独立引导、跨进程持久化、配置错误、HTTP 端点和 SIGTERM 退出,不需要外部数据库。
|
||
|
||
PostgreSQL 集成测试默认显式忽略,需要测试数据库。以下是指定验证环境中的 Docker 示例;仅操作专用测试容器,不使用生产库,当前不在本机执行:
|
||
|
||
```sh
|
||
docker run --rm -d --name aster-pg-test \
|
||
-e POSTGRES_PASSWORD=aster-test -e POSTGRES_USER=aster -e POSTGRES_DB=aster \
|
||
-p 127.0.0.1:55432:5432 postgres:17-bookworm
|
||
# 等待以下命令成功后再运行测试:
|
||
docker exec aster-pg-test pg_isready -U aster -d aster
|
||
ASTER_TEST_POSTGRES_URL='postgres://aster:aster-test@127.0.0.1:55432/aster?sslmode=disable' \
|
||
cargo test -p aster-storage --test postgres --locked -- --ignored
|
||
docker stop aster-pg-test
|
||
```
|
||
|
||
测试会留下唯一键的元数据,专用测试库可随容器删除。缺少测试 URL 时显式运行该测试会失败,不会假装通过。真实 PostgreSQL 成功路径及容器结果以 [验证记录](docs/validation.md) 为准。
|
||
|
||
## 当前语义与后续开发
|
||
|
||
- `EndorObject` 是 Rust 运行时公共对象结构,包含身份、引用计数、原生载荷和托管属性。没有语言类或类型指针;`ObjectPayload` 是 Rust 操作接口,诊断名称不参与语言类型判断。
|
||
- `ObjectRef` 包含私有 VM 身份、槽位和代数。`Box<EndorObject>` 保持存活对象地址稳定;槽位复用更新代数,旧句柄不会指向新对象。外部不构造堆索引,也不操作裸指针。
|
||
- 分配返回一个外部强引用;复制句柄或含句柄的 `Value` 不增加计数。额外持有使用 `retain`,结束持有使用 `release`。对象属性和载荷声明的边各自持有内部强引用;读属性与原生调用返回借用值,需长期持有时显式 retain。
|
||
- 最后一个强引用释放时,迭代清理依赖对象并调用 Rust Drop。`collect_cycles()` 从外部强引用追踪,显式回收不可达环;不会自动定时执行。销毁 VM 时统一释放剩余对象。
|
||
- 支持 repr/str、身份与内容比较、哈希、真值、属性读写删除、原生函数调用。操作由 Rust 载荷实现;不支持的操作返回错误。原生调用可返回标量或已有对象的借用句柄,当前不能在回调中修改 VM;没有语言函数执行器。
|
||
- 标量与字符串继续直接保存在 `Value`,不要求全部值装箱。语言根类、继承、元类、描述符、语言异常及 VM 堆持久化均留待以后定义。
|
||
|
||
下一阶段可围绕执行栈的强引用根管理和可变 VM 调用上下文推进执行器;GC 已有手动全堆版本,尚无分代、增量、并行收集或语言终结器/复活语义。详见 [架构](docs/architecture.md)、[对象基础层](docs/endor-object.md) 与 [开发交接](docs/development.md)。
|
||
|
||
依赖 API 依据 [SQLx 0.8.6 官方文档](https://docs.rs/sqlx/0.8.6/sqlx/) 和 [Axum 官方文档](https://docs.rs/axum/0.8.9/axum/) 实现。SQLx 0.9 要求更高 Rust 版本,本阶段使用与本机工具链兼容的 0.8.6;确切解析版本以锁文件为准。
|