# 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 范畴,需单独排期 | ## 四、验收标准(建议) 1. `main_reason` 非 0 且有意义的 reason 名 2. 限额定额命中 → 返回 `LIMIT_xxx` + 具体 limit 名 3. 队列关闭/时间窗外 → 返回对应 reason 4. `bjobs -p` 等价能力:MCP 工具能把 reason 翻译成人话场景