feat: release Dever 0.1.5 with direct workers and long transactions

This commit is contained in:
Codex committed 2026-10-11 11:17:33 +00:00
1 parent 89629c2197
commit a900d7e8db
153 files changed
+3676 -3708

No files matched your search

+35 -8
View File
@@ -1,25 +1,28 @@
# Python、JavaScript 与 Go SDK
# 外部 Worker SDK
SDK 用于把现成的 Python、JavaScript 或 Go 能力接入 Dever。应用先声明 Port,再用外部 Adapter 对接;Dever 会管理 Worker 进程和通信。
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 编译器生成,SDK 负责验证。
函数名与 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。
取消请求会产生一次 `dever.cancelled` 错误,已取消调用的结果会被丢弃。如果处理函数一秒内不响应取消,Worker 失败并停止接收新调用。Dever 管理进程与 stdin;自行在 Go 进程内调用 `Serve` 时,调用方负责关闭或解除自己的 `io.Reader` 阻塞。
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
@@ -34,23 +37,47 @@ SDK 用于把现成的 Python、JavaScript 或 Go 能力接入 Dever。应用先
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 同步原生调用不承诺协作取消;调用卡死、崩溃或超过 supervisor 期限时由现有进程监管终止整个 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 准入;本次源码改造不等于已部署或模型推理通过。
## 依赖与跨平台构建
`dever lib install <project>` 按现有锁恢复依赖,不重新选版本、不修改配置或锁。`dever-lock-v6` 记录 Python extras/wheel METADATA、npm 嵌套依赖及 peers/optional/bundled 依赖、Go 模块校验证据。原始包保留来源与文件名,构建记录保留完整输入,包括 npm 剪除可选构建依赖前的依赖图。
丢失的构建结果由同一沙箱重建,必须与记录的摘要一致。Python 原生 wheel 安装到配套解释器中,必须满足签名声明的标签、扩展 ABI 和动态库依赖要求。
丢失的构建结果由同一受管构建链重建,必须与记录的摘要一致。Python 原生 wheel 安装到配套解释器中,必须满足签名声明的标签、扩展 ABI 和动态库依赖要求。
生成 ARM64 应用时,先运行 `dever lib update <project> --target linux-aarch64`,再运行 `dever build <project> --target linux-aarch64 --output <new-file>`。Port 和 Worker 源码无需改写;锁、解释器、原生依赖和沙箱资源必须匹配目标平台。缺少目标资源时失败,不借用宿主运行环境。`run/test` 仍在本机执行。
生成 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 安装钩子。构建使用签名工具包和 Linux 沙箱,输入只读、网络关闭,写入仅限本次临时目录。缺少工具时不回退宿主 PATH。
只有显式准备 Lib 时才构建 Python sdist 或运行 npm 安装钩子。构建使用签名工具包,在独立临时工作目录内直接运行受管子进程;不再提供内层文件/网络隔离,因此只运行可信依赖及构建钩子。缺少工具时不回退宿主 PATH。
Python PEP 517 的构建依赖单独锁定,每个 hook 使用新进程,最终 wheel 按保留的元数据核对。npm 的配套运行器处理 `preinstall/install/postinstall`、依赖 bin 和 node-gyp。
构建记录绑定源码、目标运行环境、工具、构建前端、依赖图和输出摘要。Worker 准备阶段离线恢复结果,不重跑安装钩子。npm `.node` 产物须通过目标检查、动态加载检查和独立 Node 加载探测,才能进入只读 Worker 目录;线程和子进程也受此限制。不匹配平台或 ABI 的其他变体保留在锁定原始包中,不加入运行目录。运行时原生依赖只能来自 Worker 目录或签名 OS 库,不能借用编译器 sysroot。
构建记录绑定源码、目标运行环境、工具、构建前端、依赖图和输出摘要。Worker 准备阶段离线恢复结果,不重跑安装钩子。npm `.node` 产物须通过目标检查、动态加载检查和独立 Node 加载探测,才能进入 Worker 资源目录。不匹配平台或 ABI 的其他变体保留在锁定原始包中,不加入运行目录。运行时非系统依赖来自 Worker 目录,系统 ABI 由目标容器提供,不能借用编译器 sysroot。
生成的原生 addon 验证失败,整个构建就失败;使用纯 JS 备用路径不能证明原生构建成功。当前限制:
-45
View File
@@ -1,45 +0,0 @@
# Based on AppArmor 4.0 extras/bwrap-userns-restrict (GPL-2.0-only).
# Only signed static helpers published by the root-owned Dever installer attach.
# The exec transition is stacked under no_new_privs; child capabilities are denied.
# On restricted hosts, kernel.apparmor_restrict_unprivileged_unconfined=1 is
# mandatory BEFORE installation/loading: attachment paths do not constrain an
# explicit change_profile by an unconfined caller. Never enable this policy on
# such a host with that flag unset. Rollback removes this policy before restoring
# the previous strict flag. Dever never changes host sysctls automatically.
abi <abi/4.0>,
@{DEVER_SANDBOX_HASH} = [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]
profile dever-bwrap /opt/dever/sandbox/@{DEVER_SANDBOX_HASH}/bwrap flags=(attach_disconnected,mediate_deleted) {
allow capability,
allow file rwlkm /{**,},
allow network,
allow unix,
allow ptrace,
allow signal,
allow mqueue,
allow io_uring,
allow userns,
allow mount,
allow umount,
allow pivot_root,
allow dbus,
allow px /** -> dever-bwrap//&dever-bwrap-child,
}
profile dever-bwrap-child flags=(attach_disconnected,mediate_deleted) {
allow file rwlkm /{**,},
allow network,
allow unix,
allow ptrace,
allow signal,
allow mqueue,
allow io_uring,
allow userns,
allow mount,
allow umount,
allow pivot_root,
allow dbus,
allow pix /** -> &dever-bwrap-child,
audit deny capability,
}
+310
View File
@@ -0,0 +1,310 @@
#include "dever_ffi.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_BYTES (16U * 1024U * 1024U)
#define MAX_TOKENS 65536U
#define MAX_DEPTH 64U
typedef struct {
size_t start, end, after, count;
char kind;
} token;
typedef struct {
const char *data;
size_t length, cursor, used;
token *tokens;
} document;
static void whitespace(document *doc) {
while (doc->cursor < doc->length && strchr(" \t\r\n", doc->data[doc->cursor]) != NULL)
doc->cursor++;
}
static int string_end(document *doc) {
if (doc->data[doc->cursor++] != '"') return 0;
while (doc->cursor < doc->length) {
unsigned char byte = (unsigned char)doc->data[doc->cursor++];
if (byte == '"') return 1;
if (byte < 32) return 0;
if (byte == '\\') {
if (doc->cursor == doc->length) return 0;
byte = (unsigned char)doc->data[doc->cursor++];
if (byte == 'u') {
for (unsigned digit = 0; digit < 4; digit++)
if (doc->cursor == doc->length || !isxdigit((unsigned char)doc->data[doc->cursor++])) return 0;
} else if (strchr("\"\\/bfnrt", byte) == NULL) return 0;
}
}
return 0;
}
static int number_end(document *doc) {
if (doc->data[doc->cursor] == '-') doc->cursor++;
if (doc->cursor == doc->length) return 0;
if (doc->data[doc->cursor] == '0') doc->cursor++;
else {
if (doc->data[doc->cursor] < '1' || doc->data[doc->cursor] > '9') return 0;
while (doc->cursor < doc->length && isdigit((unsigned char)doc->data[doc->cursor])) doc->cursor++;
}
if (doc->cursor < doc->length && doc->data[doc->cursor] == '.') {
size_t start = ++doc->cursor;
while (doc->cursor < doc->length && isdigit((unsigned char)doc->data[doc->cursor])) doc->cursor++;
if (doc->cursor == start) return 0;
}
if (doc->cursor < doc->length && strchr("eE", doc->data[doc->cursor]) != NULL) {
doc->cursor++;
if (doc->cursor < doc->length && strchr("+-", doc->data[doc->cursor]) != NULL) doc->cursor++;
size_t start = doc->cursor;
while (doc->cursor < doc->length && isdigit((unsigned char)doc->data[doc->cursor])) doc->cursor++;
if (doc->cursor == start) return 0;
}
return 1;
}
static int value(document *doc, unsigned depth) {
whitespace(doc);
if (depth > MAX_DEPTH || doc->used == MAX_TOKENS || doc->cursor == doc->length) return 0;
token *item = &doc->tokens[doc->used++];
item->start = doc->cursor;
item->kind = doc->data[doc->cursor];
if (item->kind == '"') {
if (!string_end(doc)) return 0;
} else if (item->kind == '{' || item->kind == '[') {
char end = item->kind == '{' ? '}' : ']';
doc->cursor++;
whitespace(doc);
if (doc->cursor == doc->length) return 0;
if (doc->data[doc->cursor] != end) {
for (;;) {
whitespace(doc);
if (item->kind == '{') {
if (doc->cursor == doc->length || doc->data[doc->cursor] != '"' || !value(doc, depth + 1)) return 0;
whitespace(doc);
if (doc->cursor == doc->length || doc->data[doc->cursor++] != ':') return 0;
}
if (!value(doc, depth + 1)) return 0;
item->count++;
whitespace(doc);
if (doc->cursor == doc->length) return 0;
if (doc->data[doc->cursor] == end) break;
if (doc->data[doc->cursor++] != ',') return 0;
}
}
doc->cursor++;
} else {
const char *literal = item->kind == 't' ? "true" : item->kind == 'f' ? "false" : item->kind == 'n' ? "null" : NULL;
if (literal != NULL) {
size_t size = strlen(literal);
if (size > doc->length - doc->cursor || memcmp(doc->data + doc->cursor, literal, size) != 0) return 0;
doc->cursor += size;
} else if (!number_end(doc)) return 0;
}
item->end = doc->cursor;
item->after = doc->used;
return 1;
}
static int parse(document *doc, const char *data, size_t size) {
*doc = (document){.data = data, .length = size};
if (size == 0 || size > MAX_BYTES || memchr(data, 0, size) != NULL) return 0;
doc->tokens = calloc(MAX_TOKENS, sizeof(token));
if (doc->tokens == NULL) return 0;
if (value(doc, 0)) {
whitespace(doc);
if (doc->cursor == size) return 1;
}
free(doc->tokens);
doc->tokens = NULL;
return 0;
}
static dever_ffi_slice slice(const document *doc, size_t index) {
const token *item = &doc->tokens[index];
return (dever_ffi_slice){doc->data + item->start, item->end - item->start};
}
static int text_is(const document *doc, size_t index, const char *text) {
if (index >= doc->used || doc->tokens[index].kind != '"') return 0;
dever_ffi_slice part = slice(doc, index);
return part.size == strlen(text) + 2 && memcmp(part.data + 1, text, (size_t)part.size - 2) == 0;
}
static size_t field(const document *doc, const char *name) {
size_t found = MAX_TOKENS;
if (doc->tokens[0].kind != '{') return found;
for (size_t index = 1; index < doc->tokens[0].after;) {
size_t entry = doc->tokens[index].after;
if (text_is(doc, index, name)) {
if (found != MAX_TOKENS) return MAX_TOKENS;
found = entry;
}
index = doc->tokens[entry].after;
}
return found;
}
static int same(const document *left, size_t left_index, const document *right, size_t right_index) {
if (left_index >= left->used || right_index >= right->used) return 0;
const token *left_item = &left->tokens[left_index], *right_item = &right->tokens[right_index];
if (left_item->kind != right_item->kind || left_item->count != right_item->count) return 0;
if (left_item->kind == '[') {
for (size_t offset = 1; offset <= left_item->count; offset++)
if (!same(left, left_index + offset, right, right_index + offset)) return 0;
return 1;
}
dever_ffi_slice left_slice = slice(left, left_index), right_slice = slice(right, right_index);
return left_slice.size == right_slice.size && memcmp(left_slice.data, right_slice.data, (size_t)left_slice.size) == 0;
}
static int append(dever_ffi_output *output, dever_ffi_slice input) {
if (input.size > output->capacity - output->size) return 0;
memcpy(output->data + output->size, input.data, (size_t)input.size);
output->size += input.size;
return 1;
}
static int literal(dever_ffi_output *output, const char *text) {
return append(output, (dever_ffi_slice){text, strlen(text)});
}
static int send(dever_ffi_slice data) {
if (data.size == 0 || data.size > MAX_BYTES) return 0;
unsigned char size[4] = {(unsigned char)(data.size >> 24), (unsigned char)(data.size >> 16), (unsigned char)(data.size >> 8), (unsigned char)data.size};
return fwrite(size, 1, 4, stdout) == 4 && fwrite(data.data, 1, (size_t)data.size, stdout) == data.size && fflush(stdout) == 0;
}
static char *receive(size_t *length) {
unsigned char size[4];
if (fread(size, 1, 4, stdin) != 4) return NULL;
*length = ((uint32_t)size[0] << 24) | ((uint32_t)size[1] << 16) | ((uint32_t)size[2] << 8) | size[3];
if (*length == 0 || *length > MAX_BYTES) return NULL;
char *data = malloc(*length);
if (data == NULL) return NULL;
if (fread(data, 1, *length, stdin) != *length) { free(data); return NULL; }
return data;
}
static int identifier(const document *doc, size_t index, uint64_t *result) {
if (index >= doc->used) return 0;
dever_ffi_slice input = slice(doc, index);
uint64_t number = 0;
for (uint64_t offset = 0; offset < input.size; offset++) {
unsigned digit = (unsigned char)input.data[offset] - '0';
if (digit > 9 || number > ((uint64_t)INT64_MAX - digit) / 10) return 0;
number = number * 10 + digit;
}
*result = number;
return 1;
}
static int serve(const dever_ffi_handler *handler, const document *manifest, int *opened) {
size_t length = 0;
char *input = receive(&length);
document hello = {0};
char *buffer = malloc(MAX_BYTES), *returned = malloc(MAX_BYTES);
int okay = input != NULL && buffer != NULL && returned != NULL && parse(&hello, input, length);
const char *fields[] = {"port", "schema", "adapter", "capabilities", "operations"};
if (okay) okay = hello.tokens[0].count == 8 && text_is(&hello, field(&hello, "kind"), "hello") && text_is(&hello, field(&hello, "version"), "dever-component-1");
for (size_t index = 0; okay && index < 5; index++) okay = same(&hello, field(&hello, fields[index]), manifest, field(manifest, fields[index]));
size_t setting = okay ? field(&hello, "setting") : MAX_TOKENS;
if (okay) okay = setting < hello.used;
if (okay && handler->open != NULL) okay = handler->open(handler->context, slice(&hello, setting)) == DEVER_FFI_OK;
dever_ffi_output output = {buffer, MAX_BYTES, 0};
if (okay) {
*opened = 1;
okay = literal(&output, "{\"kind\":\"ready\",\"version\":\"dever-component-1\"");
for (size_t index = 0; okay && index < 5; index++)
okay = literal(&output, ",\"") && literal(&output, fields[index]) && literal(&output, "\":") && append(&output, slice(manifest, field(manifest, fields[index])));
okay = okay && literal(&output, "}") && send((dever_ffi_slice){output.data, output.size});
}
free(hello.tokens);
free(input);
uint64_t next_id = 1;
int cancelled = 0, stopped = 0;
while (okay && !stopped) {
input = receive(&length);
document message = {0};
okay = input != NULL && parse(&message, input, length);
if (!okay) { free(input); break; }
size_t kind = field(&message, "kind"), id_index = field(&message, "id");
uint64_t request_id = 0;
output.size = 0;
if (text_is(&message, kind, "shutdown")) {
okay = message.tokens[0].count == 1 && send((dever_ffi_slice){"{\"kind\":\"shutdown\"}", 19});
stopped = 1;
} else if (text_is(&message, kind, "health")) {
okay = message.tokens[0].count == 2 && identifier(&message, id_index, &request_id) && request_id == 0 && send((dever_ffi_slice){input, length});
} else if (text_is(&message, kind, "cancel")) {
okay = message.tokens[0].count == 2 && identifier(&message, id_index, &request_id) && request_id > 0 && request_id == next_id - 1 && !cancelled;
cancelled = 1;
} else if (text_is(&message, kind, "call")) {
size_t operation = field(&message, "operation"), payload = field(&message, "payload");
okay = message.tokens[0].count == 4 && identifier(&message, id_index, &request_id) && request_id == next_id && payload < message.used && operation < message.used && message.tokens[operation].kind == '"';
size_t operations = field(manifest, "operations");
int found = 0;
for (size_t index = operations + 1; okay && index < manifest->tokens[operations].after; index = manifest->tokens[index].after)
if (same(&message, operation, manifest, index)) found = 1;
okay = okay && found;
if (okay) {
next_id++;
cancelled = 0;
dever_ffi_slice operation_name = slice(&message, operation);
operation_name.data++;
operation_name.size -= 2;
dever_ffi_output result = {returned, MAX_BYTES - 256, 0};
int status = handler->invoke(handler->context, operation_name, slice(&message, payload), &result);
document result_doc = {0};
okay = result.data == returned && result.capacity == MAX_BYTES - 256 && result.size <= result.capacity && parse(&result_doc, returned, (size_t)result.size);
if (okay && status == DEVER_FFI_OK) {
okay = literal(&output, "{\"kind\":\"result\",\"id\":") && append(&output, slice(&message, id_index)) && literal(&output, ",\"payload\":") && append(&output, (dever_ffi_slice){returned, result.size});
} else if (okay && status == DEVER_FFI_ERROR) {
size_t error = field(&result_doc, "error"), error_payload = field(&result_doc, "payload");
okay = result_doc.tokens[0].count == 2 && error < result_doc.used && result_doc.tokens[error].kind == '"' && error_payload < result_doc.used;
okay = okay && literal(&output, "{\"kind\":\"error\",\"id\":") && append(&output, slice(&message, id_index)) && literal(&output, ",\"error\":") && append(&output, slice(&result_doc, error)) && literal(&output, ",\"payload\":") && append(&output, slice(&result_doc, error_payload));
} else okay = 0;
okay = okay && literal(&output, "}") && send((dever_ffi_slice){output.data, output.size});
free(result_doc.tokens);
}
} else okay = 0;
free(message.tokens);
free(input);
}
free(buffer);
free(returned);
return okay && stopped;
}
int dever_ffi_serve(const dever_ffi_handler *handler) {
if (handler == NULL || handler->abi != DEVER_FFI_ABI_V1 || handler->invoke == NULL) return 1;
FILE *file = fopen("contract.json", "rb");
if (file == NULL) return 1;
char *data = malloc(MAX_BYTES + 1);
if (data == NULL) { fclose(file); return 1; }
size_t size = fread(data, 1, MAX_BYTES + 1, file);
int okay = !ferror(file) && size <= MAX_BYTES;
fclose(file);
document manifest = {0};
okay = okay && parse(&manifest, data, size);
if (okay) {
size_t operations = field(&manifest, "operations");
okay = operations < manifest.used && manifest.tokens[operations].kind == '[' && manifest.tokens[operations].count > 0;
const char *names[] = {"port", "schema", "adapter"};
for (size_t index = 0; okay && index < 3; index++) {
size_t entry = field(&manifest, names[index]);
okay = entry < manifest.used && manifest.tokens[entry].kind == '"';
}
size_t capabilities = field(&manifest, "capabilities");
okay = okay && capabilities < manifest.used && manifest.tokens[capabilities].kind == '[';
}
int opened = 0;
okay = okay && serve(handler, &manifest, &opened);
if (opened && handler->close != NULL) handler->close(handler->context);
free(manifest.tokens);
free(data);
if (!okay) fputs("native FFI Worker failed\n", stderr);
return okay ? 0 : 1;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef DEVER_FFI_H
#define DEVER_FFI_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define DEVER_FFI_ABI_V1 1
#define DEVER_FFI_OK 0
#define DEVER_FFI_ERROR 1
#define DEVER_FFI_FAULT 2
typedef struct {
const char *data;
uint64_t size;
} dever_ffi_slice;
typedef struct {
char *data;
uint64_t capacity;
uint64_t size;
} dever_ffi_output;
typedef struct {
uint32_t abi;
void *context;
int (*open)(void *context, dever_ffi_slice setting);
int (*invoke)(void *context, dever_ffi_slice operation, dever_ffi_slice payload, dever_ffi_output *output);
void (*close)(void *context);
} dever_ffi_handler;
int dever_ffi_serve(const dever_ffi_handler *handler);
#ifdef __cplusplus
}
#endif
#endif
+1 -1
View File
@@ -54,7 +54,7 @@ def packages(manifest):
if kind["type"] == "target":
if target == manifest["platform"]:
raise ValueError("host native resources belong to the base package")
prefixes = (f"runtime/{target}/", f"sandbox/{target}/")
prefixes = (f"runtime/{target}/", f"process-runtime/{target}/")
else:
prefixes = (f"{kind['type']}/{kind['ecosystem']}/{target}/",)
if any(not artifact["path"].startswith(prefixes) for artifact in extension["artifacts"]):