> ## 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 与 Call

> task 是 BlockX 的最小调度单元，call 是 task 内的最小执行单元：两者的定义、task 的输入字段、对外状态、结果模型与不变量。

task 是 Client 提交给 Worker 的一份工作：一个 `task_id`、一份"算什么"的配置、一份可选的"结果怎么处理"的配置和一个可选的超时。Worker 把它展开成若干 call，每个 call 是"一个函数 + 一组位置参数"的一次调用。task 只在一个 Worker 上执行，call 在这个 Worker 的 Executor 池里全并行。

## task 的输入

对外提交的结构是 gRPC `TaskInput`（`api/grpc/worker/worker.proto`）：

| 字段                     | 类型               | 说明                                                                                        |
| ---------------------- | ---------------- | ----------------------------------------------------------------------------------------- |
| `task_id`              | `string`         | Client 生成的稳定唯一 ID（blockx-py 用 uuid4）。同一 `task_id` 在 Worker 上是幂等键                          |
| `function_call_config` | `{type, config}` | `type` 选择 Call Builder，`config` 是只有该 Builder 才解析的 JSON                                    |
| `result_handler`       | `{type, config}` | 可选。`type` 选择 Writer Plugin。每个 task 至多一个                                                   |
| `task_timeout_ms`      | `int64`          | 可选。`0` 用 Worker 默认（`TASK_DEADLINE_MS`，默认 300000）；负数拒绝；超过 `MAX_TASK_TIMEOUT_MS`（默认 8 小时）拒绝 |

JSON 形态的一个最小示例（字段名对应 Go 类型 `commontypes.TaskInput` 的 `json` tag）：

```json theme={null}
{
  "task_id": "3f2b1c9e8a4d4c1e9b0a6f7e5d4c3b2a",
  "functionCallConfig": {
    "type": "InputsCallConfig",
    "config": {
      "function": "",
      "code": { "sourceCode": "def _(name):\n    return {\"message\": f\"hello, {name}\"}\n" },
      "callList": [["ct2"], ["blockx"]]
    }
  },
  "resultHandler": { "type": "ReturnValueHandler", "config": {} },
  "taskTimeoutMs": 30000
}
```

task 顶层不携带链和区块信息。区块上下文由具体 Builder 与 Writer Plugin 的 `config` 携带，例如 `BlockTableCallConfig.block` 和 `BlockTableWriteHandler.block`。

## call

| 字段           | 含义                                                                                        |
| ------------ | ----------------------------------------------------------------------------------------- |
| `functionId` | 函数身份。注册函数用它的注册 ID；inline 源码由 Worker 按源码 digest 生成 `inline-<digest 前 8 位>`                 |
| `args`       | 位置参数数组，JSON 编码；用户函数按位置接收                                                                  |
| `callId`     | Worker 内唯一。`InputsCallConfig` 生成 `{taskId}-{i}`，dbScan 生成 `{taskId}-{table}-{height}-{i}` |

call 从哪里来由 Call Builder 决定：`InputsCallConfig` 的 `callList` 每行一个 call；扫描类 Builder 对触发表的每一行生成一个 call，行本身按 `params` 模板放进 `args`。

call 的性质：

* 互不依赖、全并行，共享同一时刻的只读世界状态。返回顺序即完成顺序，与 call 列表顺序无关。
* 同一 task 内函数与参数都相同的 call 只执行一次（task 级 result cache 与 singleflight）。
* 单个 call 允许有界重试（`DefaultMaxAttempts = 3`）；任一根 call 终态失败即整个 task 以 `CALL_FAILED` 失败（`fastFail`）。
* 单个 call 的执行预算是 `CallDeadlineMs = 5000`，只计 CPU 与 IO 后端时间，不含排队。
* 用户函数里的子函数调用（`function.call`）也是 call，但不进入 `outputs`，只把结果交还父调用；嵌套深度上限 `MaxSubcallDepth = 8`。

## outputs

Calls 阶段结束后，所有成功根 call 的返回值汇聚成 `outputs`。每个元素是一段原样保留的 JSON：一个对象表示一行，一个对象数组表示多行，`null` 表示这次调用没有产出。Writer Plugin 只看到 `outputs`，看不到 call 与行的对应关系。

累计字节数超过 `MaxCollectedOutputBytes` 时，task 停止派发并以 `OUTPUT_BYTES_EXCEEDED` 失败。

## 对外状态

| 状态          | 何时进入                 | 说明                                   |
| ----------- | -------------------- | ------------------------------------ |
| `ALLOCATED` | `RequestTaskSlot` 成功 | 只占位，未提交；slot TTL 到期自动回收              |
| `RUNNING`   | `SubmitTask` 校验通过    | Builder、Calls、Plugin 三个内部阶段都折叠在这个状态里 |
| `SUCCEEDED` | Plugin 阶段成功          | 终态                                   |
| `FAILED`    | 任一阶段失败、超时或 Watch 断连  | 终态，`failureCode` 说明原因                |

```mermaid theme={null}
stateDiagram-v2
    [*] --> ALLOCATED: RequestTaskSlot
    ALLOCATED --> [*]: TTL 到期回收
    ALLOCATED --> RUNNING: SubmitTask
    RUNNING --> SUCCEEDED: Plugin 阶段成功
    RUNNING --> FAILED: 任一阶段失败 / 超时
    SUCCEEDED --> [*]: 结果保留窗口过期
    FAILED --> [*]: 结果保留窗口过期
```

## 结果

`TaskResult` 只表达 task 级结果：

```json theme={null}
{
  "success": true,
  "executeResult": {
    "retryable": false,
    "pluginResults": [
      {
        "pluginName": "ReturnValueHandler",
        "success": true,
        "result": [{ "message": "hello, ct2" }, { "message": "hello, blockx" }]
      }
    ]
  }
}
```

* 不含每个 call 的返回值。只有 `ReturnValueHandler` 会把整个 `outputs` 数组放进 `pluginResults[i].result`；写表插件只返回 `written_rows` 等统计。
* 失败时用 `failureCode` 和 `retryable` 表达。`retryable=true` 表示 Client 可以用同一份 task 重投；框架自身不做 task 级重试。
* 终态结果在 Worker 上保留 `ResultRetentionMs`（默认 300000）后清除。Client 用 `WatchTasks` 流订阅终态，或用 `GetTaskResult` 轮询。

常见 `failureCode`：

| `failureCode`                           | 含义                                                                            | `retryable` |
| --------------------------------------- | ----------------------------------------------------------------------------- | ----------- |
| `BUILDER_NOT_FOUND` / `BUILDER_FAILED`  | `functionCallConfig.type` 未注册，或 Builder 展开失败（表不存在、`operator` 非法、读 BlockDB 失败） | 取决于 IO 错误分类 |
| `CALL_FAILED`                           | 有根 call 终态失败，典型原因是用户函数抛异常或审计拒绝                                                | `false`     |
| `OUTPUT_BYTES_EXCEEDED`                 | `outputs` 超过 `MaxCollectedOutputBytes`                                        | `false`     |
| `PLUGIN_NOT_FOUND` / `PLUGIN_FAILED`    | Writer Plugin 未注册，或写入失败（config 形状不匹配、目标表无列、下游写失败）                             | 由插件错误分类决定   |
| `TIMED_OUT`                             | 超过 task 超时                                                                    | `true`      |
| `ACTIVATION_FAILED` / `IO_SCOPE_FAILED` | 激活时固定代码快照或创建 IO scope 失败                                                      | 取决于错误       |
| `CANCELLED`                             | 等待阶段准入期间 task 上下文已取消                                                          | —           |
| `WATCH_DISCONNECTED`                    | 最后一条 Watch 流断开且超过 grace 期                                                     | `false`     |

## 不变量

* **以 task 为中心**：读缓存、状态共享、超时都锚定在 task 上，task 结束即释放。
* **单 Worker 执行**：一个 task 只在一个 Worker 上跑；Worker 不感知 Coordinator，只暴露 `RequestTaskSlot` 与 `SubmitTask`。
* **函数版本固定**：task 激活时固定一个代码快照（`taskCodeEpoch`），之后的所有 call 和子调用都用这份快照；代码热更新只影响新 task。
* **幂等**：同一 `task_id` 处于 `RUNNING` 或结果仍在保留窗口内时，重复 `SubmitTask` 返回当前状态，不会再执行一次。
* **不做 task 级自动重试，不支持取消**：框架只提供超时；是否重投由 Client 根据 `retryable` 决定。
* **准入错误不进 `TaskResult`**：slot 无效、无空闲 slot、参数非法、`resultHandler.type` 未注册等都以 gRPC status 返回；一旦 task 进入 `RUNNING`，之后的失败都收敛为 `FAILED` 终态。

## 小结

* task = `task_id` + Call Builder 配置 + 可选 Writer Plugin 配置 + 可选超时；call = 函数 + 位置参数。
* call 全并行、无顺序、可去重；`outputs` 是扁平的返回值集合。
* 对外只有 `ALLOCATED / RUNNING / SUCCEEDED / FAILED` 四个状态；失败用 `failureCode` + `retryable` 表达。
* 框架不重试、不取消 task；幂等键是 `task_id`。

继续阅读：

* [Task 生命周期](/architecture/task-lifecycle)：端到端时序、slot 状态机、时间语义速查。
* [Call Builder 与 Writer Plugin](/concepts/builders-and-handlers)：`functionCallConfig` 与 `resultHandler` 的配置形状。
* [函数代码](/concepts/function-code)：call 执行的那段代码长什么样。
* [提交 task 示例](/development/submit-task-example)：用 blockx-py 或直接调 gRPC 提交 task。
