# 运行时
运行时(runtime)是 Deft 的隔离单元:它拥有一个 VM、一个堆、一个协程调度器和一组顶层绑定。运行时之间按值隔离——彼此看不到对方的变量或堆——但共享平台层(`pub/*` 代理、`config/*` 注册表、默认值快照),以便相互协调。
`deft` 脚本二进制驱动单个运行时。每个 TUI 应用面板(位于 `retro` / `retra`)都在自己的 OS 线程中运行自己的运行时。你也可以从任何宿主显式创建子运行时并与其交互。
## 运行时层级
```
+------------------+
| Root runtime | owns VM, heap, scheduler
| (script or TUI | shares pubsub / config / registry with children
| workspace root) |
+------------------+
| createChild / registerChild
v
+------------------+ +------------------+
| Child runtime A | ... | Child runtime B |
| own VM + heap | | own VM + heap |
+------------------+ +------------------+
````
子运行时共享:
- `pub/*` 代理——见发布/订阅。
- `config/*` 注册表——见配置。
- 用于 `config/save-overrides` 的默认值快照。
子运行时不共享:
- 顶层绑定、`def` 或全局变量。
- 堆——值必须跨边界序列化。
- 协程调度器。
## `runtime/*` 标准库函数
| 函数 | 用途 |
|---|---|
| `[runtime/current]` | 当前运行时的名称(例如 `"main"`) |
| `[runtime/create "name"]` | 创建具名子运行时 |
| `[runtime/exists? "name"]` | 布尔值——子运行时是否存在? |
| `[runtime/list]` | 子运行时名称列表 |
| `[runtime/destroy "name"]` | 销毁子运行时 |
| `[runtime/eval "name" "code"]` | 在子运行时中同步求值——返回 `%{:value :output :error}` |
| `[runtime/eval-async "name" "code"]` | 异步求值——返回 id,结果发布到 `"runtime/eval-result"` 主题 |
| `[runtime/list-defs "name"]` | 子运行时当前命名空间中的 def |
| `[runtime/run "name" "path"]` | 在子运行时中运行文件(阻塞) |
| `[runtime/spawn "name" "path" ?params-map]` | 在专用 OS 线程上于子运行时中启动文件 |
| `[runtime/mem-stats]` | 进程 + VM 内存映射 |
| `[runtime/on-shutdown fn]` | 注册在 VM 拆卸时调用的可调用对象 |
## 创建和使用子运行时
# Create a named child runtime runtime/create "sandbox" # Evaluate code in it (synchronous) set r1 [runtime/eval "sandbox" "set secret 999"] # => %{ :value 999 :output "" :error nil } set r2 [runtime/eval "sandbox" "$secret"] # => %{ :value 999 :output "" :error nil } # variables persist across calls within the same sandbox # Inspect contents echo [runtime/list-defs "sandbox"] # Async eval — returns an id immediately set id [runtime/eval-async "worker" "[do-expensive-thing]"] # Result delivered later via pubsub on topic "runtime/eval-result" # Tear down runtime/destroy "sandbox"
## 隔离语义
变量不会跨越运行时边界:
set parent-var "hello" runtime/create "sandbox" set r [runtime/eval "sandbox" "$parent-var"] # => %{ :value nil :error "unbound variable: parent-var" } runtime/eval "sandbox" "set child-var 42" echo $child-var # Error: unbound variable: child-var (in the parent runtime)
在运行时之间传递的值(通过 `pub/*`、`runtime/spawn` 参数或 `runtime/eval` 返回值)会经 `deft/stringify` / `deft/parse` 序列化,以保持各堆彼此独立。标量、字符串、列表、映射、带标签的映射和关键字都能完整往返。闭包和原生资源则不能。
## 将文件作为进程启动
`runtime/spawn` 在具名子运行时中于专用 OS 线程上运行 `.dft` 文件。可选的参数映射会在文件运行前作为全局变量注入子运行时:
runtime/create "worker" runtime/spawn "worker" "src/worker.dft" %{ :queue "high-priority" :retries 3 }
在 `worker.dft` 内部:
# Reads injected globals as ordinary variables def queue $queue def retries $retries
这正是 `stdext/repl` 模块用来启动隔离 REPL 进程的模式(见 `packages/stdext/src/repl.dft`)。
## 通过发布/订阅进行异步求值
`runtime/eval-async` 是非阻塞的。结果发布到 `"runtime/eval-result"` 发布/订阅主题上,并以求值 id 作为关联标识:
def on-eval-result {|ev| @doc "Handle runtime/eval-async results." if ($ev~topic == "runtime/eval-result") { echo "got result for id $ev~data~id: $ev~data~value" | } } pub/subscribe "runtime/eval-result" "on-eval-result" runtime/eval-async "worker" "[compute-something]"
事件映射的形状记录在发布/订阅中。
## 内存统计与 GC
[gc/collect] # force GC + trim glibc arenas [gc/stats] # => %{ :live-objects ... :heap ... } [runtime/mem-stats] # => %{ :rss ... :vm-size ... :live-objects ... :heap-bytes ... }
在长时间运行的服务或 TUI 应用中使用这些函数来跟踪内存增长并触发显式回收。
## 关闭钩子
`runtime/on-shutdown` 注册一个在 VM 拆卸时运行的可调用对象。这是关闭数据库句柄、卸载包或向外部进程发送信号的正确位置:
def db [sqlite/open "data.db"] runtime/on-shutdown { try { sqlite/close $db } catch _e { nil } }
该钩子在正常退出、`os/exit` 以及大多数致命错误时触发。它不会在 SIGKILL 或突然崩溃时触发。
## 跨运行时通信
| 机制 | 方向 | 用途 |
|---|---|---|
| `pub/publish` / `pub/subscribe` | 一对多广播 | 松耦合、事件 |
| 针对 `@rpc` 函数的 `rpc` | 一对一直接调用 | 类型安全的 RPC、所有宿主 |
| `config/set`(带响应式) | 通过共享状态间接进行 | 跨运行时共享偏好 |
| `runtime/eval` / `runtime/eval-async` | 调用者 → 子运行时 | 直接代码注入 |
详见发布/订阅和 RPC。
## 何时启动子运行时
| 需求 | 方式 |
|---|---|
| 运行不受信任的代码 | `runtime/create` + `runtime/eval`(独立的堆可防止泄漏) |
| 后台长时间运行的进程 | 使用文件调用 `runtime/spawn` |
| 在多个核心上并行计算 | 每个工作进程一个 `runtime/spawn` + 用 `pub/publish` 收集结果 |
| 进程内 REPL 或调试器 | `runtime/create` + `runtime/spawn` 启动 REPL 宿主 |
| 带能力限制的敌对代码 | Deft 尚未提供受能力限制的运行时;隔离仅限于堆 |
## 沙箱注意事项
Deft 的运行时隔离是内存隔离,而非安全沙箱。子运行时共享同一 OS 进程、文件系统、网络以及 `os/shell` / `os/run` 标准库函数。不要将 `runtime/create` 视为针对敌对代码的安全边界。
对于 OS 级文件系统限制,Landlock 沙箱(沙箱,通过 `DEFT_SANDBOX=1` 选择性启用)将整个进程限制在预置文件的 `:sandbox` 路径列表中。子运行时共享同一组授权——它不是按运行时划分的边界。
真正的受能力限制运行时(无 `fs/*`、无 `os/run`、受限的 `net/*`)已在路线图上,但尚未发布。
## 快速参考
| 形式 | 用途 |
|---|---|
| `[runtime/current]` | 当前运行时名称 |
| `[runtime/create "name"]` | 创建子运行时 |
| `[runtime/list]` | 列出子运行时 |
| `[runtime/eval "name" "code"]` | 同步求值 |
| `[runtime/eval-async "name" "code"]` | 异步求值 |
| `[runtime/spawn "name" "path" ?params]` | 在子运行时中启动文件 |
| `[runtime/destroy "name"]` | 拆卸 |
| `[runtime/mem-stats]` | 内存映射 |
| `[runtime/on-shutdown fn]` | 清理钩子 |
| `[gc/collect]`, `[gc/stats]` | 垃圾回收控制 |
| `[deft/stringify value]`, `[deft/parse str]` | 跨运行时序列化 |
| `[satisfies? value "Protocol"]` | 协议成员检查 |