XCE PEND Reason:现状调查与分类体系设计
状态:调查完成 + 设计稿(2026-08-13)。对标 LSF bjobs -p / Slurm squeue 的 PEND reason 体系。
背景:POC 需要"为什么我的作业在等"的解释能力。上一阶段已打通 scheduler → controller → apiserver → API/SSE 的 PEND reason 数据链路(bug-1513,commit ac6131d),但喂进去的数据只有 2 种原因。本文调查"会挡作业的机制到底有哪些、各自有没有 reason",并给出分类体系与接线方案。
一、现状调查
1.1 已定义的 code(preason.h)
| code |
常量 |
状态 |
| 1 |
PREASON_BAD_RESREQ |
死代码——头文件定义,调度器从未产生过 |
| 2 |
PREASON_NO_RESOURCE |
使用中(host 容量 + shared 容量两处) |
| 3 |
PREASON_MATCH_FAIL |
使用中(select 不匹配 host) |
另有 main_reason 字段恒为 0:add_job_preason() 只在 host 和 res 都为空时才设 main_reason(preason.c:140),而调度器 allocator 从不这样调用(MATCH_FAIL 带 host、NO_RESOURCE 带 host+res),所以总原因层永远为空,只有明细层(host_reasons / res_reasons)有意义。
1.2 会挡住作业的机制全清单(选举 → 分配 → 提交全路径,共 18 处)
A. 已实现且会产生 PEND reason(3 处)
| # |
code |
场景 |
触发点 |
reason 形态 |
| 1 |
MATCH_FAIL(3) |
resreq select 条件不匹配 host 属性(type == nosuchhost、指定 hostname 等) |
resource.c:482 sched_meet_host_selection 失败 |
每失败 host 一条 host_reason{host, 3} |
| 2 |
NO_RESOURCE(2) |
host 匹配但资源容量不足(slots/mem/其它 consumable) |
resource.c:522 sched_check_host_satisfy 失败 |
每不足资源一条 res_reason{resource, host, 2} |
| 3 |
NO_RESOURCE(2) |
共享资源容量不足(含全局限额折算的 vres) |
seqjob_allocator.c:148 sched_check_shared_satisfy 失败 |
每不足资源一条 res_reason{resource, host=空, 2} |
B. 已实现但阻挡时【不产生】reason(2 处)
| # |
机制 |
说明 |
| 4 |
limit 插件 host-based 限额(limit.c:414 limit_prepare_allocation) |
命中即分配失败 → sched_try_alloc_on_host_ 返回负值 → 无 decision、无 reason,作业静默 PEND。集群配置存在 hg1_slots_per_host |
| 5 |
limit 插件全局限额(limit.c:221 limit_job_election) |
命中后返回 rc<0,但调度主循环忽略 election 返回值(scheduler.c:251);实际经 sharedInst 折算进 shared 容量检查,表现为笼统的 NO_RESOURCE(shared vres),语义上"限额"与"资源不足"无法区分。集群配置存在 ug1_slots/ug2_slots_per_user/night_mem_pct/ug1_normal_slots |
C. 基础设施错误(rc<0 → 主循环 continue,无 decision 无 reason)(3 处)
| # |
机制 |
位置 |
| 6 |
找不到 primary resreq |
scheduler.c:109 |
| 7 |
slots 资源缺失 / prepare_allocation -EINVAL/-ENOENT |
seqjob_allocator.c:63-75 |
| 8 |
alter 全失败、reason 不合并(/* FIXME: merge pend reason from all children */) |
scheduler.c:186 |
D. 状态门(1 处)
| # |
机制 |
| 9 |
作业被挂起 PSUSP/USUSP/SSUSP → 不参与选举(scheduler.c:1363 sched_job_is_ready)。hold/release 走这里;合法"等待"原因但非 PEND reason |
E. 配置里有机制、调度器完全无 reason(8 类缺失/桩)
| # |
机制 |
证据 |
| 10 |
队列状态(closed/disabled/draining/runlimit) |
sched_queue_is_ready 恒 1 的 stub(scheduler.c:1372 /* FIXME this is just stub */);queues.yaml 配了 runlimit: 20 |
| 11 |
时间窗 dispatch_window |
queues.yaml 配了 start_time: 19:00;调度器无处理 |
| 12 |
fairshare |
queues.yaml 配了 fairshare: ug1 1000;fcfs 只按提交序 |
| 13 |
依赖 depend_cond |
调度器无处理 |
| 14 |
license |
调度器无处理 |
| 15 |
保留/预留 RsvID |
调度器无处理 |
| 16 |
exclusive |
调度器无处理 |
| 17 |
worker 状态过滤 |
skip_bad_worker_ 空 stub(seqjob_allocator.c:35 /* add more check here */),坏 worker 不跳过 |
F. Controller 侧(1 处)
| # |
机制 |
| 18 |
worker 不可用 → dispatch_a_job_ 拒派 → jobDispatchFail → scheduler re-PEND(bug-1510/1512 已处理;属 dispatchfail 语义,非 PEND reason) |
1.3 统计结论
- 已实现的阻挡点:8 处(A3 + B2 + C3)
- 其中能产生 PEND reason 的:只有 3 处(A 类)
- LSF 必有 reason 的类别,XCE 缺失/桩:8 类(E 类)
- 配置里实际启用但答不上"为什么"的:
runlimit、dispatch_window、fairshare、4 种限额(B5)
main_reason 恒 0:总原因层为空,仅明细层有意义
二、PEND reason 分类体系设计(对标 LSF/Slurm)
按 XCE 实际已有的机制分层,每层定义 reason 类 + 触发语义 + 优先级。
层级 0:状态门(不是 reason,但解释"为什么")
| 状态 |
含义 |
来源 |
| PSUSP/USUSP/SSUSP |
作业被 hold/挂起 |
用户/管理员操作 |
| 新提交 |
"等待首次调度" |
提交后首轮 |
层级 1:资源类(已有基础,需完善)
| code |
reason |
场景 |
状态 |
| 3 |
MATCH_FAIL |
select 条件不匹配任何 host(resreq 写的条件无节点满足) |
✅ 已有 |
| 2 |
NO_RESOURCE(host) |
host 匹配但容量不足(slots/mem 满) |
✅ 已有 |
| 2 |
NO_RESOURCE(shared) |
共享资源不足 |
✅ 已有 |
| 3 |
HOST_UNAVAIL |
host 状态不可用(需实现 skip_bad_worker_) |
❌ 缺(E17) |
| 3 |
HOST_EXCLUSIVE |
需要排他但无空闲 host |
❌ 缺(E16) |
层级 2:限额/策略类(机制现成,缺 reason)
| code |
reason |
场景 |
状态 |
| 2 |
LIMIT_GLOBAL(含 limit 名+对象) |
全局限额命中(ug1_slots/ug2_slots_per_user/night_mem_pct) |
❌ 缺(B5) |
| 2 |
LIMIT_HOST |
host 限额命中(hg1_slots_per_host) |
❌ 缺(B4) |
| 2 |
QUEUE_RUNLIMIT |
队列运行作业数达上限 runlimit |
❌ 缺(E10) |
| 3 |
FAIRSHARE |
低于 fairshare 份额阈值 |
❌ 缺(E12) |
层级 3:队列类(配置已存在)
| code |
reason |
场景 |
状态 |
| 3 |
QUEUE_CLOSED |
队列 closed/disabled |
❌ 缺(E10,sched_queue_is_ready stub) |
| 3 |
QUEUE_DRAINING |
队列 draining(不收新作业) |
❌ 缺 |
层级 4:时间/调度类(配置已存在)
| code |
reason |
场景 |
状态 |
| 3 |
TIME_WINDOW |
不在 dispatch_window 内(queues.yaml start_time 19:00) |
❌ 缺(E11) |
| 3 |
WAIT_BEGIN_TIME |
未到 begin_time |
❌ 缺 |
| 3 |
PRIORITY_WAIT |
有更高优先级/更早作业排队 |
❌ 缺(fcfs 只有顺序无原因) |
层级 5:依赖/许可/保留类(字段存在,机制未实现)
| code |
reason |
场景 |
状态 |
| 3 |
DEPENDENCY |
depend_cond 未满足 |
❌ 缺(E13) |
| 3 |
LICENSE |
license 不可用/超限 |
❌ 缺(E14) |
| 3 |
RESERVATION |
等待 RsvID 保留期 |
❌ 缺(E15) |
code 复用策略
不新增枚举,复用现有 3 个 code 的语义并扩展明细:
main_reason 填总体类别:1=BAD_RESREQ(保留)、2=NO_RESOURCE(容量/限额)、3=MATCH_FAIL(条件/状态不满足,含队列/时间/依赖/许可)
host_reasons/res_reasons 承载精确语义:新增 reason 子码(如 MATCH_FAIL 下细分 QUEUE_CLOSED/TIME_WINDOW/DEPENDENCY),由 API 层映射成人话
- 或直接扩展 PREASON_* 枚举到 ~20 个,每个一个清晰语义(对齐 LSF 风格)
三、接线优先级建议
| 优先级 |
项 |
工作量 |
说明 |
| P0 |
修 main_reason(add_job_preason 补首个 code) |
小 |
让总原因层可用,一次 lib 重建 |
| P0 |
limit 插件 attach reason(B4/B5) |
中 |
机制现成(limit 名+对象已知),只差 attach;这是实际最常发生的"为什么没跑" |
| P1 |
队列状态 reason(E10) |
中 |
实现 sched_queue_is_ready(非 stub)+ attach reason |
| P1 |
时间窗 reason(E11) |
中 |
dispatch_window 已有配置,读配置即可判断 |
| P1 |
worker 状态过滤(E17) |
小 |
实现 skip_bad_worker_ + HOST_UNAVAIL reason |
| P2 |
依赖/license/保留/exclusive/fairshare |
大 |
机制本身也要实现,超出 PEND reason 范畴,需单独排期 |
四、验收标准(建议)
main_reason 非 0 且有意义的 reason 名
- 限额定额命中 → 返回
LIMIT_xxx + 具体 limit 名
- 队列关闭/时间窗外 → 返回对应 reason
bjobs -p 等价能力:MCP 工具能把 reason 翻译成人话场景