Files
core/sdk/README.md
T

111 lines
15 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.
# 外部 Worker SDK
SDK 用于把 Python、JavaScript、Go 或 C ABI 能力接入 Dever。应用先声明 Port,再用外部 Adapter 对接;Dever 管理 Worker 进程和通信。C ABI 运行在独立受管进程中,不是 Dever 主进程的任意 `dlopen` 或裸指针调用。
开发应用先看 [三种语言的完整例子](../skills/references/external-lib.md)。那里包含同一 Port、三种 Adapter、处理函数和 fake 测试,不需要手写协议或维护另一份接口注册表。
## 处理函数怎么写
函数名与 Port 操作名对应。输入、输出和业务错误的类型由 Dever 编译器生成;Python/JavaScript/Go SDK 负责解码,C shim 自行处理借用的 wire JSON,所有结果还须通过 Runtime 的 Port 类型校验。
| 语言 | 处理函数 | 加载生成的接口定义 |
| --- | --- | --- |
| Python | 异步函数 | `Worker.from_manifest(manifest, handlers)` |
| JavaScript | 函数接收 `AbortSignal`;支持 ESM 导出或 CommonJS 属性 | `Worker.fromManifest(manifest, handlers)` |
| Go | 函数接收 `context.Context` | `NewFromManifest(manifest)`,再 `RegisterHandlers(handlers)` |
| C/C++ | `dever_ffi_handler` 的同步 C ABI 回调 | `dever_ffi_serve` 读取编译器打包的 `contract.json` |
处理函数返回能用 JSON 表达的值,业务失败返回已声明的 `BusinessError`。不要往 stdout 写日志;stdout 用于 Worker 通信,日志写 stderr。
Worker 调用不设固定执行倒计时,耗时推理可以持续等待。启动握手、协议写入和终止清理仍有短限时;调用方主动取消或应用停止时仍走既有取消和进程回收机制。SDK 不为每次调用创建容器或内层沙箱。
Python/JavaScript/Go 的协作取消会产生一次 `dever.cancelled` 错误,已取消调用的结果会被丢弃。如果处理函数一秒内不响应取消,Worker 失败并停止接收新调用。Dever 管理进程与 stdin;自行在 Go 进程内调用 `Serve` 时,调用方负责关闭或解除自己的 `io.Reader` 阻塞。C ABI 的同步调用终止边界见后文。
## SDK 维护与验证
C ABI 的独立协议/动态库验收:`/usr/bin/python3 -B test/external-sdk/test_c_ffi.py --cc /absolute/path/to/cc`。测试在自有临时目录编译并调用真实 `.so`,结束清理;不代表 GPU 或完整发行已验收。
维护 SDK 时,显式指定本机运行环境的绝对路径:
```text
/usr/bin/python3 -B test/external-sdk/check.py --python /usr/bin/python3 --node /absolute/path/to/node --go /absolute/path/to/go
```
未指定的语言报告 `unavailable`,不会通过 PATH 猜测。Go 夹具在临时离线缓存中构建。这项检查验证本机 SDK 和类型协议;不能代替发行包或“机器未安装 Python/Node/Go”的运行验收。普通应用测试使用 Port fake,不启动真实 Worker。
编译器通过 `ExternalWorkerContract::sdk_manifest()` 生成唯一接口定义,包括身份、配置、输入、输出和业务错误。SDK 在 `hello` 时核对身份,不接受 supervisor 临时指定的 schema。配置在 `ready` 前解码,只读取 `hello.setting`。
本目录的三个 `example` 展示底层 `Contract`/`Worker` 注册 API,用于协议夹具。`fixture-schema-v1` 和 `test/external-sdk/typed_contract.json` 都是测试数据,不能复制成生产 Port hash。通信帧由 4 字节大端长度和 UTF-8 JSON 组成。
SDK 不依赖第三方包,也不读取环境变量或 PATH。托管 Worker 只读取已打包的 manifest。Python 固定使用 `-I -S -B`,先加载打包的 SDK,再把 Adapter 与依赖加入 `sys.path`。
## 受管 C ABI
复用 `external exec "worker/native" {}`,不引入另一套 FFI 语法或运行器。C/C++ shim 包含 `c/dever_ffi.h`,与 `c/dever_ffi.c` 一起预编译为对应平台 ELF;在 `invoke` 中调用现成库的 C ABI。编译由开发/发行环境显式完成,Dever `run/build` 不查找宿主编译器。不要把 C++ 异常跨过 C ABI 边界。
- `abi` 必须为 `DEVER_FFI_ABI_V1`;`open(context, setting)` 在握手时初始化一次,`invoke` 串行处理操作,`close` 在正常退出或可处理故障时释放状态。每个 Worker 生命周期可复用已加载状态,但不保证跨独立 Dever invocation 保温。
- `operation` 是借用的操作名字节;`setting`、`payload` 是借用的 JSON 切片,均不保证 NUL 结尾,只在当前回调有效,不得留存指针。
- 输出缓冲由 SDK 分配,shim 只写 `data[0..capacity]` 并设置 `size`,不得替换指针/容量、释放缓冲或越界。业务值使用 Port 的 wire JSON;Runtime 按已编译 Port 校验实际输出类型、声明错误及深度/大小,不接受动态接口签名。
- 返回 `DEVER_FFI_OK` 时输出为 JSON 结果;`DEVER_FFI_ERROR` 时输出为 `{"error":"已声明错误身份","payload":...}`;其他状态关闭通道并报告运行故障。源码中的 Secret 不得写 stdout/stderr,stdout 仅作协议通信。
- v1 同步原生调用不承诺协作取消,也没有隐式执行期限;调用方取消或应用退出时,清理宽限结束后由现有进程监管终止未退出的 Worker。崩溃或协议故障直接报错,不在主进程强杀线程,不自动重放失败调用。强制终止不保证执行 `close`,外部副作用应幂等。
静态 ELF 可直接打包;动态依赖放在入口旁的 `lib/`,只接收正常文件,不接收软链接。构建验证目标、SONAME、递归依赖和 ABI 版本;非系统依赖来自该目录;Linux 的解释器/基础系统 ABI 由运行容器提供并须匹配构建目标,不扫描宿主 Python、Node、Go 或编译器。例如 `worker/native` 对应 `worker/lib/libengine.so`。编译器同时打包唯一的 `contract.json`,不手写第二份 Port hash。C++ STL、跨语言对象、回调指针、可变参数及跨进程零拷贝不属于 v1 接口;需要时在 shim 内转为明确的 C 数据与 JSON 值。
开发验收在 `test/dever-cli-tests/external_workers/ffi.rs`:显式指定绝对路径 `DEVER_FFI_CC`(C 编译器)、`DEVER_PROCESS_RUNTIME`(已验证的目标 loader/libc 等运行库目录,仅读取 `lib/`),运行 `cargo test --offline -p dever-cli --test external_workers ffi::real_c_abi_direct_worker_errors_deadlines_and_cancellation -- --ignored --exact`。验证真实 `.so` 打包、Dever 应用执行、业务错误、原生崩溃、阻塞调用、取消和期限处理;测试不替换已安装编译器,不算 GPU/模型验收。原生调用不响应取消、导致协议清理超时时,当前调用报告运行故障,而不是伪装成成功的取消或超时回退。
发行验收还须用 `DEVER_RUNTIME_ABI_ARCHIVE` 指向实际候选归档并运行 `native_runtime_link`,尤其是 Bytes wire codec 用例;源码测试通过不能替代配套发行运行时的真实链接与执行。
## 执行边界与 GPU
Worker 和依赖构建都直接在当前容器中运行,由内核启动真实可执行文件(不以 GNU loader 充当进程入口,保留 Python/Node/Rust 的自身可执行路径和子进程行为)。目标容器须提供匹配的 Linux/glibc 基础 ABI,签名 `process-runtime` 库保留用于目标依赖核验,不用旧版 libc 替换容器加载器的配套 libc。不再创建 Bubblewrap/user/mount/PID/network namespace、附加 seccomp 或安装 Dever 专用 AppArmor 策略。`dever-process` 只负责明确的执行路径、运行库和进程组;超时、取消、退出会清理本次进程组。自行 `setsid` 脱离监管的守护进程不受此保证,Worker 不应自行后台化。
`allow file/network/process/gpu` 保留为编译期效果声明和握手契约,**不是操作系统访问控制**。Worker 具有当前容器用户已有的文件、网络和设备权限。旧 `adapter.<port>.files` / `.gpu` 挂载授权字段已移除,配置会明确拒绝;真实模型路径和输出目录放入业务 `setting`,由 Pod volume、UID/GID、NetworkPolicy 和设备分配实施边界。只运行可信代码,不用这套进程监管执行不可信租户程序。
GPU 使用父容器已经分配的设备和驱动,不再由 Dever 重新挂载。除已打包的私有库路径外,仅透传 `CUDA_VISIBLE_DEVICES`、`NVIDIA_VISIBLE_DEVICES`、`NVIDIA_DRIVER_CAPABILITIES`、`CUDA_DEVICE_ORDER` 与 `LD_LIBRARY_PATH`;其他宿主环境不自动传入。CUDA/PyTorch 等依赖仍需匹配目标平台。是否有 GPU、是否可加载驱动以及显存是否足够由真实部署决定,不会绕过 Kubernetes 调度或容器权限。设备验收须单独取得 GPU 准入;本次源码改造不等于已部署或模型推理通过。
## Python 发行资源
应用容器的系统库基线与编译器开发机不同。不要直接复制开发容器的 Python:解释器和每个动态扩展都必须满足目标 glibc 基线;Python 版本相同不代表系统 ABI 相同。保留真实解释器入口,不改用私有 GNU loader,也不回退到应用容器的系统 Python。
`sdk/python-runtime.py` 从显式提供并锁定 SHA-256 的 Python Build Standalone `install_only_stripped` 归档生成输入,不联网下载、不安装服务。参数为 `--archive`、`--sha256`、`--version`、`--target`、`--readelf`、`--output`,默认 `--glibc-max 2.17`。输出目录必须不存在;工具检查路径、目标 ELF、所有 GLIBC 版本依赖、基础模块及迁移后的解释器身份,保留运行文件和 Python 许可证,不带 pip、第三方 site-packages 或测试缓存。
输出的 `python-runtime.json` 对应既有作者配置 `runtimes.pip`,其 `source` 相对输出根;合入同一作者根后仍由 `native-release` 校验、打包和签名,Python 继续是独立扩展,不塞进基础包。wheel 标签包含同一 CPython 的原生 ABI、兼容的 abi3 和纯 Python 标签;manylinux 不超过目标 glibc 基线,通用 `linux_<架构>` 用于本机源码构建产物,不承诺跨系统 ABI 兼容。源码归档的来源、SHA-256 和实际目标环境验收结果归本次发行记录;本地候选目录不能充当正式源或修改现行安装。
## 完整公开发行
正式附件使用 `sdk/release-assets.py --editor-vsix <VSIX>` 导出,默认执行完整性检查。必须包含独立基础包、x86_64/ARM64 的 Python/Node/Go 运行扩展、宿主 Python/npm 构建扩展、ARM64 原生目标扩展、同版本编辑器扩展,以及签名清单和引导文件。缺任一项就失败;部分资源只允许通过显式的 `--partial-test-fixture` 用于定向测试,不能当作完整公开版本。Go 扩展由 `test/go-managed/make_pack.go` 导出完整 `std`,不能只导出样例依赖。
`.tar.zst` 是 tar 文件集合经过 Zstandard 压缩,减少附件体积;它不是另一个运行环境。安装器使用已验签的引导程序解压,不要求使用者额外安装 zstd。Python、Node、Go 及构建工具继续按需单独下载,不并入基础包。
当前发行序列经项目所有者确认从 `0.0.1` 重新开始,旧 Release 与附件在替代包验收后清理,不删除源代码提交历史。资源格式固定为现行 `process-runtime`,不再根据版本号推断旧沙箱布局。旧版 `0.1.x` 安装不能通过普通升级降到 `0.0.1`;须在新的安装根验收后明确切换,保留项目和凭据。不要关闭签名、同版本身份检查或防降级检查来绕过此规则。后续版本变更由项目所有者确认,不因每次修改自动递增版本号。
定向测试:`python3 -B -m unittest discover -s test/onboarding -p test_python_runtime.py -v`。这些测试验证打包约束,不代替在目标容器中执行签名应用、真实模型和取消清理验收;x86_64 验收也不证明 ARM64 已验收。
跨架构作者验证可显式传入 `--runner /absolute/qemu-aarch64 --sysroot /absolute/arm64-sysroot`,仍执行相同解释器身份和基础模块探测,不跳过 ELF 检查;须记录为 QEMU 验收,而不是原生 ARM64 硬件验收。这两个参数不进入应用运行配置。
## 依赖与跨平台构建
`dever lib install <project>` 按现有锁恢复依赖,不重新选版本、不修改配置或锁。`dever-lock-v6` 记录 Python extras/wheel METADATA、npm 嵌套依赖及 peers/optional/bundled 依赖、Go 模块校验证据。原始包保留来源与文件名,构建记录保留完整输入,包括 npm 剪除可选构建依赖前的依赖图。
丢失的构建结果由同一受管构建链重建,必须与记录的摘要一致。Python 原生 wheel 安装到配套解释器中,必须满足签名声明的标签、扩展 ABI 和动态库依赖要求。
生成 ARM64 应用时,先运行 `dever lib update <project> --target linux-aarch64`,再运行 `dever build <project> --target linux-aarch64 --output <new-file>`。Port 和 Worker 源码无需改写;锁、解释器、原生依赖和进程运行库资源必须匹配目标平台。缺少目标资源时失败,不借用宿主运行环境。`run/test` 仍在本机执行。
Go pack 的 `build.host` 描述编译器、链接器与分析器的运行平台;`build.goos/goarch` 和标准库对象头描述输出平台。工具在清空的环境中执行,目标必须预先正确配置。
## 第三方源码构建限制
只有显式准备 Lib 时才构建 Python sdist 或运行 npm 安装钩子。构建使用签名工具包,在独立临时工作目录内直接运行受管子进程;不再提供内层文件/网络隔离,因此只运行可信依赖及构建钩子。缺少工具时不回退宿主 PATH。
直接执行 GCC/binutils 时,工具依赖的非 glibc 动态库从已验签的 SDK 在本次临时目录内建立硬链接,供既有受管库路径加载;不依赖宿主另装 libbfd 等作者工具库,不复制额外库数据,也不替换宿主 glibc/loader。Python 原生扩展和 npm 原生 addon 必须分别通过真实源码构建验收,纯 Python/JS 回退不算通过。
Python PEP 517 的构建依赖单独锁定,每个 hook 使用新进程,最终 wheel 按保留的元数据核对。npm 的配套运行器处理 `preinstall/install/postinstall`、依赖 bin 和 node-gyp。
构建记录绑定源码、目标运行环境、工具、构建前端、依赖图和输出摘要。Worker 准备阶段离线恢复结果,不重跑安装钩子。npm `.node` 产物须通过目标检查、动态加载检查和独立 Node 加载探测,才能进入 Worker 资源目录。不匹配平台或 ABI 的其他变体保留在锁定原始包中,不加入运行目录。运行时非系统依赖来自 Worker 目录,系统 ABI 由目标容器提供,不能借用编译器 sysroot。
生成的原生 addon 验证失败,整个构建就失败;使用纯 JS 备用路径不能证明原生构建成功。当前限制:
- 构建进程最多 5 分钟,stdout/stderr 各最多 16 MiB。
- 每 250 毫秒检查临时目录,限制 512 MiB、65,536 个条目;这是定期检查,检查间隔内可能短暂超出。
- 最终 Worker 最多 256 MiB、65,536 个条目。失败时清理本次拥有的进程。