Skip to main content
Sync Invoker(二进制 syncinvoker)是 BlockX 的同步函数调用服务。调用方发一次 gRPC unary Invoke,服务端在本地 Python executor 池里执行一次函数,同一次 RPC 里返回结果、call_id 和执行归因时长。它独立部署,不经过 Coordinator,也不走 Worker 的 slot / task 流程;它只复用 Worker 的 executor 池、UDS 协议、Function Code View 和只读 IO 子系统。整体位置见 架构总览

职责与边界

负责:
  • 接收 Invoke / DebugInvoke,执行已注册函数(function_id)或临时源码(inline_source),返回 result_json / Failureexecution_duration_msqueue_wait_ms
  • admission:在 N 个 executor × K 个 context 槽位里选一个负载最低的 executor 派发;打满时返回 RESOURCE_EXHAUSTED
  • deadline:到点只发软 CancelCall,然后等 executor 的真实终态(协作 cancel / SIGALRM)或兜底 sweep(wedge / 心跳超时)收敛。
  • subcall:父函数发起的子调用在同一个 executor 上派发,父子共享一个 deadline、一个 code epoch 和一个 IO scope。
  • Precheck:对源码只做静态审计,不执行;返回 pass + findings
  • 执行归因:execution_duration_ms = 整棵 subcall tree 的 effective CPU + 本 call 的 IO backend 时长;EXECUTOR_LOST 一律计 0 且 retryable。
  • 失败来源标注:每个失败带 FailureOriginUSER / SYSTEM / CAPACITY / UNKNOWN),准入层失败通过 google.rpc.ErrorInfo.metadata 携带。
不负责:
  • 鉴权、API Key、限流、计费、业务级并发控制(网关侧)。
  • 任何写路径:HandleIO 拒绝 mode == "write";不接 BlockDB writer / Event Writer / ResultHandler。
  • task 编排:没有 readyQueue、fairness、失败重试、call result cache、callbuilder / writer plugin。
  • 服务端重试:一个 call 至多进一次 EXECUTING,retry 由 SDK 发新 call。
核心不变量:
  • 终态不可逆:core.Registry.Finish 是唯一终态门,每个 call 只放行一次;迟到的 executor / IO / subcall 结果丢弃。
  • 一个 call 至多进一次 EXECUTINGRegistry.Bind 只接受 ACCEPTED)。
  • 每次 sync-call(含 inline)在 ACCEPTED 时 pin 当前 FCV epoch,随 ExecuteCallPayload.TaskCodeEpoch 下发,子调用继承。
  • 计量数字只来自 executor 的真实终态 payload,或在 EXECUTOR_LOST 时明确为 0;不从心跳快照推断。
  • 配置约束:MaxCallDeadlineMs + heartbeat_interval < SchedulerStallTimeoutMs,否则进程启动时拒绝(cmd/syncinvoker/main.go)。
  • [reg.Bind → SendExecuteCall]、deadline / client-gone 的 subtree 清理、executor-lost fan-out 三者共享 Service.dispatchMu,两两互斥。

代码位置

cmd/syncinvoker/ 约 1100 行非测试代码,比一般 cmd/ 入口重。原因是它没有 internal/syncinvoker/app/ 包:executor 池、FCV 三种来源、IO backend 装配、health probe 都直接在 cmd/ 里镜像 cmd/worker 的装配逻辑。改装配时先看 cmd/worker 对应文件是否已有同款实现。

核心类型与接口

core(Sans-IO)

  • core.CallStateinternal/syncinvoker/core/call.go):ACCEPTED / EXECUTING + 六个终态;IsTerminal()
  • core.FailureCode / core.FailureOrigin / core.Failure:与 proto 枚举同值但不依赖 proto;FailureTerminal(f) 按 code 选终态并用 defaultFailureOrigin 补 origin。
  • core.Terminal:交给等待者的最终结果,带 EffectiveDurationUsQueueWaitUsIOBackendDurationMsDebug
  • core.RegistryAccept / AcceptSubcall / Bind / Finish / Drop / Subtree / CallsByRoot / InflightCount / MarkTerminating / ObserveSnapshot。executor 健康只有 HealthReady / HealthTerminating,用 attach generation 区分 replacement 与被杀进程的残影。
  • core.CallMeta:accept 时 pin 的 EpochCacheKey(环检测)、DeadlineMsRoot
  • core.PickExecutor(loads, maxInflight) / core.RankExecutors(loads, maxInflight, shuffle)admission.go):默认策略与 admit-spread 策略。

adapters

  • adapters.ServiceConfigservice.go):DefaultDeadlineMsExecutorMaxInflightMaxCallDeadlineMsMaxAcceptedDeadlineMsSchedulerStallTimeoutMsAdmitSpread
  • adapters.ServiceNewService(cfg, exec, codeView) 构造时就把回调挂到 executor adapter 上(wireExecutorCallbacks)。主要方法:
  • adapters.ExecutorAdapterexec_adapter.go):Bind / BindSubcall / Unbind / MarkDraining / SendExecuteCall / SendResumeCall / SendCancelCall / Snapshots + SetCallbacks / SetExecutorLostCallback / SetIOHandler / SetSubcallHandler。测试注入 stub,生产传 *executor.Adapter
  • adapters.FunctionCodeViewCurrentEpoch() + Fetch(functionID, epoch)fccore.SnapshotStore 直接满足。可为 nil(只支持 inline)。
  • adapters.GRPCServergrpc_server.go):Invoke / DebugInvoke / Precheck 三个 handler;准入失败时 mirrorCallIDcall_id 写进 trailer x-blockx-sync-call-id
  • callScopeservice.go,未导出):按 sync-call 建的 iocore.TaskIOScope + cancel + in-flight IO 计数;整棵 subcall tree 共用 root 的 scope。

协议(api/grpc/syncinvoker/sync_invoker.proto

两层错误语义:准入层失败返回 non-OK gRPC status(INVALID_ARGUMENT / NOT_FOUND / RESOURCE_EXHAUSTED / UNAVAILABLE),call_idfailure_origin 放在 ErrorInfo.metadata;执行层失败返回 OK + success=false + Failure{code, message, retryable, origin}

数据流 / 执行流程

Service.Invoke 是主路径。core 只做决策(Registry 状态转换、PickExecutor),adapter 围绕它做 Bind / Send / 计时 / IO。 关键步骤(service.go):
1

pin epoch 与解析源码

pinEpochFunctionCodeView.CurrentEpoch()resolveSource 校验 oneof exactly-one,function_id 按该 epoch Fetch,inline 直接返回源码但仍 pin epoch。
2

audit gate 与参数校验

auditSourceFUNCTION_CODE_AUDIT_MODE 决定是否 gate:off 不审、enforce fail-closed、dark 只记日志;space 在 allowlist 里的函数跳过。normalizeArgsJSON 一次扫描校验 JSON 数组并压缩空白,同一份字节既做 cycle key 又下发给 executor。
3

admission

admitreadyLoadsSnapshots 里过滤 HealthyAvailableContexts > 0 的 executor,用 Registry.ObserveSnapshot 做 attach generation 对账,再用 Registry.InflightCount(Bind +1 / Finish -1)作负载键;PickExecutor 选最低负载,exec.Bind 失败重选一次,仍失败 UNAVAILABLEADMIT_SPREAD=1 时改为 RankExecutors 逐个试 Bind,候选耗尽 RESOURCE_EXHAUSTED
4

dispatch 与等待

openCallScope 建 IO scope;SendExecuteCalluds.ExecuteCallPayload{TaskID: callID, TaskCodeEpoch, CallDeadlineMs, Debug, Audit}。然后 select 三路:w.ch 终态、deadline timer、ctx.Done()
5

终态与归因

onCallTerminalRegistry.Finish 单次门,Unbind,把 treeEffective(子调用 effective µs 累加)折进 root,关闭 scope 并 FinalizeCallIOsignal 给等待者。buildOutput 计算 execution_duration_ms = effective/1000 + IOBackendDurationMsEXECUTOR_LOST 恒为 0。queue_wait_ms 直接取终态 payload 的 QueueWaitUs,由 executor 上报,不并入 execution_duration_ms

状态与生命周期

core.CallState 的转换由 Registry 独占。TERMINAL_ADMISSION_DENIED 有两种落地:Bind 前失败走 Registry.Drop(不记终态,call 直接从表里移除);Bind 后 openCallScope / SendExecuteCall 失败才 Finish(StateTerminalAdmissionDenied) deadline 路径(onDeadline)分层收敛,每层都产出真实终态:
  1. Subtree 和 root 只发软 SendCancelCall,都不 FinishquiesceCallScope 停新 IO。
  2. 有界等 w.ch,窗口 deadlineEscalationWindow = max(5s, 2×SchedulerStallTimeoutMs + 2s)。四条路径都会送信号:executor 协作 cancel 或 SIGALRM 自愈发真实终态;CheckWedgedExecutors 检测 scheduler 冻结超过 SchedulerStallTimeoutMsRunningCallId 已过 deadline → reapExecutorCause("wedge_sweep");心跳超时 → adapter 的 executor-lost 回调 → reapExecutor;超窗 → reapExecutorCause("deadline_escalation") 强制收敛。
  3. 收到终态后用 CallsByRoot 清掉 grace 期间新加的 subcall,最终 Failure 统一覆盖为 TIMED_OUT(origin UNKNOWN)。
client 断连(onClientGone)不同:立即 Finish root、取消子树、MarkDraining,返回 gRPC context error;只把 Go 侧已观察到的子调用 CPU 和 IO 时长记入。 executor 侧:Registry 只跟踪 {ready, terminating}reapExecutor 把 executor 标 terminating、fan-out EXECUTOR_LOST 给所有 in-flight call、调 PoolManager.KillExecutor;pool 的 waitLoop 拉起替代进程,ObserveSnapshot 见到更高 attach generation 才重新参与 admission。

与 Worker 的差异

复用边界(docs/specs/sync-invoker.md §6):
  • internal/worker/adapters/executorAdapter(UDS server、Bind / BindSubcall / Unbind / MarkDraining、心跳、Snapshots)、PoolManager(fork N 个 Python executor、崩溃拉起、KillExecutor)、IOHandler / SubcallHandler / IORequest 接口。
  • internal/worker/core:只用 ExecutorSnapshot
  • internal/worker/devstubStubBackendAdapter,未配置外部后端时的 IO stub。
  • internal/functioncode/*SnapshotStore、Redis / BlockDB syncer、audit.Pool
  • internal/io/coreinternal/io/assemblyinternal/io/adaptiveWorkerIOScope / TaskIOScope、backend 装配、AIMD admission。
  • internal/sdk/*:blockdb / noderpc / localtestservice / router 客户端。
  • api/udsExecuteCallPayload / CallCompletedPayload / CallFailedPayload / ResumeCallPayload
Worker 内部细节见 WorkerCall 执行子系统IO 访问子系统Function Code View

Precheck 与 audit gate

Precheck 与 Invoke 的 audit gate 用同一个 audit.Auditoraudit.NewPool,进程启动时对 off / enforce / dark 三种模式都启动,Size: 2),但两者解耦:
  • FUNCTION_CODE_AUDIT_MODE 只管 Invoke gate:SetAuditGateOff(true)(off)/ SetAuditDark(true)(dark)/ 都不设(enforce)。
  • Precheck 无视 mode 与 space allowlist,固定按 _ entry 审计,返回真实判定。审计不过是 OK + pass=false + findings;只有产不出判定才 non-OK:FAILED_PRECONDITION(无 auditor)、UNAVAILABLE(auditor 宕机)、INVALID_ARGUMENT(空源码)、CANCELED / DEADLINE_EXCEEDED(调用方 ctx 取消,单独计数)。
  • enforce 模式下 auditor 启动失败进程拒绝启动;off / dark 降级为无 auditor(Invoke 不 gate,Precheck 返回 FAILED_PRECONDITION)。

失败分类与归因

执行层失败在 failedTerminalservice.go)里从 uds.CallFailedPayload.ErrorKind 映射,不解析 message:
  • classifyFailureexecute_call_rejectedCODE_LOAD_FAILEDruntime_error / cancelledCALL_FAILEDdeadline_exceededTIMED_OUTtransport_lost / resume_send_failed / terminal_decode_failedEXECUTOR_LOSTexecutor_capacity_exhaustedEXECUTOR_CAPACITY_EXHAUSTED;IO classifier 的 system_error / retryable_io / param_error / non_retryable / timeout / scope_closedIO_PROXY_FAILED
  • failureRetryableEXECUTOR_LOST / EXECUTOR_CAPACITY_EXHAUSTED 恒 true;IO_PROXY_FAILED 尊重 payload 的 retryable;其余 false。
  • classifyFailureOrigin:按 ErrorKind + 内部 DetailCode(如 grpc:NotFoundUSERsyncinvoker:io_not_configuredSYSTEM)+ ChildOriginsubcall_failed 继承子调用 origin);deadline_exceededUNKNOWN。完整表见 docs/specs/sync-invoker-failure-origin.md
  • 准入层:denyAdmissionwithCallIDcall_idfailure_origin 写进 ErrorInfo.metadataclassifyAdmissionOrigin 按 reason / gRPC code 定 origin。

配置

全部在 cmd/syncinvoker/config.go,环境变量覆盖 DefaultConfig() ShutdownTimeoutMs 固定 5000,没有环境变量覆盖。FunctionSnapshotRetention() 同时给 FCV syncer 和 ServiceConfig.MaxAcceptedDeadlineMs

观测

指标定义在 internal/obs/metrics_syncinvoke.go,口径与看板见 blockx 仓库 docs/sync-invoker-grafana.md。最重要的几个:
  • blockx_syncinvoke_calls_total{status, failure_code}:执行层终态计数(不含准入拒绝)。
  • blockx_syncinvoke_admission_denied_total{reason}:dispatch 前拒绝。
  • blockx_syncinvoke_call_duration_milliseconds / blockx_syncinvoke_billed_duration_milliseconds / blockx_syncinvoke_queue_wait_milliseconds:端到端墙钟、归因时长、排队。
  • blockx_syncinvoke_deadline_overruns_total{resolved_by}:超 deadline 的 call 由哪一层收敛(completed_late / cooperative_cancel / in_process_timeout / executor_reaped / escalation_reap)。
  • blockx_syncinvoke_executor_reaps_total{cause} / blockx_syncinvoke_executor_lost_victims_total{cause}:hard kill 频率与连坐量,健康时应为 0。
  • blockx_syncinvoke_precheck_total{result}
tracing:obs.Tracer("blockx-syncinvoker"),span syncinvoker.invoke 下有 resolve_source / admission / dispatch / wait_terminal / build_output

部署形态

sync-invoker 作为与 worker 平行的独立 fleet 部署(EC2 systemd + nerdctl + host containerd sandbox),gRPC 9900、metrics/health 9901;详见 部署概览 与 blockx 仓库 docs/deploy/syncinvoker-systemd-nerdctl.md

扩展点

  • 新增失败分类:改 classifyFailure / failureRetryable / classifyFailureOriginservice.go),若要新 code 需同步 core.FailureCode、proto FailureCodefailureCodeToProtofailureCodeLabel
  • 新增 admission 策略:在 core/admission.go 加纯函数,在 Service.admit 里按 ServiceConfig 开关切换;只依赖 ExecutorLoad
  • 新增只读 IO backend:在 cmd/syncinvoker/io.goioBackendModules 追加一个 assembly.Module,与 cmd/worker 保持同款注册。
  • 新增 FCV 来源:在 cmd/syncinvoker/function_code.gosetupFunctionCodeView 增加分支,返回 functionCodeSetup{view, start, close, desc, devstub}
  • 新增 RPC:改 api/grpc/syncinvoker/sync_invoker.protomake protoGRPCServer 加 handler → Service 加传输无关的 XxxInput / XxxOutput
  • 新增配置项:Config 字段 + DefaultConfig + LoadConfigFromEnvChecked 里的 envXxx,需要时透传到 ServiceConfig

测试

  • internal/syncinvoker/core/*_test.goRegistry 终态门、executor 健康 / attach generation、PickExecutor / RankExecutors
  • internal/syncinvoker/adapters/service_test.go:admission、subcall 竞态、onDeadline / onClientGone、duration 聚合、失败分类;audit_gate_test.go:enforce / dark / allowlist;precheck_test.go:Precheck 各种返回码。
  • cmd/syncinvoker/syncinvoker_process_test.gostartSyncInvoker 起真实进程,覆盖 inline / function_id / debug / 只读 IO / 写拒绝 / subcall / RESOURCE_EXHAUSTED / CPU hog 自愈 / wedge hard kill / graceful shutdown / health probe。
  • cmd/syncinvoker/syncinvoker_perf_test.go:延迟分解 perf 测试;syncinvoker_sandbox_e2e_test.go 带 build tag sandbox_e2e
  • Makefilecmd/syncinvoker 列为 SLOW_TEST_PACKAGESmake test 对它逐文件跑(每个文件独立 SYNCINVOKER_TEST_TIMEOUT,默认 3m),避免整包超时。
更多见 测试

相关文档

  • blockx 仓库 docs/specs/sync-invoker.md:形态、协议、admission、deadline 分层、subcall、生命周期、复用边界、归因、容量。
  • blockx 仓库 docs/specs/sync-invoker-failure-origin.mdFailureOrigin 语义与映射表。
  • blockx 仓库 docs/sync-invoker-grafana.md:指标清单、PromQL、排障路径。
  • blockx 仓库 docs/deploy/syncinvoker-systemd-nerdctl.md:EC2 runbook。
  • 站内:WorkerCall 执行子系统Python ExecutorFunction Code ViewIO 访问子系统可观测性协议与接口