> ## 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.

# 运行时

> 承载 task 的进程与资源：Coordinator 选址、Worker 的 slot 准入、Python Executor 进程池、IO 子系统与函数代码视图，以及 block / bundle 两套集群和 Sync Invoker。

task 从 Client 出发，经 Coordinator 选址，在一个 Worker 上执行，call 落到这个 Worker 的 Python Executor 子进程里。本页解释这条路径上的每一个名词；组件的实现细节在各组件页。

## 进程

| 进程              | 二进制                                                              | 职责                                                     | 状态                     |
| --------------- | ---------------------------------------------------------------- | ------------------------------------------------------ | ---------------------- |
| Coordinator     | `cmd/coordinator`、`cmd/bundle_coordinator`                       | watch etcd 里的 Worker 心跳，替 Client 选一个 Worker 并预占 slot   | 无持久状态，可多实例             |
| Worker          | `cmd/worker`、`cmd/bundle_worker`                                 | 执行面真相：slot 表、task 三阶段、结果短期保留、etcd 心跳                   | 进程内存；重启后旧 slot 与结果全部丢失 |
| Python Executor | `python -m blockx_executor`，Worker 的子进程，`EXECUTOR_COUNT` 个（默认 8） | 编译、缓存并执行用户函数，IO 时挂起                                    | 按源码 digest 缓存编译产物      |
| Sync Invoker    | `cmd/syncinvoker`                                                | 同步 `Invoke` / `DebugInvoke` / `Precheck`，自带 Executor 池 | 无 slot、无 task          |
| etcd            | 外部服务                                                             | 只承载 Worker 注册与心跳                                       | —                      |

## 一次提交经过的路径

<Steps>
  <Step title="选址">
    Client 向任意一个 Coordinator 调用 `ReserveWorkerSlot(task_id)`。Coordinator 从 etcd 心跳重建的 Worker 视图里挑一个候选，向它调用 `RequestTaskSlot(task_id, ttl_ms)`，把 `worker_addr + slot_id` 交回 Client。
  </Step>

  <Step title="提交">
    Client 直连 `worker_addr` 调用 `SubmitTask(task, slot_id)`。Worker 校验 slot、创建 task 上下文、固定代码快照后返回 `RUNNING`。不带 `slot_id` 时 Worker 会内联申请一次。
  </Step>

  <Step title="取结果">
    Client 对同一个 Worker 调用 `WatchTasks([task_id])` 订阅终态，或用 `GetTaskResult(task_id)` 轮询。Coordinator 不再参与。
  </Step>
</Steps>

blockx-py 不直连任何地址：所有 RPC 经 `PROXY_SOCKET_PATH` 指定的 UDS proxy 转发。proxy 按 gRPC method path 把 `ReserveWorkerSlot` 分到 block 或 bundle Coordinator，按 `x-blockx-worker-addr` header 把 `WorkerService` 请求转到对应 Worker。本地开发用 `examples/local_test_service/local_proxy.py` 代替这一环。

## slot

slot 是 Worker 上的 task 并发额度：

* 每个 Worker 有固定数量的 slot（`TASK_SLOTS`，默认 50），一个 task 占一个。
* `RequestTaskSlot` 把一个空闲 slot 切到 `ALLOCATED` 并绑定 `task_id`；TTL（Coordinator 侧 `SlotTTLMs = 5000`）内没有 `SubmitTask` 就自动回收。
* `SubmitTask` 校验通过后 slot 进入 `RUNNING`；task 收敛到终态的那一刻 slot 就被释放，与结果保留窗口无关。
* 没有中心化队列。拿不到 slot 时 Worker 返回 `NoSlot`（gRPC `ResourceExhausted`），由调用方退避重试；blockx-py 会一直退避直到拿到 slot。
* Worker 本地的 slot 表是唯一真相。Coordinator 只有一份可丢弃的派生视图；多个 Coordinator 同时选中同一个 Worker 时，在 Worker 的原子容量检查处收敛。

## Worker 内部

| 部件                         | 作用                                                                                                                        | 组件页                                             |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| WorkerCore                 | slot 与 task 的状态机，纯内存、不做 IO，只裁决"能否进入下一阶段"                                                                                  | [Worker](/components/worker)                    |
| Call Builder               | Builder 阶段：把 `functionCallConfig` 展开成 call 列表                                                                             | [Plugin 系统](/components/plugins)                |
| Dispatcher + Executor Pool | 按窗口把 call 投递到 Executor，处理挂起、恢复、重试与子调用；task 内同时 in-flight 的 call 上限 `TaskMaxInflightCalls = 1024`                          | [Call 执行子系统](/components/call-execution)        |
| Function Code View         | 函数源码的不可变快照与 epoch；task 激活时固定                                                                                              | [Function Code View](/components/function-code) |
| IO 子系统                     | 所有 BlockDB、链节点 RPC、Meta 与能力后端访问的统一入口：Worker 级读缓存、task 内 singleflight、IO 窗口、后端自适应准入                                        | [IO 访问子系统](/components/io-subsystem)            |
| 后端适配器                      | 把 IO 请求翻译成真实远端调用；已注册的后端种类有 `blockdb`、`blockdb_executor`、`rpc`、`meta`、`logicalTypes`、`iceberg`、`localtestservice`、`router` | [后端适配器](/components/backend-adapter)            |
| Writer Plugin              | Plugin 阶段：把 `outputs` 写回或回传                                                                                               | [Plugin 系统](/components/plugins)                |

Executor 与 Worker 之间是一条 UDS 长连接：Worker 发 `ExecuteCall`，Executor 在用户函数发起 IO 或子调用时回 `CallWaiting`，Worker 处理后 `ResumeCall`，最后 Executor 以 `CallCompleted` / `CallFailed` 结束。Executor 不直连任何外部服务。

## 两套集群

同一份代码用不同的 `app.Profile` 起成两套互不可见的集群：

|                | block 集群                                                                      | bundle 集群                                                      |
| -------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------- |
| 进程             | `cmd/coordinator` + `cmd/worker`                                              | `cmd/bundle_coordinator` + `cmd/bundle_worker`                 |
| etcd 注册前缀      | `/blockx/workers/`                                                            | `/blockx/bundle-workers/`                                      |
| Coordinator 服务 | `coordinator.v1.CoordinatorService`                                           | `bundlecoordinator.v1.BundleCoordinatorService`                |
| Call Builder   | `BlockTableCallConfig`、`InputsCallConfig`、`BlockBundleCallConfig`             | `BlockBundleCallConfig`、`InputsCallConfig`                     |
| Writer Plugin  | `BlockTableWriteHandler`（按区块写）、`NormalTableWriteHandler`、`ReturnValueHandler` | `BlockTableWriteHandler`（按 bundle 写）、`NormalTableWriteHandler` |
| stream build   | 默认关                                                                           | 默认开                                                            |
| 面向             | 实时单块计算、一次性计算                                                                  | 大范围历史回填                                                        |

task 协议、slot 状态机和心跳在两套集群里完全一致。blockx-py 按 config 与 handler 自动选集群：`BlockTableCallConfig` 走 block，`BlockBundleCallConfig` 走 bundle，带 `ReturnValueHandler` 的一律走 block，`InputsCallConfig` 默认走 bundle。

## Sync Invoker

Sync Invoker 是 task 之外的第二个入口：一次 gRPC unary `Invoke` 执行一次函数并当场返回结果、`call_id` 和执行归因时长。它复用 Worker 的 Executor 池、UDS 协议、Function Code View 和只读 IO 子系统，但不经过 Coordinator，没有 slot、Builder 和 Writer Plugin，也拒绝所有写请求。blockx-py 的 `function.call(...)` 在 Executor 之外运行时走的就是它。

## 时间参数速查

| 参数        | 默认值                | 含义                                           |
| --------- | ------------------ | -------------------------------------------- |
| slot TTL  | 5000 ms            | `ALLOCATED` 未提交的自动回收时间                       |
| task 超时   | 300000 ms（上限 8 小时） | `task_timeout_ms = 0` 时的有效超时，从 Worker 接受提交起算 |
| 结果保留      | 300000 ms          | 终态后可查询、可幂等命中的窗口                              |
| 单 call 预算 | 5000 ms            | CPU + IO 后端时间，不含排队                           |
| 心跳间隔      | 200 ms（生产建议 2000）  | Worker 写 etcd 的频率                            |

完整表见 [Task 生命周期](/architecture/task-lifecycle) 的"时间语义速查"。

## 小结

* Coordinator 只选址、只预占 slot，无状态可多实例；Worker 是执行面唯一真相。
* slot 是 Worker 上的 task 并发额度，没有中心化队列，拿不到就退避重试。
* call 在 Worker 本机的 Python Executor 子进程里执行，所有 IO 经 UDS 回到 Worker 的 IO 子系统。
* block 与 bundle 是同一份代码、不同 profile 的两套集群；Sync Invoker 是不走 task 流程的同步入口。

继续阅读：

* [架构总览](/architecture/overview)：组件图与核心约束。
* [协议与接口](/architecture/protocols)：gRPC、UDS 与 etcd 协议细节。
* [Coordinator](/components/coordinator) 与 [Worker](/components/worker)：选址、slot 表与阶段准入的实现。
* [Sync Invoker](/components/sync-invoker)：同步调用入口的实现。
