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

8.8 KiB
Raw Blame History

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