forked from lei.chen/myxce
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>
145 lines
8.8 KiB
Markdown
145 lines
8.8 KiB
Markdown
# 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 翻译成人话场景
|