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