ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DORA 运行时拆分:`dora-runtime-api` SDK 与 OperatorRunner 多后端架构实战解析

DORA 运行时拆分:`dora-runtime-api` SDK 与 OperatorRunner 多后端架构实战解析 DORA 运行时拆分dora-runtime-apiSDK 与 OperatorRunner 多后端架构实战解析【免费下载链接】doraDORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed dataflow capabilities. Applications are modeled as directed graphs, also referred to as pipelines.项目地址: https://gitcode.com/GitHub_Trending/do/dora导读本文以 DORADataflow-Oriented Robotic Architecture仓库中关于运行时架构重构的设计文档为主体完整解析将单体dora-runtime拆分为语言中立运行时 SDK 各语言后端 crate的方案dora-runtime-api承载操作员事件循环与后端抽象dora-runtime-shared-lib与dora-runtime-python分别以 libloading/C-ABI 和 PyO3 实现两种语言后端daemon 则通过表驱动的spawn/runtime_registry.rs依据OperatorSource::runtime_name()统一决策启动命令。读完本文你将掌握 DORA 运行时的分层结构、后端的接入方式、启动命令的复现逻辑含 #1797/#1805 修复以及未来通过runtimes:map 扩展第三方运行时的接缝位置。一、背景为什么要把单体dora-runtime拆开拆分前的dora-runtime是一个全能单体 crate它通过pythoncargo feature 和大量#[cfg(feature python)]条件编译同时容纳 C-ABI 共享库操作员与 Python 操作员两套执行路径。这种做法的代价是依赖耦合只要开启 Python 支持整个运行时就必须链接 pyo3dora-cli作为 daemon 的宿主二进制也因此被拖入 pyo3 依赖树新增语言成本高每支持一种新语言都要在同一个 crate 里继续叠加条件编译分支feature 组合爆炸、维护困难无法按需裁剪嵌入式部署例如纯 Python 进程内运行 daemon与原生二进制部署需要完全不同的后端组合单体结构无法独立演进。这次重构的核心约束是行为保持对用户而言没有任何 descriptor/YAML 变更数据流配置文件、节点声明方式与历史版本完全一致仅内部实现被重组。这一点也是验证重构是否成功的第一标准。二、新架构总览一个 SDK两个内置后端拆分后运行时被组织为三个独立 cratecrate角色启动方式关键依赖dora-runtime-api语言中立运行时 SDK由各后端调用其main(runner)入口dora-node-api、dora-message、tokiodora-runtime-shared-libC-ABI / 共享库后端doraCLI 的dora runtime子命令libloadingdora-runtime-pythonPythonPyO3后端wheel 中的dora.start_runtime()pyo3三者均位于binaries/目录下对应 binaries/runtime-api、binaries/runtime-shared-lib、binaries/runtime-python。拆分后的关键收益之一dora-cli不再把 pyo3 拉入依赖树Python 后端随 wheel 发布原生二进制不再需要 Python 支持同时pythoncargo feature 与所有#[cfg(feature python)]分发逻辑被整体移除新增语言的标准动作从改单体加条件编译变成新增一个实现OperatorRunnertrait 的 crate。需要说明的是dora-runtime-api在 Cargo.toml 与源码 lib.rs 中明确标注为内部 crate不属于公共 API它仅因 cargo 要求发布 crate 的依赖必须可发布而被发布到 crates.io不享受 DORA 1.0 稳定性保证任何版本含 patch都可能变化。直接依赖它需要自行承担风险相关说明见 docs/api-rust.md 的 Stability scope at 1.0 一节。三、dora-runtime-api语言中立的运行时 SDK这是整个拆分的枢纽。它提供四类核心抽象1.main(runner)进程入口lib.rs 中的pub fn main(runner: impl OperatorRunner)是运行时进程的通用入口流程如下从环境变量DORA_RUNTIME_CONFIG读取序列化后的RuntimeConfig含节点配置与操作员定义缺失时报 env variable DORA_RUNTIME_CONFIG must be set解析 dataflow descriptor校验操作员数量空报no operators多于一个报multiple operators are not supported——每个运行时进程当前只托管单个操作员构建 tokio 多线程运行时worker_threads(1)。源码注释明确必须用 multi-thread scheduler因为 Zenoh 在分布式跨 daemon 通信的会话初始化阶段会派生后台任务current_thread会 panic在独立线程上block_on语言中立事件循环同时在主线程上调用runner.run_operator(...)——PyO3 与 libloading 都要求专用线程持有后端返回的RunnerGuard直到事件循环 join 完成最后释放对共享库后端即卸载.so。2.OperatorRunnertrait后端接入的唯一契约operator.rs 定义了后端必须实现的 traitpub trait OperatorRunner { fn run_operator( self, node_id: NodeId, operator: OperatorDefinition, incoming_events: flume::ReceiverEvent, handle: RuntimeHandle, init_done: oneshot::SenderResult(), dataflow_descriptor: Descriptor, ) - eyre::ResultRunnerGuard; }runtime↔operator 的契约是语言无关的后端从incoming_events消费dora_node_api::Event通过handle发送输出与生命周期事件并通过init_done恰好一次地报告初始化成功或失败。一个关键约定对应 issue #2595后端若无法托管某类操作员必须返回Err且不发送init_done使失败以 spawn 错误形式呈现而不是让运行时任务在init_done.await上挂死。3.RuntimeHandle与所有权关键设计RuntimeHandle 是操作员侧与运行时通信的通道核心方法是send_output。它的实现体现了修复 issue #2742 的所有权不变量操作员的 Arrow 数组只在操作员线程上被借用随即编码进 DORA 自有 sampleallocator.encode_arrow(array)从不跨通道发送原始数组释放数组内存可能需要其语言运行时例如pyarrow数组背后的 numpy buffer 释放时需要持有 GIL若把数组本身送给运行时事件循环操作员持锁期间会卡死整个循环SharedAllocatorArcOnceLockSampleAllocator解决了时序问题操作员线程先于DoraNode::init启动节点就绪后才能拿到分配器因此用OnceLock在节点创建后立即填充保证操作员的第一次send_output就有编码去处。operator.rs内附单元测试直接断言了该不变量send_output_releases_the_operator_payload_before_it_crosses_the_channel以及节点未初始化时发送输出返回清晰错误而非 panic。4. 事件循环与诊断机制run()函数lib.rs是语言中立的运行时主循环合并操作员事件与 daemon 事件两条流处理Output、Error、Panic、Finished、Stop、Reload、Input、InputClosed等事件。两个值得注意的设计stall 看门狗每 2 秒检查主循环是否在某事件上停滞超过 3 秒LoopActivity并打印正在处理的事件种类。这在 Windows 上尤为关键——CTRL_BREAK_EVENT无法像 UnixSIGTERM那样中断 native 代码卡死的操作员只能等待强杀看门狗能指明它停在哪里发送期间不失聪await_send_watching_for_stop在等待 in-flight 输出发送时继续消费事件流Stop立即转发给操作员其余事件缓冲上限MAX_BUFFERED 4避免把共享内存映射的输入全部拉进内存待发送完成后按序处理。对应单元测试见 lib.rs。四、两个内置后端1.dora-runtime-shared-liblibloading / C-ABI 后端该后端加载.so/.dll/.dylib操作员由 daemon 以dora runtime子命令启动。入口极简lib.rspub fn main() - eyre::Result() { dora_runtime_api::main(SharedLibRunner) }runner.rs 实现加载细节若 source 是 URL 则先下载到build/目录source_is_url判断否则用adjust_shared_library_path规范化路径通过 libloading 解析三个必需符号dora_init_operator、dora_drop_operator、dora_on_eventWindows 下符号缺失会给出明确提示在extern C的 send-output 闭包内容错解析输出 idDataId::from遇到非法字符会 panic而这里运行在 FFI trampoline 里panic 会跨越 FFI 边界导致整个进程 abort因此改为返回错误加载的libloading::Library作为RunnerGuardOptionBoxdyn Any交回main()虽然 #2742 后事件循环不再持有.so导出的 Arrow 数组但OperatorEvent::Panic的 payload 等值仍可能携带.so内的 vtable必须等主循环 join 后才能卸载否则释放时跳入未映射内存SIGSEGV。后端对不支持的 source 返回描述性错误Python 操作员误入此后端会报 uses a Python source, but this is the shared-library runtimeWASM 报 not supported yet测试见 lib.rs。2.dora-runtime-pythonPyO3 后端并委托托管 native 操作员这是唯一链接 pyo3 的运行时 crate随 wheel 发布由 daemon 通过python -uc import dora; dora.start_runtime()启动Python 侧的start_runtime绑定见 apis/python/node/src/lib.rs。runner.rs 的职责包括把模块父目录加入sys.path、导入模块并实例化其中的Operator类、通过pythonize注入dataflow_descriptor属性支持热重载importlib.reload后保留操作员__dict__状态将send_output回调暴露为#[pyclass]接受PyBytes或 pyarrow 数组零拷贝并在py.detach中编码输出——编码不需要 GIL这样操作员持锁时事件循环不会因PyAcquireGIL而阻塞。一个承载关键行为的分支PythonRunner对OperatorSource::SharedLibrary以及Wasm不是拒绝而是委托给SharedLibRunnerlib.rs。这是 load-bearing 的当 daemon 本身就是嵌入式 Python 进程时见下一节native 运行时节点也会被路由到python -uc import dora; dora.start_runtime()此时 wheel 里的运行时必须有能力 dlopen 共享库。拆分前的dora-runtime因为无条件编译 shared-library 后端所以天然支持该场景拆分后靠这条委托链路保持。对应测试shared_library_source_is_delegated_not_rejected验证了委托行为。五、daemon 侧的表驱动启动spawn/runtime_registry.rs这是本次重构在 daemon 侧的核心落地。新的 spawn/runtime_registry.rs 取代了原先分散的#[cfg(feature python)]分发逻辑。1.OperatorSource::runtime_name()单一事实来源libraries/message/src/descriptor.rsL1292-L1361为OperatorSource枚举SharedLibrary/Python/Wasm定义了三个运行时名常量与一个映射方法pub const RUNTIME_SHARED_LIBRARY: str shared-library; pub const RUNTIME_PYTHON: str python; pub const RUNTIME_WASM: str wasm; pub fn runtime_name(self) - static str { /* 按枚举变体返回对应常量 */ }daemon 的 spawn 逻辑与 CLI 的构建哈希都基于该名称做决策而不是逐个匹配枚举变体——这就是表驱动的含义也是后续扩展第三方运行时名的挂载点。2.runtime_command按运行时族路由runtime_command接收运行时节点的全部操作员全部为 Python →python_runtime_command全部为非 Pythonnative/WASM→native_runtime_command混合 → 直接bail!Cannot spawn runtime with both Python and non-Python operators要求单一操作员或全部 Python。3.python_runtime_command复现历史启动命令python_runtime_command完整复现拆分前的行为同时保留多操作员的限制PyO3 子解释器尚未就绪一个 runtime 进程只支持一个 Python 操作员。三种解释器来源conda 环境操作员声明了conda_env时使用conda run -n env python -uc import dora; dora.start_runtime()找不到 conda 时报错uv 模式若构建时准备了 managed Python 环境python_env_dir优先复用该解释器以保证操作员所见依赖与构建一致否则uv run python -uc ...普通模式get_python_path()解析系统 Pythonpython -uc import dora; dora.start_runtime() # node_id。无论哪种来源都强制-u-uc以刷新 stdout/stderr 缓冲。4.native_runtime_command三个分支复现 #1797/#1805 修复native_runtime_command通过检查std::env::current_exe()的文件名做三分支路由current_exe以python/python3结尾daemon 是嵌入式 Python 进程即python -c import dora; dora.start_daemon()场景→ 仍用python -uc import dora; dora.start_runtime()启动。这正是上一节所述委托链路的触发条件该命令落在dora-runtime-python上后者再委托SharedLibRunner托管 native 操作员current_exe名为dora→ 直接使用当前二进制 runtime参数对应 #1797 修复spawn 出的 runtime 与 daemon 版本始终一致其它情况例如嵌入式 example runner 通过dora_cli::run()调用见 examples/c-dataflow/run.rs→ 回退到 PATH 查找dora二进制再附加runtime对应 #1805 修复直接复用current_exe会递归进入 example runner 自身。这三条分支逐字复现了拆分前的启动命令保证了行为保持。六、扩展第三方运行时的接缝未来的runtimes:mapruntime_registry.rs模块文档L1-L8明确说明今天它只把两个内置运行时python与shared-library映射到各自的启动命令但它就是未来第三方运行时解析器的唯一接缝——由 descriptor 中的runtimes:map 驱动调用方传入操作员该模块决定由哪个 launcher 托管它们。也就是说新增一种语言运行时 实现OperatorRunnertrait 在 registry 中注册一条名称到启动命令的映射daemon 侧无需再改动事件循环或 spawn 主流程。七、发布与 CI 基建的配套调整1. 发布顺序两个 release workflow 将已删除的dora-runtime替换为dora-runtime-apidora-runtime-shared-libdora-runtime-python且发布顺序必须保证dora-cli之前——因为dora-cli依赖 shared-lib 后端cargo 要求每个已发布 crate 的依赖都可发布。2. 测试排除dora-runtime-python的原因dora-runtime-python与dora-cli-api-python一起被排除在cargo test --all之外原因值得注意它是一个纯 rlib不是extension-modulecdylib因此它的测试二进制会链接 libpython而 CI 的测试任务运行时不带setup-python。不过它的 lib 仍会在 CI 中作为dora-cli-api-python的依赖被正常构建编译覆盖不因此缺失。3. 验证清单设计文档给出的验证命令覆盖了正确性、质量与端到端三层cargo test --all仅剩两个已知的容器本地rmw_zenoh_pubsub失败二者需要组播网络与本次重构无关clippy --all -D warnings与fmt --check静态质量门槛cargo check --examples示例工程编译通过make qa-fast仓库 QA 快速套件一个端到端 shared-library operator 数据流daemon → runtime → dlopen 操作员 → sink全链路通过。这些验证共同确认拆分后的新运行时在无 descriptor/YAML 变更的前提下行为与拆分前完全一致。八、小结DORA 的运行时拆分是一次典型的内部重构、外部零感知架构演进分层清晰语言无关的dora-runtime-api提供事件循环、节点 harness 与OperatorRunnertraitdora-runtime-shared-liblibloading与dora-runtime-pythonPyO3各自实现后端Python 后端还通过委托同时托管 native 操作员保住嵌入式 Python daemon 场景决策表驱动daemon 依据OperatorSource::runtime_name()在 spawn/runtime_registry.rs 中路由启动命令逐字复现历史命令并保留 #1797/#1805 修复可扩展runtime_registry.rs成为未来runtimes:map 第三方运行时的唯一接缝新增语言不再触碰事件循环工程配套完整发布顺序、CI 测试排除策略与多层级验证清单一并落地。对想深入源码的读者建议按此顺序阅读libraries/message/src/descriptor.rs运行时名映射→ binaries/daemon/src/spawn/runtime_registry.rs启动路由→ binaries/runtime-api/src/operator.rstrait 与契约→ binaries/runtime-api/src/lib.rs事件循环→ 两个后端的 runnershared-lib / python。【免费下载链接】doraDORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed dataflow capabilities. Applications are modeled as directed graphs, also referred to as pipelines.项目地址: https://gitcode.com/GitHub_Trending/do/dora创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表