11. Task Runner:Code 节点与执行隔离
11.1 为什么 Code 节点需要单独边界
普通节点的代码由 n8n 开发者打包,Code 节点的 JavaScript/Python 来自工作流作者。它可能访问大对象、执行循环、调用语言运行时 API 或加载用户允许的依赖,因此不能把“用户代码”当成普通节点 class 在主进程里直接调用。
n8n 在源码中把 task runner、task broker、JavaScript/Python sandbox 和代码节点适配拆开:
11.2 主要模块
packages/@n8n/task-runner:任务运行器协议、broker/client 和运行时。packages/@n8n/task-runner-python:Python 任务执行支持。packages/cli/src/task-runners/task-broker:main/worker 侧任务 broker server。packages/nodes-base/nodes/Code:Code 节点 description、输入输出校验、sandbox 与错误包装。packages/@n8n/expression-runtime:表达式的受限运行环境。
Code 节点目录同时包含 JavaScriptSandbox.ts、PythonTaskRunnerSandbox.ts、JsTaskRunnerSandbox.ts 等实现,说明 n8n 仍需要兼容不同执行后端和历史行为。
11.3 一次 Code 执行
Code node execute()
→ 读取 input items / parameters
→ 生成 task payload 与允许的 context
→ broker 建立或复用 runner 通道
→ runner 执行 JavaScript / Python
→ 捕获 stdout、错误、超时和序列化结果
→ 校验输出必须是 item 形状
→ 返回给 WorkflowExecute
上下文不是完整 Node.js process。它应只暴露节点需要的 $json、$input、helpers、环境变量子集和受控模块。任务返回后还要执行结果验证,防止任意对象破坏下游 item 语义。
11.4 任务 broker 与 worker 的关系
queue worker 执行 workflow,并不意味着它应该直接 fork 任意 Python 子进程。Task broker 为 execution context 建立协议,把任务提交、连接认证、心跳、超时和结果回传抽出来。这样可以将 runner 作为独立进程/容器部署,在资源和安全策略上与 n8n main 分开。
11.5 错误与资源控制
任务 runner 必须至少处理:
- 语法错误和运行时错误。
- 用户代码显式抛出的节点错误。
- 超时、取消和 broker 断连。
- 返回值不是 item 数组或包含不可序列化值。
- Python runtime 不可用。
- 内存、CPU、并发和依赖白名单。
Code 目录中的 error wrapper 和 test fixture 很值得读,因为它们定义了“用户看到的错误”如何保留节点、行号和运行模式信息。
11.6 设计取舍
隔离 runner降低主进程被用户代码拖垮或污染的风险;代价是序列化、IPC、连接管理和部署复杂度。
JS/Python 统一 task 协议让节点层不必知道语言差异;代价是最小公共能力集和跨语言类型转换会限制高级特性。
结果验证让 downstream graph 更可靠;代价是用户代码的自由度不可能是无限的,文档和错误信息必须把约束讲清楚。