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

168 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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