myxce/gaps.md
lei.chen 50cf424146 build: remove nix dependency layer -- apt system libs + meson wrap subprojects
The build no longer self-wraps into `nix develop`: src/tools/xce-build.sh now
runs meson+ninja directly on system packages, with a dependency preflight
that fails early naming the missing tool/pkg-config package (DEPS.md).

Dependencies (see DEPS.md for the full tables):
- 11 C libraries from apt (libuv, openssl, libedit, sqlite3, hwloc, libcgroup,
  libtirpc, libcyaml, libyaml, zlib, libsystemd) with version floors in
  meson.build mirroring the Ubuntu 24.04 baseline.
- sd-bus: default libsystemd, -Duse_elogind=true opt-back for nix-era hosts.
- 6 libraries/tools unavailable in apt become committed meson wrap
  subprojects under src/subprojects/ (source closure, ~500 files): the
  upstream trees plus packagefiles/ overlays and diff_files patches
  registering dependency/program overrides the nix .pc files used to
  provide (incl. the '<name>-raw' variable deps the Go CGO path reads).
  Fresh-clone reproducibility verified: wiped trees + `meson subprojects
  download` reproduce the committed state exactly (fixed librtti.wrap's
  dangling patch_directory and c-capnproto's empty packagefiles overlay).
- xceadmin: self-contained venv built from requirements-build.txt (pinned,
  offline-seedable via PIP_FIND_LINKS) instead of --system-site-packages.

Build/packaging:
- Makefile facade over xce-build.sh; scripts/ci-ubuntu.sh drives a bare
  Ubuntu 24.04 container through deps + build + tests.
- src/tools/xce-package.sh: release-tree tarball packager. Collects
  non-base DT_NEEDED libs from the BUILD-tree binaries (staged ldd lies:
  the dangling build RPATH makes ldd drop the very lines the naive filter
  needs), sets $ORIGIN RPATHs, rebases the xceadmin venv at install time
  (kernel does not parse quotes in shebangs), ships systemd unit samples +
  install.sh. Local smoke test: all 8 binaries resolve, xceadmin works from
  both bin/ wrapper and venv script.

Test fixes found on the way (all environmental, not build bugs):
- harness_test.c setUp(): pin XCE_CONTROLLER_PEERS to a bound-then-closed
  loopback port so the CLI-error tests assert the no-controller failure
  path even on a dev box running a live cluster on localhost:9990.
- test_test_cli.c: setpgid(0,0) in the spawned-xce_test child. libedit's
  sig_handler re-raises received signals via kill(0, sig) -- process GROUP
  wide (strace -k evidence) -- and the child previously shared the
  harness's pgrp, so an interrupted SSL handshake killed the whole suite
  (exit 130).

Also fixed while re-baselining on the de-nix toolchain (pre-existing, now
deterministic): raft_init zeroes pre-vote tally state (pre_vote_in_progress/
pre_votes_granted/pre_vote_replies were left at stack garbage);
push_config_to_standbys clears stale config acks before fan-out
(repl_reset_config_acks) -- SHA-256-derived tags are not monotonic, so a
previous push's ack numerically above the new tag fabricated a barrier
quorum; capnpc-c's generated encode_* memsets the destination first
(skipped Data fields fed garbage into capn_setp).

Validation: asan profile 161/161 meson tests green (one known p2_integration
-110 timing flake on first run, green on re-run); debug + release full
builds OK; tarball smoke-tested on the build host. Cluster rollout on
t1/t2/t3 follows.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:04:22 +00:00

15 KiB
Raw Blame History

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 按列切分被提交日期干扰。