> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python Executor

> 常驻 Python 进程如何装载并执行用户函数、用 greenlet 挂起 IO、通过 UDS 与 Worker 往返，以及用户函数可用的 SDK 与其拦截点。

Python Executor 是 Worker 本机 Function Executor Pool 里的一个常驻 Python 进程（`python -m blockx_executor`）。它从 Worker 收 `ExecuteCall`、装载并执行用户函数，把用户函数发起的 BlockDB / RPC / 子函数调用翻译成 `CallWaiting` 回送 Worker，收到 `ResumeCall` 后恢复原执行栈。它在系统中的位置见 [架构总览](/architecture/overview)，Worker 侧的对端（dispatcher、executor adapter、子函数）见 [Call 执行子系统](/components/call-execution)。

## 职责与边界

负责：

* 通过一条 UDS 长连接接收 `ExecuteCall` / `ResumeCall` / `CancelCall` / `HeartbeatAck`，发出 `CallWaiting` / `CallCompleted` / `CallFailed` / `Heartbeat`。
* 按源码 digest 编译并缓存函数模块，按 task 隔离模块命名空间，缺源码时按 digest 向 Worker 回拉。
* 用 `greenlet` 让同步风格的用户代码在 SDK 调用点挂起、在 `ResumeCall` 后恢复。
* 在安全点执行取消与 deadline / budget 检查，并用 `SIGALRM` 定时器抢占纯 CPU 循环。
* 每个心跳周期上报进程指标（RSS、CPU 利用率、累计 CPU 秒、各状态 call 计数）。

不负责：

* 决定 call 派发顺序或选择哪个 Executor（Worker dispatcher 的事）。
* 直接访问 BlockDB / 节点 RPC / Function Code View。SDK 的所有 IO 都被拦截后经 Worker 出去。
* 持久化或跨进程恢复任何 call。进程重启后一切上下文重建。

核心不变量：

* 用户代码只在主线程的 greenlet 里跑，每次只跑一个；UDS reader / writer / heartbeat 是独立线程，不切换 greenlet。
* 一个 `ExecContext` 同一时刻最多一个 pending `requestId`；`ResumeCall` 必须同时匹配 `callId`、状态对应的 `resumeKind` 和 `requestId`，否则按 stale 丢弃。
* 同一 `callId` 再次 `ExecuteCall` 视为新 attempt：旧 greenlet 标 stale 并终止、不再发终态；新 attempt 用新 greenlet。
* 每个被丢弃的 context 恰好发出一条终态消息（`CallCompleted` 或 `CallFailed`），attempt 替换除外。
* 编译产物按 digest 跨 task 共享；exec 出来的模块命名空间按 `task_id` 隔离，task 最后一个 call 结束时释放。

## 代码位置

| 路径                                                                       | 用途                                                                                                                                                                                                                   |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `python/pyproject.toml`、`python/uv.lock`                                 | `uv` 管理的项目 `blockx-python`（Python ≥ 3.12）。外部 SDK 以 git tag 引入：`blockx-py@v0.1.64`、`blockdb-py@v0.1.26`、`leafage-py@v0.2.5`、`chaintable-py@v0.1.4`；运行时依赖 `greenlet`、`msgspec`、`orjson`、`opentelemetry-*`、`pyRFC3339`。 |
| `python/blockx_executor/`                                                | Executor 进程本体（入口 `__main__.py`）。文件清单见下方折叠表。                                                                                                                                                                          |
| `python/blockx_sdk/`                                                     | 极薄的 provider 接缝层（约 150 行）：`db` / `rpc` / `call_subfunc` / `sleep` 与 `get_provider()`。                                                                                                                                |
| `python/blockx_audit/`                                                   | 函数代码静态审计器（`python -m blockx_audit` 起 stdin/stdout 帧协议 daemon），Worker 装载前调用；细节见 [Function Code View 与代码审计](/components/function-code)。                                                                                |
| `python/tests/`                                                          | `unittest` 单测，按实现边界分文件。                                                                                                                                                                                              |
| `internal/worker/adapters/executor/spawner.go`                           | Go 侧拉起进程的地方，决定传给 `blockx_executor` 的命令行参数。                                                                                                                                                                           |
| `api/uds/types.go`                                                       | UDS 消息类型与 payload 结构的 Go 定义（Python 侧字段名常量在 `blockx_executor/wire_keys.py`）。                                                                                                                                          |
| `cmd/worker/worker_process_*_test.go`、`cmd/syncinvoker/*_test.go`、`e2e/` | 拉起真实 Python 进程的 E2E。                                                                                                                                                                                                 |
| `docs/specs/python-function-executor.md` 等                               | 设计 spec，见文末。                                                                                                                                                                                                         |

<AccordionGroup>
  <Accordion title="blockx_executor 模块文件清单">
    | 文件                                    | 一句话                                                                                                                                                                   |
    | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `__main__.py`                         | 解析命令行、装日志 / tracing / stdout 捕获、建 `UDSSession`、跑主循环 `run_until_idle()` + `wait_for_message()`；`SIGTERM` 转干净退出。                                                        |
    | `runtime.py`                          | `ExecutorRuntime`：上下文表、runnable / sleep 队列、入站消息分发、greenlet 调度、取消与 deadline、心跳线程、attempt 替换。                                                                           |
    | `context.py`                          | `ExecState`、`ExecContext`（含 budget 计费字段）、`ResumePayload`、`DebugCapture`，以及 greenlet-local 的当前上下文存取。                                                                   |
    | `sdk_bridge.py`                       | `SdkBridge`：`do_read_req` / `do_write_req` / `do_capability_req` / `do_subcall` / `do_function_code_req` / `do_sleep`，核心是 `_perform_wait`；错误映射与 `RemoteCallError` 家族。 |
    | `sdk_bridge_provider.py`              | `SdkBridgeProvider`：实现 `blockx_sdk.Provider`，把 provider 调用转发到 `SdkBridge`。                                                                                            |
    | `blockdb_bridge.py`                   | `BridgeChannel`：伪 gRPC channel，把 `blockdb-py` / `blockx-py` 的 unary 调用变成 `read_req` / `write_req`，protobuf 走二进制 sidecar。                                              |
    | `noderpc_bridge.py`                   | `NodeRPCBridgeProvider`：替换 `leafage._endpoints.W3_DICT`，把 EVM JSON-RPC 请求转成 `read_req`。                                                                               |
    | `module_registry.py`                  | `ModuleRegistry`：按 digest 的编译 LRU、按 task 的 exec 命名空间、audit gate 校验、入口名解析。                                                                                             |
    | `restricted_runtime.py`               | 审计通过代码的受限命名空间：`SAFE_BUILTINS`、facade importer、`blockx.function.call` 重绑到子函数桥。                                                                                         |
    | `uds_session.py`                      | `UDSSession`：一条 UDS 连接 + reader / writer 线程 + `threading.Event` 唤醒主循环。                                                                                                |
    | `uds_codec.py`                        | 帧编解码：`uint32` 长度前缀 + JSON，或带 `BXB1` 标签的 JSON + 二进制 sidecar；`orjson` 快路径、`msgspec` / stdlib 兜底。                                                                        |
    | `messages.py`                         | `MessageEnvelope` 与 `call_waiting` / `call_completed` / `call_failed` / `heartbeat` 构造函数。                                                                             |
    | `wire_keys.py`                        | 协议字段名常量（`F_*`、`FP_*`、`MT_*`）。                                                                                                                                         |
    | `process_metrics.py`                  | `ProcessMetricsSampler`：`/proc/self/statm` RSS 与 `time.process_time()` CPU 采样、启动探测、降级。                                                                                |
    | `sleep_patch.py`                      | 进程级 `time.sleep` 包装：有活动 `ExecContext` 时转 cooperative sleep，否则原样阻塞。                                                                                                    |
    | `debug_capture.py`                    | tee `sys.stdout` / `sys.stderr` 到当前 call 的 `DebugCapture`（仅 debug 调用）。                                                                                                |
    | `logging_setup.py`、`tracing_setup.py` | 结构化 JSON 日志（自动带 call 字段）与 OTel span / 跨 UDS 的 trace 传播。                                                                                                               |
    | `metrics.py`                          | 错误栈与输入预览的截断工具。                                                                                                                                                        |
    | `third_party_caches.py`               | 心跳线程顺带修剪 `eth_abi` 的无界 `lru_cache`。                                                                                                                                   |
  </Accordion>
</AccordionGroup>

## 核心类型与接口

| 类型 / 函数                                          | 文件                                       | 一句话                                                                                                                                |
| ------------------------------------------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `ExecutorRuntime`                                | `blockx_executor/runtime.py`             | 进程总控。`submit_call` / `resume_call` / `cancel_call` 是三条入站语义入口，`run_until_idle` 是调度循环，`build_heartbeat` 组心跳。                         |
| `ExecContext`、`ExecState`                        | `blockx_executor/context.py`             | 单个 call attempt 的本地记录与状态枚举。                                                                                                        |
| `SdkBridge._perform_wait`                        | `blockx_executor/sdk_bridge.py`          | 所有"发 `CallWaiting` → 让出 greenlet → 校验后返回结果 / 抛异常"的唯一实现。                                                                            |
| `SdkBridgeProvider`                              | `blockx_executor/sdk_bridge_provider.py` | 装进 `blockx_sdk.runtime.set_provider()` 的 provider。                                                                                 |
| `blockx_sdk.Provider`                            | `blockx_sdk/provider.py`                 | SDK 接缝协议：`read_req` / `write_req` / `capability_req` / `subcall` / `sleep`。                                                        |
| `BridgeChannel`、`make_blockdb_channel_factory`   | `blockx_executor/blockdb_bridge.py`      | 注入 `blockdb._grpc_client.connection.set_channel_factory` 与 `blockx._grpc_client.connection.set_channel_factory` 的伪 channel。        |
| `install_noderpc_bridge`                         | `blockx_executor/noderpc_bridge.py`      | 替换 leafage 的 per-chain Web3 provider。                                                                                              |
| `ModuleRegistry.load_callable`                   | `blockx_executor/module_registry.py`     | 返回本 task 的入口可调用对象；缺源码时用 `source_loader` 回拉。                                                                                        |
| `UDSSession`                                     | `blockx_executor/uds_session.py`         | `send_message` / `drain_incoming` / `wait_for_message` / `is_connected`。                                                           |
| `MessageEnvelope`                                | `blockx_executor/messages.py`            | 出入站消息的统一信封，`binary_payload` 承载 sidecar。                                                                                            |
| `ProcessMetricsSampler`                          | `blockx_executor/process_metrics.py`     | `probe_capabilities` / `sample_memory_bytes` / `sample_cpu_metrics`。                                                               |
| `RemoteCallError` 及子类                            | `blockx_executor/sdk_bridge.py`          | `ResumeCall.error` 映射成的同步异常：`SubcallFailedError`、`SubcallDepthExceededError`、`SubcallCycleDetectedError`、`FunctionCodeFetchError`。 |
| `CallCancelledError`、`CallDeadlineExceededError` | `blockx_executor/sdk_bridge.py`          | 框架级取消 / 超时信号；前者继承 `BaseException`，用户 `except Exception` 兜不住。                                                                       |

`Provider` 协议原文：

```python theme={null}
class Provider(Protocol):
    def read_req(self, backend: str, operation: str, params: Mapping[str, Any], *, cache_key: Optional[str] = None) -> Any: ...
    def write_req(self, backend: str, operation: str, params: Mapping[str, Any]) -> Any: ...
    def capability_req(self, backend: str, operation: str, params: Mapping[str, Any], *, cache_key: str = "") -> Any: ...
    def subcall(self, function_id: str, args: Any) -> Any: ...
    def sleep(self, seconds: float) -> None: ...
```

## 用户函数编程模型

一个函数模块是一段 Python 源码，入口是模块顶层的一个 `def`。入口名解析规则在 `blockx_audit/tables.py` 的 `resolve_entry_name`：优先取 `functionId` 最后一段同名的顶层 def，其次是 `def _`（或 `_ = some_def` 别名），最后兜底第一个公开 def。实践中写 `def _(...)` 即可。

`ExecuteCall.payload.args` 是 JSON 数组时按位置展开传参，否则整体作为单参数（`runtime.py` `_run_callable`：`target(*input_payload) if isinstance(input_payload, list) else target(input_payload)`）。返回值直接是行数据：`dict` 表示单行，`list[dict]` 表示多行，`None` 表示无输出（详见 [Plugin 系统](/components/plugins)）。返回值经 `uds_codec.dumps_wire` 序列化：超出 64 位的 Python `int`（如 uint256）可直接返回，`datetime` / `date` 转 RFC 3339 字符串，dataclass 经 `asdict` 兜底转换（并打一次告警），`Decimal` 等其他类型不可序列化，需先转成 `str`（下方示例里的 `str(total)` 就是这个原因）。

下面是 `cmd/worker/worker_process_subcall_test.go` 里的一个真实用例。它没有任何 `import`，因为非受限命名空间预置了 `tables.PRELUDE_IMPORTS`（`json`、`Decimal`、`datetime`、`date`、`blockdb.Table` / `BlockTable` / `TimeTable` / `Block`、`blockx.function`、`leafage.ChainState` 等）：

```python theme={null}
def _(p):
    child = function.call("adder", {"left": p["left"], "right": p["right"]})
    total = Decimal(str(child["sum"])) + Decimal("0.50")
    return {
        "child": child,
        "encoded": json.dumps({"sum": child["sum"]}, sort_keys=True, separators=(",", ":")),
        "total": str(total),
        "day": date(2026, 6, 10).isoformat(),
        "stamp": datetime(2026, 6, 10, 9, 30).isoformat(),
    }
```

`e2e/perf/s4_noderpc_batch_test.go` 里的节点 RPC 读法：

```python theme={null}
from leafage import ChainState

def _(p):
    cs = ChainState(p["chain"], p["block_id"])
    code = cs.get_address_code(p["address"])
    bal = cs.get_address_balance(p["address"])
    return len(code) + bal
```

各类 SDK 调用的用户侧写法与被拦截的位置：

| 用户代码                                                                               | 外部包          | Executor 拦截点                                                                                                                                                      | 走的 provider 方法                                      |
| ---------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `Table("t").get_row(...)`、`BlockTable("s").get_state(...)`、`upsert_rows(...)`      | `blockdb-py` | `BridgeChannel`（gRPC channel 层，`WriteService/` 走写）                                                                                                                | `read_req` / `write_req`，backend `blockdb_executor` |
| `ChainState(...).get_address_balance(...)` 等                                       | `leafage-py` | `NodeRPCBridgeProvider.make_request`                                                                                                                              | `read_req`，backend `rpc`                            |
| `function.call("child", *args)`                                                    | `blockx-py`  | `blockx.function.call` 见到 `BLOCKX_EXECUTOR_RUNTIME=1` 时改走 `blockx_sdk.runtime.get_provider().subcall(...)`；受限模式下 `restricted_runtime._subcall_call` 直接绑到 provider | `subcall`                                           |
| `blockx_sdk.call_subfunc(...)`、`blockx_sdk.db.get(...)`、`blockx_sdk.rpc.call(...)` | `blockx_sdk` | 直接调 provider                                                                                                                                                      | `subcall` / `read_req`                              |
| `time.sleep(x)`、`blockx_sdk.sleep(x)`                                              | stdlib       | `sleep_patch.install_global_sleep_patch`                                                                                                                          | `sleep`（本地 timer，不发 `CallWaiting`）                  |

`blockx_sdk` 与 `blockx-py` 的关系：`blockx-py` / `blockdb-py` / `leafage-py` 是用户真正面对的 SDK，也能在 Executor 之外直连服务；`blockx_sdk` 不是它们的替代品，而是 Executor 进程内的接缝——`blockx_sdk.runtime` 用 `contextvars` 保存当前 `Provider`，`blockx-py` 的 `function.call` 在检测到 Executor 环境时 lazy import 它。`blockx_sdk.db` / `rpc` / `call_subfunc` 这组早期入口现在主要出现在单测、Go 侧 E2E fixture 和 `cmd/bundle-loadgen` 生成的负载函数里。

<Note>
  审计开启时（`ExecuteCall.payload.audit` 存在），模块在 `restricted_runtime.restricted_namespace` 里 exec：`__builtins__` 只有 `SAFE_BUILTINS`，`import` 只能拿到白名单模块的 facade，`blockx` 只暴露 `function` 与能力类。哪些语法与 API 在白名单内见 blockx 仓库 `docs/specs/function-code-python-whitelist.md`。
</Note>

## 数据流 / 执行流程

```mermaid theme={null}
sequenceDiagram
    participant W as Worker
    participant S as UDSSession 线程
    participant R as ExecutorRuntime 主线程
    participant G as call greenlet
    participant B as SdkBridge

    W->>S: ExecuteCall(callId, functionCodeDigest, args)
    S->>R: drain_incoming 后 handle_inbound_message
    R->>R: submit_call 准入, 建 ExecContext 与 greenlet 并入队
    R->>G: _step_context 首次 greenlet.switch()
    G->>G: ModuleRegistry.load_callable（miss 时 do_function_code_req 回拉源码）
    G->>G: set_provider(SdkBridgeProvider), 调用用户入口
    G->>B: Table.get_row → BridgeChannel → provider.read_req
    B->>B: _perform_wait 生成 requestId, 状态改为 WAITING_IO
    B->>S: emit CallWaiting(waitKind=io, cacheKey, request, sidecar)
    S->>W: 串行写出帧
    B->>R: greenlet.parent.switch() 让出
    W->>S: ResumeCall(callId, requestId, resumeKind=io, result 或 error, budgetUsedMs)
    S->>R: resume_call 校验 attempt / 状态 / requestId
    R->>G: 下一轮 _step_context 以 greenlet.switch(payload) 恢复
    B->>B: 恢复 RUNNING, charge_wait, 检查取消 / deadline
    B-->>G: 返回 result 或抛 RemoteCallError
    G->>G: 用户函数 return
    G->>S: emit CallCompleted(output, effectiveExecutionDurationUs, queueWaitUs)
    S->>W: 写出帧
```

主循环在 `__main__.py`：`while session.is_connected(): runtime.run_until_idle(); session.wait_for_message(timeout=runtime.next_scheduler_delay(None))`。`run_until_idle` 每轮先唤醒到期 sleeper、刷新连接状态、处理入站消息，再从 `_sleep_ready`（优先，连续 64 个后让位）和 `_runnable` 取一个 `call_id` 交给 `_step_context`。`_step_context` 只有三个分支：有 `cancel_reason` 则 `_terminate_context`；有 `resume_payload` 则 `switch(payload)` 回等待点；否则首次 `switch()` 进入 `_run_callable`。

启动时 `ExecutorRuntime.__init__` 完成一次性 hook 安装：`install_global_sleep_patch()`、`_install_blockdb_bridge`（`blockdb` 的 `mode.set_mock(False)` + `set_channel_factory`）、`_install_noderpc_bridge`、`_install_localtest_bridge`（`blockx-py` 的 channel factory，供能力后端的 Python 客户端用），并设置 `BLOCKX_EXECUTOR_RUNTIME=1`。`SdkBridgeProvider` 不是全局装一次，而是每次进入 `_run_callable` 时 `set_provider`、退出时 `reset_provider`。

<Info>
  SDK hook 由两种手法组合而成：`blockx_sdk` 走 provider 注入；`blockdb-py` 与 `blockx-py` 通过它们暴露的 `set_channel_factory` 接缝在 gRPC channel 层被替换；`leafage-py` 通过替换 `W3_DICT` 条目被 patch。socket 层没有任何 hook。
</Info>

BlockDB 与 NodeRPC 请求体不走 JSON：`MessageEnvelope.binary_payload` 挂 protobuf / JSON-RPC 原始字节，`uds_codec.encode_frame` 打成 `BXB1` 标签帧；Worker 回的 `ResumeCall` 也用 sidecar 带响应字节，`runtime._handle_inbound_message_typed` 把它塞进 `result["Data"]`，bridge 再交给 gRPC stub 反序列化。blockdb-py 侧另有 mode / channel / stub 缓存。Executor 进程内没有 Python 本地 L1 读缓存，也没有协议批处理。

## 状态与生命周期

`ExecState` 的实际取值（`context.py`）：

```mermaid theme={null}
stateDiagram-v2
    [*] --> RUNNABLE : ExecuteCall / submit_call
    RUNNABLE --> RUNNING : 调度器 switch 进 greenlet
    RUNNING --> WAITING_IO : read_req / write_req / capability_req
    RUNNING --> WAITING_SUBCALL : subcall
    RUNNING --> WAITING_FUNCTION_CODE : digest miss 回拉源码
    RUNNING --> WAITING_SLEEP : sleep（本地 timer）
    WAITING_IO --> RUNNING : ResumeCall 匹配后再次调度
    WAITING_SUBCALL --> RUNNING : ResumeCall 匹配后再次调度
    WAITING_FUNCTION_CODE --> RUNNING : ResumeCall 或本地 leader 唤醒
    WAITING_SLEEP --> RUNNING : timer 到期进 sleep_ready
    RUNNING --> DONE : return / 异常 / 取消 / deadline
    WAITING_IO --> DONE : CancelCall / attempt 替换 / 连接丢失
    WAITING_SUBCALL --> DONE : 同上
    WAITING_FUNCTION_CODE --> DONE : 同上
    WAITING_SLEEP --> DONE : 同上
    RUNNABLE --> DONE : 未运行即被取消
    DONE --> [*]
```

几点补充：

* 恢复时状态不会先回到 `RUNNABLE`：`resume_call` 只写 `resume_payload` 并置 `queued=True` 入队，`_perform_wait` 从 `switch()` 返回后直接改成 `RUNNING`。等待中的 context 是否已排队由 `queued` 标志表达，不由状态表达。
* `DONE` 的 context 立即从 `_contexts` 移除，所以 `inflight_count()` 就是 `len(_contexts)`。
* `WAITING_FUNCTION_CODE` 用于按 digest 回拉源码。同一 digest 的并发 miss 在 `SdkBridge._function_code_fetches` 里做本地 singleflight，只有 leader 发 `CallWaiting(waitKind=function_code)`。

**attempt 与 requestId。** `_replace_existing_attempt` 在 `submit_call` 里执行：旧 context 标 `stale`、从队列剔除、`_terminate_context(..., suppress_terminal_emit=True)`；`attempt_seq` 来自 `ExecuteCall.payload.attemptSeq`（默认 1），不是本地递增。`requestId` 形如 `req-<processInstanceId>-<seq>`（`next_request_id`），进程实例 id 前缀让重启后的进程不会与旧进程的 pending 请求撞号，`seq` 在进程内单调递增。stale resume / cancel 分别累加 `_stale_resume_dropped` / `_stale_cancel_dropped`。

**取消。** `cancel_call` 对 `WAITING_*` 立即 `_terminate_context`（向 greenlet `throw(CallCancelledError)`，再 `GreenletExit`）；对 `RUNNING` / `RUNNABLE` 只设 `cancel_reason` 并入队，由下一个安全点的 `check_cancelled` 抛出。`CancelCall.payload.reason` 缺省为 `worker_shutdown`；本地超时闩用 `call_timeout`，终态归类为 `deadline_exceeded` 而不是 `cancelled`。

**deadline 与 budget。** `ExecContext.deadline_expired_reason()` 是唯一判定点：`budget_ms`（`callBudgetMs`，只计 CPU + IO 后端 + 子函数 + sleep，排队不计）优先于绝对的 `call_deadline_ms`。安全点有：进入用户函数前、每次 `_perform_wait` 发送前与恢复后、sleep 醒来后、返回前。此外 `_switch_greenlet_with_deadline` 在每次切入 greenlet 前 `signal.setitimer(ITIMER_REAL, remaining, 0.1)`，`_on_deadline_signal` 只在该 call 处于 `RUNNING` 且确实过期时抛 `_DeadlineSignalExpired`。这条 `SIGALRM` 通路能抢占纯 CPU 循环；吞掉 `BaseException` 的循环或长 C 调用仍要靠 Worker 的 wedge 处理。

**心跳与连接。** `_heartbeat_loop` 独立线程按 `heartbeat_interval_s` 发 `Heartbeat`，payload 有 `runnableCount`、`inflightCount`、`availableContexts`（`max(0, max_inflight - inflight)`；不限时报 `1<<30`）、`memoryBytes`、`executorCpuUtilization`、`executorCpuSecondsTotal`、`executorProcessInstanceId`、`lastSchedulerActiveAtMs`、`runningCallId`、`callStateCounts`、`sentAtMs`。`ProcessMetricsSampler` 在构造 `ExecutorRuntime` 时先做带重试的能力探测，失败则进程启动报错；运行期采样失败沿用上次值。UDS 断开后 `_refresh_transport_state` 给所有活动 context 打 `transport_lost` 并入队收敛，`submit_call` 拒绝新 call。

**准入。** 顶层 call 在 `inflight_count() >= executor_max_inflight` 时抛 `ExecutorCapacityExhausted`（回 `CallFailed(errorKind=executor_capacity_exhausted, retryable=true)`）；子函数不看该闸门，只要求父 context 驻留本进程，否则 `subcall_parent_not_resident`（不可重试）。`executor_max_runnable` 只是接收并保存，本地不强制。

## 配置

命令行参数（`blockx_executor/__main__.py`，由 `internal/worker/adapters/executor/spawner.go` 的 `executorRuntimeArgs` 传入）：

| 参数                        | 默认    | 说明                                     |
| ------------------------- | ----- | -------------------------------------- |
| `--executor-id`           | 必填    | 逻辑 executor id，跨重启可复用。                 |
| `--socket-path`           | 必填    | Worker 监听的 UDS 路径；sandbox 模式下是容器内挂载路径。 |
| `--heartbeat-interval`    | `1.0` | 心跳周期（秒）。                               |
| `--executor-max-inflight` | `128` | 驻留 context 上限，`0` 表示不限。                |
| `--executor-max-runnable` | `128` | 仅保存，Worker 侧派发阈值。                      |

环境变量：

| 变量                                                                                                       | 默认               | 说明                                   |
| -------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------ |
| `BLOCKX_LOG_LEVEL` / `BLOCKX_LOG_FORMAT`                                                                 | `info` / JSON    | 结构化日志级别与格式（`text` 切纯文本）。             |
| `OTEL_EXPORTER_OTLP_ENDPOINT`、`OTEL_TRACES_SAMPLER_ARG`、`BLOCKX_OTEL_SPAN_FILE`、`BLOCKX_OTEL_UDS_EVENTS` | 未设则不 tracing     | OTLP 导出、采样率、写文件导出、UDS 收发细粒度事件。       |
| `BLOCKX_EXECUTOR_CODE_CACHE_MAX_ENTRIES`                                                                 | `512`            | `ModuleRegistry` 编译产物 LRU 容量，`0` 不限。 |
| `BLOCKX_EXECUTOR_ABI_CACHE_TRIM_THRESHOLD`                                                               | `4096`           | `eth_abi` 缓存修剪阈值。                    |
| `BLOCKX_EXECUTOR_RUNTIME`                                                                                | 由 runtime 设为 `1` | `blockx-py` 用它判断自己在 Executor 内。      |

沙箱模式（`EXECUTOR_SPAWN_MODE=sandbox`）下 Go 侧只透传 `internal/worker/adapters/executor/pool.go` 里 `childEnvKeys` 白名单中的变量（上表各项加 `LEAFAGE_ENDPOINT`、`PROXY_SOCKET_PATH`）；裸进程模式仍继承全部父环境。容器空 netns，因此 OTLP 直连不可用。Python 进程本身对两种 spawn 模式无感，见 blockx 仓库 `docs/specs/executor-sandbox-isolation.md`。

## 扩展点

* **接一个新的外部 SDK 到 Worker IO 通道**：写一个 bridge 模块（参考 `blockdb_bridge.py` / `noderpc_bridge.py`），把 SDK 的传输接缝替换成对 `provider.read_req` / `write_req` 的调用，在 `ExecutorRuntime.__init__` 里安装；backend 名要与 Worker 侧 IO 子系统注册的 backend 对齐（见 [IO 访问子系统](/components/io-subsystem)）。若 SDK 是 gRPC unary，直接复用 `BridgeChannel(provider_getter, backend=..., streaming_methods=frozenset())`。
* **给用户代码新增 SDK 入口**：只加 `blockx_sdk` 下的薄封装并让它调 `get_provider()`；不要在 SDK 里持有连接。受限模式还要同步 `blockx_audit/tables.py` 的白名单与 `restricted_runtime` facade。
* **新增 `ResumeCall` 错误类型**：在 `sdk_bridge.py` 加 `RemoteCallError` 子类并扩展 `_map_resume_error`；backend 专属映射用 `register_io_error_kinds`。
* **新增心跳字段或消息类型**：`messages.py` 加构造、`wire_keys.py` 加常量、`runtime._handle_inbound_message_typed` 加分支，并同步 `api/uds/types.go`。
* **改安全点或计费**：所有过期判断经 `ExecContext.deadline_expired_reason` 与 `raise_if_deadline_expired`，不要在别处另写判断。

## 测试

```bash theme={null}
uv sync --project python                                    # 首次或 uv.lock 变更后；Go E2E 也用这个 .venv
PYTHONPATH=python uv run --project python python -m unittest discover -s python/tests
PYTHONPATH=python uv run --project python python -m unittest python.tests.test_executor_protocol_contract   # 单模块
```

`python/tests/` 按实现边界分文件（`test_<boundary>.py`），文件内按逻辑块分 `TestCase`（`<Boundary><Theme>Test`），规范见 blockx 仓库 `docs/specs/python-test-organization.md`。主要边界：

* Runtime：`test_executor_runtime_scheduler.py`、`test_executor_runtime_termination.py`、`test_executor_runtime_attempt.py`、`test_executor_budget.py`、`test_executor_subcall.py`、`test_executor_oom_hardening.py`、`test_terminal_bounds.py`。
* 协议：`test_executor_protocol_contract.py`（`ExecuteCall` 校验、入站控制消息、心跳字段）、`test_uds_codec.py`、`test_uds_session.py`、`test_envelope_*.py`。
* SDK hook：`test_executor_sdk_hook.py`、`test_sdk_bridge_provider.py`、`test_sdk_bridge_capability.py`、`test_perform_wait_emit_failure.py`、`test_blockdb_bridge.py`、`test_noderpc_bridge.py`、`test_sleep_patch.py`。
* 装载与隔离：`test_module_registry.py`、`test_restricted_runtime.py`、`test_executor_audit_gate.py`。
* 上下文与指标：`test_exec_context_*.py`、`test_process_metrics_sampler.py`、`test_logging_setup.py`、`test_tracing_setup.py`、`test_debug_capture.py`。
* 审计器：`test_audit_*.py`。

单测里不需要真实 UDS：`ExecutorRuntime("exec-1")` 不传 `session` 时消息进 `drain_outbox()`，用 `handle_inbound_message(dict)` 喂入站消息，`run_until_idle()` 推进。

E2E 在 Go 侧：`cmd/worker/worker_process_*_test.go` 与 `cmd/syncinvoker/*_test.go` 拉起真实 Worker + `python/.venv/bin/python -m blockx_executor`；`e2e/system` 起整套系统；`e2e/perf` 用仓库根 `test.sh` 在 `systemd-run` cgroup 限额下跑。命令见 [测试组织与命令](/development/testing)。

## 相关文档

blockx 仓库 spec：

* `docs/specs/python-function-executor.md` — Executor 进程内设计：调度、状态、attempt / requestId、取消与 deadline、资源边界（重点）。
* `docs/specs/worker-executor-connection-and-python-sdk-hook.md` §6–§8 — SDK hook 方案、`SdkBridge` 同步 contract、挂起恢复时序。
* `docs/specs/python-executor-process-metrics.md` — 心跳里进程指标的采样与降级。
* `docs/specs/2026-07-23-execute-call-function-code-reference.md` — 按 digest 回拉源码的协议。
* `docs/specs/executor-sandbox-isolation.md` — containerd 沙箱 spawn 模式。
* `docs/specs/2026-07-30-python-executor-cpu-optimization.md` — CPU 优化方案。
* `docs/specs/python-test-organization.md` — 单测组织标准。
* `docs/specs/function-code-python-whitelist.md`、`docs/specs/function-code-audit-design.md` — 用户代码白名单与审计。

站内：

* [Call 执行子系统](/components/call-execution) — Worker 侧 dispatcher、executor adapter、子函数。
* [协议与接口](/architecture/protocols) — UDS 帧格式与消息字段。
* [Function Code View 与代码审计](/components/function-code) — 源码来源与 `blockx_audit`。
* [IO 访问子系统](/components/io-subsystem) — `CallWaiting(waitKind=io)` 在 Worker 侧的去向。
* [可观测性](/components/observability) — 日志字段、指标、tracing。
