myxce/docs/pend-reason-investigation-design.md
lei.chen f66a626bae docs: PEND reason investigation + classification design (LSF-aligned)
Survey of every mechanism that can block a job from dispatch across the
scheduler (election → allocation → commit): 18 blocking points in 6
categories. Only 3 produce a PEND reason (MATCH_FAIL + 2×NO_RESOURCE);
the limit plugin (4 configured limits), queue state, dispatch_window and
fairshare are configured but silent; 8 LSF-standard reason categories
(dependency/license/reservation/exclusive/worker-state/...) are missing or
stubbed. Includes a layered reason taxonomy and wiring priorities.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-13 10:24:32 +00:00

145 lines
8.8 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 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 翻译成人话场景