call list 变成聚合后的 outputs。它是 Worker 的 Executor 阶段实现,在 系统架构总览 中对应 4.2.4 Call 执行子系统。Python Executor 进程本身见 Python Executor,本页只把它当作 UDS 对端。
职责与边界
负责:- 用一个 Worker 全局的
DispatcherCore调度多个 task 的 call,按 task / instance 公平放量。 - 为顶层 call 做
Filter -> Score选 Executor;子函数调用固定绑回父 call 所在 Executor。 - 在 task 内做 call result cache 与 singleflight 去重。
- 处理 call 的挂起(IO / 子函数 / 源码拉取)与恢复,把 IO 请求转交 IO 访问子系统。
- 管理 Python Executor 进程池(spawn / 重启 / 沙箱),并维护与每个 Executor 的 UDS 长连接。
- 汇聚成功根 call 的输出为
outputs []any,交回WorkerCore进入 Plugin 阶段。
- 生成 task、分配 slot、Worker 选址(归 Coordinator 与 Worker)。
- 函数源码快照与审计本身(归 Function Code View),本页只消费
TaskFunctionView.ResolveForDispatch。 - 真实 BlockDB / RPC 访问(归 IO 子系统),Executor 不直连外部依赖。
DispatcherCore是 Sans-IO 纯内存状态机,单写者:只有 dispatch actor goroutine(Orchestrator.RunDispatchLoop)能改它;其他 goroutine 只能投递dispatchEvent。锁不变量由internal/worker/adapters/actor_invariants_test.go强制。- 一个 call 整个生命周期只对应一个
DispatchCallContext;retry 复用同一callID,只递增AttemptCount。 DONE是唯一终态;setCallTerminal是唯一记账点,重复调用幂等。- 子函数调用有独立的
DispatchCallContext,MaxAttempts = 1,不进入TotalCount/outputs,只把结果通过ResumeCall回送父 call。 - 子调用 ID 由进程级单调
subcallSeq生成({parent}/sub-{n}),worker 进程内全局唯一。 - Worker 侧没有 per-call 定时器:执行预算
CallBudgetMs由 Executor 本地强制,墙钟外层保护是 task deadline 加 wedge backstop。 - Executor 终态帧到达即释放 adapter 预留(transport truth),与 dispatcher 的
UnbindCall幂等。
代码位置
核心类型与接口
core.DispatcherCore(internal/worker/core/dispatcher.go):全局调度器。输入是HandleXxx事件方法,输出是[]DispatchCommand。core.DispatchCallContext(dispatcher_types.go):单个 call 的调度载体,含State、ExecutorID、AttemptCount、BudgetMs、IsSubcall、BatchIdx、ArgsJSON。core.TaskDispatchState(dispatcher_types.go):per-task 状态,含ReadyQueue、InflightCalls、CallCache、Singleflight、PendingBindSubcalls、ChildSubcalls,以及流式生产字段(ProductionOpen、BatchRemaining、PendingSettle、CollectedOutputs)。core.ExecutorSnapshot(dispatcher_types.go):adapter 提供的 Executor 容量视图,含Healthy、RunnableCount、UsableContexts、HeartbeatSeq、RunningCallID、LastSchedulerActiveAtMs。core.DispatchCommand(dispatcher_types.go):sealed 接口,实现有BindCall、UnbindCall、CancelCallCmd、DispatchCallCmd、ArmCallRetry、ConvergeTaskCalls、CompleteCallFromCache、BatchSettled、StopCallProduction。core.DispatcherCore.TryDispatchNext / tryDispatchNextFair:一次放量决策;selectExecutor做Filter -> Score。core.DispatcherCore.PrepareCallPhaseWithDigests + CommitCallPhase、CommitCallStream + PrepareCallBatchWithDigests + CommitCallBatch:一次性与流式两种 call 注册入口;Prepare*无副作用可 off-actor 运行。core.DispatcherCore.FlushDirtyTasks(dispatcher_internal.go):收敛 epilogue,按 task 分组产出StopCallProduction -> 排空 -> BatchSettled -> ConvergeTaskCalls。adapters.ExecutorAdapter(exec_adapter.go):orchestrator 依赖的 UDS adapter 接口。
adapters.Orchestrator.executeDispatchCommands(orchestrator_dispatch.go):命令执行器。Bind/Unbind在 actor 上同步执行;SendExecuteCall/SendResumeCall/SendCancelCall一律 off-actor goroutine。executor.IOHandler、executor.SubcallHandler、executor.FunctionCodeResolver(adapters/executor/adapter.go):adapter 收到CallWaiting后回调 orchestrator 的三个入口。executor.Adapter(adapter.go):每个 Executor 一个executorConn,持有reservations、draining、dispatchedAttempts、usableContexts;Snapshots()由此派生。executor.PoolManager、PoolConfig、SlotState(pool.go):进程 slot 状态机RUNNING / WAITING / BACKOFF / STOPPED;executorSpawner抽象 bare process 与 sandbox。uds.MessageEnvelope与 payload 结构(api/uds/types.go):ExecuteCallPayload、ResumeCallPayload、CancelCallPayload、CallWaitingPayload、CallCompletedPayload、CallFailedPayload、HeartbeatPayload。
数据流 / 执行流程
交互模式:Executor reader goroutine / 定时器 / gRPC 把事实投递为dispatchEvent(evCallCompleted、evCallWaiting、evSubcallRequest、evExecutorSnapshot 等);actor 调 DispatcherCore.HandleXxx 得到命令;executeDispatchCommands 执行命令;每个事件结束都走 flushDispatchEpilogue。
关键步骤说明:
1
注册 call
runCallPhase(orchestrator_phases.go)在 task 激活的 goroutine 上按 taskCodeEpoch 解析每个 call 的 functionCodeDigest,调用 PrepareCallPhaseWithDigests,再投递 evCallPhaseStarted 让 actor CommitCallPhase。流式 builder 走 CommitCallStream + 逐批 CommitCallBatch,生产者受 StreamBuildConfig.MaxOutstandingBatches 信用窗口背压,BatchSettled 归还一个信用。2
选 call
TryDispatchNext 从 ReadyQueue 队首取 call:先查 CallCache(命中直接 CompleteCallFromCache),再查 Singleflight(同 key 连续 call 一次性登记为 waiter),否则 selectExecutor 选 Executor、登记 singleflight leader、置 RUNNING、AttemptCount++、BudgetMs = CallDeadlineMs,输出 BindCall。3
选 Executor(Filter -> Score)
selectExecutor 过滤:!Healthy;调度器停摆超过 SchedulerStallTimeoutMs;headroom = UsableContexts - localInflight <= 0;补偿后 runnable >= ExecutorMaxRunnable;localInflight >= ExecutorMaxInflight;MemoryBytes >= ExecutorMemoryHighWatermark。打分:runnable 低优先,再 localInflight 低,再 headroom 高,最后 ExecutorID 字典序。ExecutorSelectionSampleSize > 0 时只扫前 N 个 eligible 候选(power-of-d 近似)。4
Bind 与下发
BindCall 在 actor 上同步调 Adapter.Bind(子调用走 BindSubcall,父有活预留即放行),成功后 HandleExecutorBound 输出 DispatchCallCmd。adapter 用 resolveFunctionCodeForDispatch 按 (functionID, digest) 从 task pinned view 校验并取 EntrySelector,组装 ExecuteCallPayload(只带 digest,不带源码),在新 goroutine 里过 audit gate、做 alive / draining 检查后 SendExecuteCall。Bind 失败对根 call 输出 ArmCallRetry(now) 且不消耗 attempt 预算,子调用则重新 park 到 PendingBindSubcalls。5
挂起与恢复
Executor 发
CallWaiting。waitKind=function_code 不改变 dispatcher 状态,adapter 直接用 FunctionCodeResolver 回源码。io / subcall 触发 HandleCallWaiting,adapter 在 processWaitRequest goroutine 里调 IOHandler.HandleIO(按 payload 回显的 TaskID 找 runtime,不查 dispatcher)或 SubcallHandler.HandleSubcall。结果经 SendResumeCall 阻塞到帧真正 flush 才触发 HandleCallResumed;发送失败则合成父 call 的 resume_send_failed 失败事件(retryable,带 attempt 序号)。6
终态与收敛
CallCompleted / CallFailed 到达 adapter 后先 releaseOnTerminal 释放预留,再回调 applyCallCompleted / applyCallFailed。core 写 CallCache、解决 singleflight waiter、取消该 call 的子调用子树,markTaskDirty。flushDispatchEpilogue 循环 FlushDirtyTasks 到静止:TerminalRootCount == TotalCount(流式还要 ProductionDone)时输出 ConvergeTaskCalls,adapter 翻译成 WorkerCore.HandleTaskPhaseFinished(PhaseCalls)。子函数调用路径
- Executor 内 SDK 发
CallWaiting(waitKind=subcall, functionId, args, ancestry, grantedBudgetMs)。 HandleSubcall投递evSubcallRequest,applySubcallRequest在 actor 上校验:深度len(ancestry) < MaxSubcallDepth、环检测(CacheKeyForJSON与祖先 key 比对)、请求来源 executor 必须等于父 call 当前绑定的 executor(拒绝旧 attempt 的迟到请求)。resolveFunctionCodeForDispatch取子函数 digest,AddSubcallJSON创建子DispatchCallContext(继承父BatchIdx,BudgetMs = grantedBudgetMs),记录pendingSubcalls[childID],TryBindSubcallToParent绑回父 Executor;父未绑定或父 Executor 不健康时 park 到PendingBindSubcalls,等HandleExecutorBound(parent)或HandleExecutorSnapshotUpdated重试。- 子 call 终态后
sendSubcallResume把结果或subcallError(含retryable)经ResumeCall回送父 Executor,BudgetUsedMs让父扣减预算。cache / singleflight 命中的子调用走CompleteCallFromCache直接 resume 父。 - 父 call 终态(含 attempt 失败重试)时
finalizeSubcallSubtree递归取消整棵子树,输出CancelCallCmd;adapter 把预留标为 draining 并 best-effortSendCancelCall。
Executor Pool 与连接
PoolManager.Start为每个 slot spawn 一个python -m blockx_executor --executor-id exec-N --socket-path ... --executor-max-inflight ... --executor-max-runnable ...。进程退出后waitLoop按RestartDelayMs重启,RestartWindowMs内超过MaxRestarts次进入BACKOFF等BackoffMs。KillExecutor供 wedge backstop 硬杀。ExecutorSpawnMode=sandbox时sandboxSpawner通过github.com/Chaintable/emulator/sandbox起 containerd 容器:无网络、只挂载 UDS socket 目录、只透传childEnvKeys白名单环境变量、可选 CPU / 内存限额。设计见 blockx 仓库docs/specs/executor-sandbox-isolation.md。- Executor 主动连
Adapter.SocketPath()(<SocketDir>/blockx-worker-<pid>.sock),首个有效Heartbeat完成 attach;没有显式注册消息。每条连接一个 reader goroutine 和一个 writer goroutine;writer 从outbox(深度 256)取帧,一次最多 coalesce 64 帧后共享一次Flush。 ExecuteCall非阻塞入队,outbox 满返回可重试错误;ResumeCall/CancelCall阻塞到 flush 完成,因为 dispatcher 状态推进依赖它真正到达。- 500ms tick:
SyncExecutorSnapshots把Adapter.Snapshots()喂给HandleExecutorSnapshotUpdated;CheckWedgedExecutors硬杀RunningCallID != ""且LastSchedulerActiveAtMs停摆超过SchedulerStallTimeoutMs的 Executor;CheckHeartbeatTimeouts把超过HeartbeatTimeoutMs无心跳的连接判为 transport lost,触发onExecutorLost→applyExecutorLost先刷快照再对该 Executor 上全部 inflight call 发 retryable 失败。
UDS 协议
帧格式:4 字节大端长度前缀 + JSONMessageEnvelope;带二进制 sidecar 时 body 以 BXB1 魔数 + 4 字节 JSON 长度开头,JSON 之后紧跟原始字节(BlockDB protobuf 走此路径,避免 base64)。单帧上限 16 MiB。编解码用 sonic,json.RawMessage 字段原样拼接。
状态与生命周期
DispatchCallContext.State 只有四态(CallReady / CallRunning / CallWaiting / CallDone)。Executor 内部的 runnable / greenlet 细状态不上升到 Worker。
- 重试与退避:
handleAttemptFailure在retryable && AttemptCount < MaxAttempts && !StopDispatching时置READY,computeBackoff按base * 2^(attempt-1)加最多 5% 抖动输出ArmCallRetry;adapter 用time.AfterFunc到时投递evRetryReady。base 优先取 task 的CallRetryPolicy.BaseBackoffMs,否则DispatcherConfig.BaseBackoffMs。 - 陈旧事件:
CallFailureStale丢弃READY && AttemptCount > 0(退避中)或attemptSeq不匹配的失败事件;已DONE的 call 忽略一切迟到终态。 - 失败策略:
CallFailureFastFail下任一根 call 最终失败即markStopDispatching,之后不再补发,drainCancelledCalls在 inflight 归零后把余下 call 全部取消收敛。 - 预算:根 call 每次 attempt
BudgetMs = CallDeadlineMs(默认 5000ms,0 无限),只计 CPU + IO 后端 + 子调用 + sleep,排队不计;子调用拿父切出的grantedBudgetMs。 - 预留生命周期(adapter):
Bind加入reservations;正常终态Unbind;取消路径MarkDraining,等 Executor 终态帧releaseOnTerminal才释放;重连 era 清空全部预留。 - 输出保留:
MaxCollectedOutputBytes > 0且累计输出超限时 task 以OUTPUT_BYTES_EXCEEDED停产并丢弃输出。
配置
来自internal/worker/app/config.go(WorkerFullConfig)与 internal/worker/core/config.go(DispatcherConfig)。环境变量覆盖见 internal/worker/app/app.go。
扩展点
- 改调度策略:
selectExecutor/betterExecutorCandidate(dispatcher_internal.go)改 Filter / Score;tryDispatchNextFair(dispatcher.go)改公平模型。跑dispatcher_select_bench_test.go、dispatcher_fairness_bench_test.go看回归。 - 新增 core 事件或命令:在
dispatcher_types.go加DispatchCommand实现,在orchestrator_dispatch.go的executeDispatchCommands加分支;新事件在orchestrator_actor.go加evXxx类型和applyXxx,并保证只在 actor 上改dispCore(actor_invariants_test.go会拦)。任何可能影响收敛的入口必须markTaskDirty,收敛只在FlushDirtyTasks里发生。 - 新增 UDS 消息类型或 payload 字段:
api/uds/types.go加常量与结构,adapters/executor/adapter.go的handleMessage加路由,handlers.go加处理器;同步改 Python 侧协议与api/uds/codec_test.go。 - 新增
waitKind:handlers.go的handleCallWaiting/processWaitRequest加分支;决定它是否进入 dispatcherWAITING状态(function_code就不进入)。 - 新增 Executor spawn 方式:实现
executorSpawner与executorProcess(spawner.go),在newExecutorSpawner注册,sandbox_config.go补校验。 - 调整 Executor 心跳字段:
uds.HeartbeatPayload→handlers.go的handleHeartbeat/heartbeatUpdateExisting→core.ExecutorSnapshot。
测试
- core 测试按主题分文件:
dispatcher_test.go(基础调度)、dispatcher_subcall_test.go、dispatcher_stream_test.go(流式生产 / batch settle / 输出上限)、dispatcher_instance_fairness_test.go与dispatcher_fairness_quantum_test.go、retry_backoff_test.go、dispatcher_stale_failure_test.go、dispatcher_wedge_test.go。 - adapter 测试通过
ExecutorAdapterstub 注入发送失败等路径:orchestrator_dispatch_test.go、orchestrator_subcall_test.go、orchestrator_send_failure_test.go、orchestrator_capacity_failure_test.go。 adapters/executor/测试用真实 UDS socket 起 fake executor:adapter_test.go、pool_test.go、resume_pool_test.go、write_coalesce_test.go、payload_buffer_test.go。api/uds/codec_test.go、bench_test.go覆盖帧编解码与二进制 sidecar。- E2E 在
cmd/worker/worker_process_*_test.go,测试组织约定见 测试组织与命令 与 blockx 仓库docs/specs/worker-test-organization.md。
相关文档
blockx 仓库 spec:docs/specs/call-execution-subsystem.md:本子系统设计(重点),含 Filter/Score、重试、子调用、流式生产。docs/specs/worker-executor-connection-and-python-sdk-hook.md:连接附着、失效判定、UDS 消息 contract、SDK hook。docs/specs/architecture.md§4.2.4:子系统在整体架构中的位置。docs/specs/2026-07-23-execute-call-function-code-reference.md:ExecuteCall只带 digest、waitKind=function_code按需取源码。docs/specs/2026-07-30-python-executor-cpu-optimization.md:结论性影响是 BlockDB 请求 / 响应走BXB1二进制 sidecar,Worker 用原始字节做 IO cache key。docs/specs/executor-sandbox-isolation.md:containerd 沙箱 spawn 模式。