☰
Kiln 跨平台 spawn 沙箱发布检查清单:macOS / Windows / Linux 与 PyInstaller 冻结构建验证指南
2026/10/15 17:17:29 网站建设 项目流程
  • 人工智能
  • AI 应用
  • AI 评测
  • RAG
  • 微调
  • 提示工程
  • 开发工具
  • 桌面应用

【免费下载链接】Kiln

Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.

项目地址:https://gitcode.com/gh_mirrors/ki/Kiln
点击查看免费下载

导读

本文围绕 Kiln 仓库中 code_tools 项目的跨平台 spawn 检查清单,系统讲解 Kiln 代码工具(code tools)与代码评测(code evals)共享的多进程 spawn 基础设施:进程级_spawn_lock、__main__轻量桩替换、同步/异步入口点统一调用,以及 macOS 已验证、Windows 与 Linux 待验证的三平台发布流程。读完本文,你将掌握 Kiln 沙箱执行的底层原理,并能照章完成 PyInstaller 冻结构建在各平台的发布前验证,规避 PyInstaller bug #7410 等已知陷阱。

一、背景:为什么 code tools 与 code evals 必须共享同一套 spawn 基础设施

在 Kiln 中,代码工具(code tools)和代码评测(code evals)是两类核心能力:

  • code tools:用户编写 Python 代码作为工具(实现一个def run()),供 Agent 在任务中调用,由 PythonCodeTool 负责在沙箱子进程中执行;
  • code evals:用户编写打分器(实现def score()),对模型输出进行评估,由 v2_eval_code_eval 适配器 负责执行。

两者虽然职责不同,但共享同一套多进程 spawn 基础设施,即 spawn.py。这正是该检查清单存在的根本原因:任何一个发布版本只要包含 code tools 或 code evals,就必须在目标平台上验证这套共享设施。

从源码结构看,这种共享是刻意设计的:kiln_ai.sandbox模块被设计为stdlib-only(只依赖 Python 标准库,不引入 Pydantic、数据库、UI 等重依赖),因此子进程可以轻量启动。检查清单在发布前需要逐平台确认的,正是这套共享设施在 macOS、Windows、Linux 以及 PyInstaller 冻结构建下的正确性。

二、共享基础设施四要素(源码级解析)

检查清单开篇列举了四项共享基础设施,全部位于 libs/core/kiln_ai/sandbox/ 目录。下面结合源码逐一深入。

2.1_spawn_lock:进程级全局锁,对抗 PyInstaller bug #7410

spawn.py 中定义:

_spawn_lock = threading.Lock()

这是一把进程级全局锁,由 code-evals(run_scorer)和 code tools(PythonCodeTool)共同持有。两个功能路径最终都通过 run_bridged_child 发起 spawn,并在其中调用start_process_with_light_main,从而串行化p.start()前后的__main__桩替换窗口。

为什么必须共用同一把锁?PyInstaller bug #7410 是并发 spawn 导致的崩溃:在冻结构建中,多个进程同时启动时可能出现竞态。锁的共享性由测试 test_sandbox_shared.py 中的TestSpawnLockIdentity显式断言:

def test_spawn_lock_shared_with_eval(self): """Code evals and code tools share the same _spawn_lock (PyInstaller #7410).""" assert spawn_mod._spawn_lock is _spawn_lock def test_bridge_delegates_to_shared_spawn_helper(self): """The shared bridge spawns via start_process_with_light_main from sandbox.spawn.""" assert sandbox_bridge.start_process_with_light_main is start_process_with_light_main

test_code_eval_bridge.py中也有同样的同源性断言(code_tool.run_bridged_child is sandbox_bridge.run_bridged_child、v2_eval_code_eval.run_bridged_child is sandbox_bridge.run_bridged_child),确保两条调用链解析到同一个共享实现。

2.2start_process_with_light_main:__main__轻量桩替换

spawn.py 中的start_process_with_light_main(p)是这套基础设施的技术核心。其原理是:

  1. Python 的multiprocessing.spawn.get_preparation_data会读取sys.modules["__main__"],决定子进程需要重新执行什么;
  2. 若不处理,子进程会重新导入父进程沉重的__main__模块——例如desktop.py会传递引入 litellm、google-cloud 等重依赖,导致子进程启动缓慢甚至失败;
  3. 该函数在p.start()之前,将sys.modules["__main__"]替换为一个轻量桩模块(types.ModuleType("__main__"),不带__file__、不带__spec__),于是init_main_from_name与init_main_from_path都不会被触发,子进程完全跳过重型重新导入;p.start()结束后在finally中恢复真实主模块。

实现中有三个值得注意的细节:

  • 桩模块命名为"__main__"而非"__mp_main__"——后者会与 multiprocessing 内部主模块名冲突;
  • 桩替换发生在multiprocessing.spawn现有的 bootstrap 流程之内,因此freeze_support()与 PyInstaller 冻结构建继续正常工作;
  • 线程安全上,替换窗口是进程级的,但只有亚毫秒级(仅覆盖p.start()),且被_spawn_lock串行化,而请求处理代码不读取__main__,因此跨线程可见性是无害的。

2.3call_entrypoint:同步 / 异步入口点统一处理

entrypoint.py 中的call_entrypoint(fn, kwargs)同时处理同步与异步入口点:

def call_entrypoint(fn: Callable, kwargs: dict) -> Any: result = fn(**kwargs) if inspect.iscoroutine(result): result = asyncio.run(result) return result

如果fn是普通函数,返回值原样返回;如果fn是async def(或任何原因返回协程),则用asyncio.run()在子进程主线程中驱动协程完成。该函数被 code-eval 打分器和 code-tool 的run()入口点共用。

test_sandbox_shared.py 的TestCallEntrypoint对同步、异步、asyncio.gather、异常传播等路径做了完整覆盖——包括asyncio.run()嵌套调用会报错的约束测试。

2.4 spawn 上下文:显式get_context("spawn"),绝不用 fork

子进程使用multiprocessing.get_context("spawn")显式创建(绝不用 fork),并设置daemon=True。这一要求在实际代码中有两处体现:

  • sandbox_bridge.py 在run_bridged_child中用 spawn 上下文创建两条 IPC 队列;
  • sandbox_bridge.py 在_pump中用ctx.Process(target=..., daemon=True)创建子进程,并通过loop.run_in_executor(executor, start_process_with_light_main, p)在专用线程池中启动。

为什么必须在 Linux 上显式指定 spawn?fork 在存在多线程的应用中不安全(可能死锁),而 spawn 在三个平台上行为一致。从代码结构看,daemon=True保证了子进程随应用退出而结束,避免僵尸进程泄漏。

三、macOS:已验证(2026-07-05)

检查清单明确标注:macOS 已在本机验证(2026-07-05)。验证方式是在该平台上完整运行测试套件,包括 test_code_tool_execution.py、test_sandbox_shared.py 以及 code-eval 套件,确认了以下能力:

验证项对应源码/测试
同步与异步run()入口点TestChildSyncRun/TestChildAsyncRun(test_code_tool_execution.py)
嵌套工具调用(IPC 队列)ToolCallBridge(tools_api.py)
超时与崩溃处理_pump中的 deadline、p.kill()、BridgeResult(timed_out/crashed)
_spawn_lock下的并发 spawnTestSpawnLockIdentity
stdout/stderr 捕获worker.py 的io.StringIO重定向
返回值序列化_serialize_result(str 直传,JSON 类型json.dumps,其余报错)

关于返回值序列化,worker.py 的规则是:str原样传递;dict/list/int/float/bool/None通过json.dumps(ensure_ascii=False)序列化;其他类型抛出明确的 TypeError。

四、Windows:发布前必须验证

Windows 目前是待验证状态([ ] Must verify before release),核心关注点有三:

1.freeze_support()必须先于任何 spawn 进程创建执行

multiprocessing.freeze_support()在应用入口点中被调用。检查清单中给出的位置是desktop.py(原记 line 186)与dev_server.py(原记 line 25);以当前仓库源码为准,实际位置分别是:

  • desktop.py:__main__中先调用multiprocessing.freeze_support(),紧接着multiprocessing.set_start_method("spawn", force=True);
  • dev_server.py:同样的两行调用。

关键约束:code tools 自身不调用freeze_support(),它们依赖应用入口点已经调用过。没有freeze_support(),Windows 上 PyInstaller 冻结的可执行文件使用 multiprocessing 会陷入无限循环。此外set_start_method("spawn", force=True)强制所有平台使用 spawn(Windows/macOS 本就默认 spawn,Linux 上 fork 是默认且不安全)。

2.start_process_with_light_main保持在 spawn bootstrap 内

由于__main__桩替换发生在multiprocessing.spawn现有的 bootstrap 流程内部,freeze_support()的兼容性不受影响。这一点在 spawn.py 的 docstring 中明确说明,并在 test_sandbox_bridge.py 的桥接池测试中通过录制run_in_executor提交来断言("start_process_with_light_main" in submitted)。

3. PyInstaller 冻结构建验证

需要在 Windows 构建机上验证 PyInstaller 冻结构建能否为 code tools spawn 子进程(与 code-evals 相同的验证标准),并确认_spawn_lock能防止 Windows 下 PyInstaller 的并发 spawn 问题。

五、Linux:发布前必须验证

Linux 同样是待验证状态([ ] Must verify before release),核心关注点:

  • 与 Windows 相同的freeze_support()与_spawn_lock要求;
  • PyInstaller bug #7410(并发 spawn 崩溃)最初就是在 Linux 上报告的——因此 Linux 是_spawn_lock最关键的平台;
  • 验证__main__桩替换在 Linux 冻结构建中工作正常。

仓库中还保留了一个专门的回归基准 _heavy_main_bench.py:其 docstring 明确指出"这正是该基准要捕获的 bug——打分器现在通过run_bridged_childspawn……以保留修复"。基准入口同样先调用multiprocessing.freeze_support()(见该文件第 75 行),可用于在 Linux 上复现/回归 PyInstaller #7410 场景。

六、冻结构建(PyInstaller)逐平台验证流程

检查清单为 macOS、Windows、Linux 三个平台给出了统一的六步验证流程,每一步对应一个已实现的机制:

  1. 用 PyInstaller 构建冻结应用——验证构建产物本身可启动;
  2. 启动应用并创建一个简单的def run()返回字符串的 code tool——对应TestChildSyncRun.test_sync_run_returns_string('def run(x):\n return "hello " + x\n');
  3. 从 UI 测试面板测试该 code tool——验证完整 UI → API → sandbox 链路;
  4. 创建一个调用其他工具(例如演示用数学工具)的 code tool 并测试——这一步专门验证嵌套工具调用 IPC 层(通过两条队列requests/responses实现tool_call/list_tools消息往返,见 tools_api.py 的ToolCallBridge);
  5. 验证 code-evals 仍然工作——共享_spawn_lock的回归检查(对应TestSpawnLockIdentity的同一性断言);
  6. 尽可能并发运行两个测试——验证 spawn 锁正确串行化p.start()。

其中第 4 步是 code tools 相对 code-evals 的新增核心验证点:code-evals 的冻结构建验证已经覆盖了大部分 spawn 基础设施,code tools 在其上叠加了嵌套工具调用 IPC 层,这是需要重点验证的新东西。

七、附注:stdout/stderr 重定向的"一石二鸟"

检查清单的 Notes 部分提到:stdout/stderr 在子进程中被重定向到StringIO。这在两个层面发挥作用:

  1. 捕获用户输出:worker.py(code tools)与 sandbox_worker.py(code evals)在进入用户代码前将sys.stdout/sys.stderr换成io.StringIO(),结束后恢复,输出随结果消息一起回传,且超过 64 KB 会被截断(_TRUNCATION_LIMIT = 64 * 1024);
  2. None-stdout 防护:在 PyInstaller--windowed构建中(macOS.app和 Windows GUI 构建),sys.stdout可能是None,用户代码一旦打印就会崩溃。提前重定向恰好规避了这个问题。

call_entrypoint、child_main/execute_scorer_bridged所在的三个沙箱模块全部保持 stdlib-only,这是子进程能够轻量、安全启动的前提。

八、完整执行链路速览

综合源码,一次 code tool 调用的完整链路是:

PythonCodeTool.run() → _invoke(): 构建 NestedToolServer(allowlist、project、task、recorder) → run_bridged_child(): 深度上限检查(>=10 直接报错,不 spawn) → 深度 0 时获取共享信号量(CODE_SANDBOX_MAX_CONCURRENCY = 16) → get_context("spawn") 创建两条队列 → _pump(): ctx.Process(daemon=True) → start_process_with_light_main(p)(_spawn_lock + __main__ 桩替换) → 轮询 requests 队列,tool_call/list_tools 分发给 NestedToolServer → 超时 p.kill(),崩溃返回 exit_code → 子进程 child_main(): exec(code) → run() → call_entrypoint() → ToolCallBridge 通过两条队列完成嵌套工具调用

相关实现位于 code_tool.py、sandbox_bridge.py、spawn.py、worker.py。Code eval 侧则走 v2_eval_code_eval.py → 同样的run_bridged_child→ execute_scorer_bridged,共享同一把锁、同一个桥、同一个并发池。

九、测试与验证资源索引

发布前可重点复用的测试与基准:

  • test_code_tool_execution.py:code tool 执行引擎全量测试(真实 spawn 子进程),覆盖同步/异步、超时、崩溃、嵌套调用、并发;
  • test_sandbox_shared.py:共享基础设施测试——_spawn_lock同一性、桥委托关系、call_entrypoint同步/异步路径、打分器走共享桥的回归;
  • test_sandbox_bridge.py:桥接池行为测试,断言 spawn/poll/join 都发生在专用kiln-sandbox-bridge线程池而非默认执行器上;
  • test_code_eval_bridge.py:code tool 与 code eval 解析到同一run_bridged_child与同一start_process_with_light_main的引用同一性测试;
  • _heavy_main_bench.py:针对 PyInstaller #7410 的回归基准(含freeze_support()调用)。

在 Windows 与 Linux 构建机上,建议以"完整测试套件 + 上述六步冻结构建验证"双轨并行,待两项全部通过后再发布包含 code tools / code evals 的版本。

  • 人工智能
  • AI 应用
  • AI 评测
  • RAG
  • 微调
  • 提示工程
  • 开发工具
  • 桌面应用

【免费下载链接】Kiln

Build, Evaluate, and Optimize AI Systems. Includes evals, RAG, agents, fine-tuning, synthetic data generation, dataset management, MCP, and more.

项目地址:https://gitcode.com/gh_mirrors/ki/Kiln
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询