# 运行时 运行时(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"]` | 协议成员检查 |