# XCE 调度器现存问题清单(gaps 分析) > 生成时间:2026-10-16;来源:t1/t2/t3 集群实测 + 调度器源码逐模块审查(feature/x11-forwarding 分支视角)。 > 背景:t1 亲和性根因已确认(first-fit × 插入序槽位,见 memory xce-firstfit-t1-affinity),本文记录**除 t1 亲和外的**调度器问题。 > 约定:本文档只做记录,未做任何代码改动。 > > **2026-10-20 增补**:openmpi 回归(OS openmpi 4.1.6 跨机跑通,见 memory xce-mpi-regression)与全框架评审后新增"五、架构层改进项"(11–17)与"六、构建/分发系统",原有 1–10 维持不变。 --- ## 一、策略缺失类(功能"看起来有、实际没生效") ### 1. 作业级优先级全链路断裂 ⭐ 最严重 这条链路上每一环都是断的: | 环节 | 现状 | 位置 | |---|---|---| | wire schema | `JobSubmitData` 没有 priority 字段,提交时无法携带 | src/schema/controller.capnp:583 | | scheduler 入口 | `job->priority = 0; /* FIXME */` 硬编码 | core_events.c:353、schedobj.c:214 | | 排序器 | builtin_fcfs 的 `job->priority` 降序比较器因恒为 0 而成死代码 | plugins/builtin_fcfs.c:190-215 | | Go API 层 | apiserver 的 UserPriority/JobPriority 模型字段没映射到 wire | apiserver Go models | **实际效果**:只有队列优先级在起作用,同队列内提交顺序即调度顺序,用户无法插队/降级。 **修复面**:wire schema 加字段(注意 master/worker/controller/scheduler 同版本 ABI 约束)+ core_events/schedobj 填充 + Go API 提交路径映射。改动面中等,对用户价值最高。 ### 2. 抢占(preemption)不存在 `make_preempt_decision`(src/scheduler/src/decision.c:35)已完整实现,但全库只有 testsuite 调用——没有任何 live 路径会触发它。高优先队列来了也只会排着,不会踢低优先作业。 ### 3. 无 fairshare 只有静态配额(user/queue/host 的 slot、mem 上限)。多用户共享集群时,先来的人可以连续吃满配额,没有按用户/组的份额均摊或历史使用量惩罚机制。builtin_fcfs 排序维度只有:队列优先级 →(死的)作业优先级 → 提交时间。 ### 4. 队列 sched_delay / accept_interval 是死配置 这两个字段被复制进 scheduler 的 queue 结构,但没有任何代码路径消费它们(仅一个 controller 测试 fixture 引用)。管理员配了也不生效,属于会误导人的配置项。 --- ## 二、吞吐 / 时延类 ### 5. 轮次节奏 + 容量 1 的管道 = 吞吐下限 派发链路是三级串行管道: - **主循环 sleep(1)**:每轮间隔 ≥1s(src/scheduler/src/main.c:552); - **jbuf 容量 1**:picker 每轮只递给 allocator 一个作业,allocator 消化完才能拿下一个(src/scheduler/src/core.c:151); - **decisions 容量 5 硬编码**:`int ndecisions = 5; /* FIXME: make this configurable */`(src/scheduler/src/core.c:87)。 **实际效果**:即使所有资源充足,派发速率也被压在 ~1 作业/秒量级;大量 PEND 作业排队时这个下限直接可见(也是 `-I --tty` 首输出延迟问题中我们只能优化 CLI 轮询退避、改不了调度侧的原因)。 **修复面**:sleep 与容量参数化,低风险高收益。 --- ## 三、健壮性类 ### 6. alloc_rc < 0(内部错误)时作业"失踪"一轮 sched_election.c:365-370:allocator 内部错误时**不下发任何 decision**(既不派发也不回 PEND-with-reason)。作业要等下一轮 session 重新被捡起来,且用户看不到任何 reason。对照 bug-1512 的教训(派发失败必须有明确语义),这条内部错误路径的语义是缺失的。 ### 7. ALTER 作业的 PEND reason 只取一个 child src/scheduler/src/scheduler.c:491 `/* FIXME: merge pend reason from all children */`——多备选作业挂在某个备选上时,report 出去的 reason 未必是"主"原因。与 PEND reason 全链路补全(ac6131d)的精神相悖,是漏网之鱼。 ### 8. 多机作业内存用量无法对账 sched_reconcile_job_resource_usage(src/scheduler/src/resource.c:965):rusage 上报的 wire 格式没有 host 归属,所以只对单机作业做 mem 释放对账,多机作业永远停留在按 host 摊派的初始预留值。长期运行会造成幽灵占用 → 影响后续 fit 判断(当前 t1 亲和恰好掩盖了它)。 ### 9. 90 秒心跳窗口内向死 worker 派发 worker 掉线要 `heartbeat_interval(30s) × inactive_heartbeat_count(3) = 90s`(src/configs/xce.yaml)才被标 UNAVAIL(src/controller/cluster.c:2215 check_worker_timeout_)。这期间 allocator 照常往它派发,作业进 worker 后要么 IPC 失败回 PEND,要么挂着没响应。且 UNAVAIL 触发时**没有作业重排机制**——挂在它上面的作业要靠 dispatch-fail ack(src/scheduler/src/handler.c:384)或看门狗兜底。 ### 10. resource.c:602 空 FIXME 错误路径上是空的 `/* FIXME: */`——具体失败场景无注释,值得单独看一眼。 --- ## 四、运维 / 历史类 | 项 | 说明 | |---|---| | **notifyAcked opcode-45 版本偏差** | t1 新 worker(a8c3e91)发 `OPCode_notifyAcked`(bug-1741),运行中的 controller(c3acd62)不认识,日志刷 "Unknown opcode: 45"。升级 controller 即消。与调度无关但污染告警 | | **历史 "free(): invalid pointer"** | t1 /opt/xce/logs/xce_scheduler.out 有 3 条(Sep 1 时代)。与 bug-1513(已修)时代吻合,建议确认现版本二进制无同类残留 | | **legacy fcfs 插件仍注册** | fcfs.c 走 deprecated `job_elect_fn` 钩子,启动 WARN "deprecated job_elect_fn hook"。与 builtin_fcfs 两条路径并存增加维护面 | | **jobs kvmgr nslots=5381 硬编码** | src/scheduler/src/core.c:94 FIXME,与 ndecisions=5 同源 | | **kvmgr 槽位序 = 派发序** | 即已确认的 t1 亲和根因:worker 遍历顺序无策略介入,sched_claim_bucket_ 取第一个过 filter 的候选(alloc_runner.c:219),槽位序继承自 snapshot restore 链(controller/worker.c:564 restore_host_state → wobj.c:220 key_add EEXIST 保序) | --- ## 优先级建议(如果要动手) 1. **t1 亲和**(已确认根因,改 start 策略即可见效)→ 2. **作业级优先级**(wire schema + core_events + Go API 三处,改动面中等但对用户价值最高)→ 3. **轮次节奏/容量参数化**(低风险高收益)→ 4. **alloc_rc<0 decision 语义补全** → 5. 其余按需。 (2026-10-20 补:架构层评审新增 #11–#17 与构建/分发系统一节,动手顺序见文末"架构层动手顺序建议"——共享复制层第一,TSAN/写者规则第二。) --- ## 五、架构层改进项(2026-10-20 全框架评审新增) > 来源:feature/scheduler-advisor 分支视角的整框架评审;每条都对应一次实际故障或 2026-10-20 openmpi 回归现场。 ### 11. 一致性路径每子系统各造一套,ack 语义无统一定义 config push(config_repl.c WAL/committed-marker/hash-verify)、scheduler 状态同步、worker resync 各自实现复制与 ack。已知的直接后果: - **ackedCount==0 误报**(2026-10-20 现场复现两次):无 standby 集群里 `do_upgrade_config_with_bytes` 提交成功(majority=primary 单机),但 reply 的 `acked_count` 只计 standby ack,xceadmin 误报 "push rejected (ackedCount == 0)"(config_repl.c:1863-1892)。majority 是否含自身没有统一定义。 - **config-push 屏障 stale-ack 缺陷**:E2E 确定性红,修复已知(repl.c 等值判断 + 2 个测试点)但长期未修——长红的测试等于没有测试。 - **rejoin 三连崩溃**(bug-1513):共享串 raw-free、limit key_add EEXIST double-free、config-vs-job 竞态——rejoin 状态重同步是第三套手工一致性代码。 **建议**:把 config_repl 那套 WAL/marker/hash-verify 抽成共享复制层,ack 语义定义一次(majority 含自身),三个 bespoke 路径全部收编。一个动作同时消灭至少三个已知缺陷类别。 ### 12. 核心组件状态变更不传播,靠 join 快照 + 重启兜底 - `xcesched_push_configs`(sched.c:139)唯一调用点是 `on_scheduler_join`(sched.c:768-793)——config push 到达不了运行中的 scheduler,limit 改动必须重启 scheduler(2026-10-20 现场两次重启)。`config_activate_committed` 只刷新 controller 自身 `c->myconf`,`reconcile_config_queues` 只建新队列。 - worker 重连后的状态补报靠透明 failover 协议手工补(9d7628e)+ bug-1522(xcecli_start 清 rejoin_cb 使 reconnect_cb 成死代码)这类补丁。 - EventBroker/SSE 只加在 API 层(advisor findings 推送),没进控制面。 **建议**:controller 发布带版本号的 jobs/hosts/configgen 增量流,daemon 订阅而非 join 快照。config 传播是它的第一个收益(顺带消灭"改 limit 要重启 scheduler")。 ### 13. 共享可变状态无单一写者纪律(随机堆损坏的根因类别) - bug-1521:fcfs picker 线程写 `job->schedobj->reason`,allocator 线程对同一字段 MOVE_PTR——堆损坏随机崩溃,#17 绕开(不 attach)但未修。 - PEND reason 需要"全链路补全"(ac6131d)逐段手工穿线,说明 reason 的归属权从未定义。 - 这类竞态现有单测抓不到(union 变异测试无效的教训)。 **建议**:job 结构每字段标注唯一写者(或 reason 改 owned/RCU 归属),**TSAN 进 CI**。1513+1521 两次各耗数天的排查成本远超预防成本。 ### 14. 契约无文档,合并丢逻辑 main_reason 契约在两个代码库各自演化,scheduler-framework 合并时丢 PEND-reason P1(见 memory xce-merge-drop-audit)。job 状态机 + reason 来源枚举应落成一页文档 + 共享头文件;合并审计时对照的"契约清单"应存在 repo 里而不是人脑里。 ### 15. 调度策略与容量检查耦合,跨机拆分只做均分(2026-10-20 实测) - 布局 t2:5/t3:2 空闲时 `-n 6`/`-n 7` PEND——总量够但不均分拆分不走(t2:4,t3:2 理论可行);均分 3+3 放不下即 PEND。`-host t2:2,t3:2`(-n4)、`t2:3,t3:3`(-n6)、`t2:4,t3:4`(-n8)实测均分拆分正常。 - 拆分成功的先决条件是"每台都放不下整个作业",first-fit + 插入序槽位(见 t1 亲和条目)共同导致放置结果依赖 worker 遍历序。 - PEND 时 `pending_reason` 依旧显示 '-',用户无从知道"总量够但拆分策略不放"。 **建议**:placement 抽成策略层(pack/spread/fairshare 可插拔 + span[nodes]/每节点最少 rank 旋钮,对齐 LSF/SGE 用户词汇);PEND reason 对"策略性拒绝"给出诚实文案。 ### 16. IPC 协议卫生:手工 union 与 capnp 并存、定长缓冲、jobid 位打包 - `ipc_message_t` 手工二进制 union 与 capnp 并存:bug-1936/1957 的 ABI skew 要求 daemon 成对替换(见 memory xce-ipc-abi-skew),无法滚动升级。建议控制面消息全面迁 capnp + 版本协商,加 `xceadmin preflight` 检查节点版本偏斜。 - 定长缓冲:`job_assignment_t.cmd` 4096 截断(COMMAND_LENGTH 系列)——长 MPI 命令行(PRTE_PREFIX+LD_LIBRARY_PATH 前缀的 prted 命令实测逼近上限)随时可截断。 - jobid 位打包(20 位 sequence / 32 位 timestamp 会绕回)同时当主键、systemd 单元名、分片路由键:stale unit "already loaded"(2026-10-20 现场撞到)与截断解码出垃圾 ID 都与之相关。ID 内部可打包,对外应是不透明字符串 + epoch。 ### 17. 作业执行上下文无显式契约(MPI 类环境敏感负载的隐患源) 2026-10-20 nix prted 崩溃调查的整条链都源于此: - systemd 瞬态单元不设 `Environment=`(xcecd_cgroups.c start_systemd_job);任务继承 worker env(uv_proc.c:205 `options.env = NULL`)。 - xrun 依赖 `XCE_JOBID` 这个从未写进文档的继承契约(worker apply_env_vars → mpirun → xrun env 继承),传错粒度(timestamp 而非全 uint64)只在 master 日志 DEBUG 里报。 - cwd 无权限时静默 fallback 到 /tmp 只打 WARN(uv_proc.c:522)。 - master daemon 日志 INFO 级看不到 remote-start 回执,排查只能靠副作用。 **建议**:定义显式 JobExecutionContext(env 白名单、cwd 解析规则、用户映射、limits),所有启动路径(本地/systemd/remote-start/interactive/MPI relay)共用并校验,失败大声报错。 --- ## 六、构建 / 分发系统(2026-10-20,用户提出重构意向) 背景:nix 是个别同事未经商定替换原 makefile 系统引入的。EDA 客户及潜在客户无人用 NixOS 当 server;nix 的配置逻辑诡异、使用反人类(`nix develop` + click/pycapnp 仅存在于该 shell、`--option sandbox false` 咒语、未 git-add 的新文件不进构建)。openmpi 回归中 nix openmpi 5.0.11 的 prted 在 XCE relay 启动环境必崩(OS openmpi 4.1.6 正常),进一步印证 nix 工具链在非常规启动环境下的脆弱。 **建议方向:构建归构建、分发归分发,产品路径完全去 nix 化。** - 构建:统一 CMake(或 meson);依赖用系统包管理器声明,裸 Ubuntu `apt install build-essential libuv-dev ...` 可出全部产物。 - 分发:tarball + systemd unit + 安装脚本(/opt/xce 布局已接近该形态);需要自带依赖时用容器镜像(docker/podman),可理解性远高于 AppImage 解包 nix store 闭包(xce-appimage-cluster-script 里的坑)。 - CI:标准 Ubuntu 容器内 apt install + build + test,与客户环境一致。 - 迁移可渐进:nix 保留为 CI 内部多版本工具链矩阵,产品 build/分发不经过 nix。 - 先行动作:现状盘点(哪些环节被 nix 锁死:build/测试/分发/集群部署各自依赖什么),输出迁移顺序草案。 --- ## 架构层动手顺序建议(2026-10-20) 1. **共享复制层(#11)**——同时消灭最多已知缺陷类别(ackedCount 误报、stale-ack 红测试、rejoin 崩溃类); 2. **TSAN + 写者规则(#13)**——随机堆崩溃排查成本(1513、1521 各耗数天)远超预防成本; 3. **状态传播(#12)**——顺带解决"改 limit 要重启 scheduler"; 4. **placement 策略层(#15)**——EDA 客户最先撞上的墙; 5. 其余(#14、#16、#17)与构建系统重构("六")并行推进。 --- ## 附:排查方法论备忘(可复用) - **gdb 活体结构遍历**:`static xce_scheduler_t sched`(main.c:202)二进制缺完整 DWARF 时,按裸地址 `x/64gx` dump,手工映射 kvmgr {key_type@0, nslots@4, kvtab@8, data@16} 与 xzarray_t {count@0, caps@8, delta@16, pos@24, data@32, killer@40}(zarray.c:392 实际布局,非 include/zarray.h 旧布局),用已知常量(queues nslots=59、workers 5381、delta=64)校验。 - **ssh+gdb 引号**:外层单引号包整条 ssh 命令,内层用双引号。 - **作业分布统计**:`grep -oE 't[0-9]+'` 数 hostname,避免 awk 按列切分被提交日期干扰。