fix(apiserver): detail-only PEND reason promotion was unreachable on the C path
cJobToModel pre-filled main_reason_name with pendReasonName(main_reason), so a detail-only record (main_reason 0, the allocator's ordinary shortfall shape) arrived at rawJobToModel named "unknown(0)" — and the promotion that derives the overall reason from the first detail row requires an EMPTY name. The promotion's own tests passed because they build rawJobData directly; the live C path never hit it. Found by the advisor P0 verification: jobs whose res_reasons named no_resource read main_reason_name=unknown(0) over the API. main_reason 0 now leaves the name empty for the promotion to fill; a non-zero main reason keeps its mapped name unchanged. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
4db1aa8aef
commit
3e3b6d7947
2 changed files with 54 additions and 1 deletions
|
|
@ -201,9 +201,19 @@ func cJobToModel(job *C.xce_job_t) *models.JobInfo {
|
|||
// baselines encode main_reason=0 detail-only PENDs).
|
||||
if job.pend_reasons != nil {
|
||||
pr := job.pend_reasons
|
||||
// main_reason 0 is detail-only (the allocator's ordinary shortfall
|
||||
// shape): leave the NAME empty so rawJobToModel's promotion fills it
|
||||
// from the first detail row. Pre-filling pendReasonName(0) here
|
||||
// ("unknown(0)") made the promotion unreachable on the live C path —
|
||||
// the advisor P0 verification caught jobs whose res_reasons named the
|
||||
// real cause while main_reason_name read unknown(0).
|
||||
mainName := ""
|
||||
if pr.main_reason != 0 {
|
||||
mainName = pendReasonName(int(pr.main_reason))
|
||||
}
|
||||
r.pendReason = &rawPendReason{
|
||||
MainReason: int(pr.main_reason),
|
||||
MainReasonName: pendReasonName(int(pr.main_reason)),
|
||||
MainReasonName: mainName,
|
||||
}
|
||||
for i := 0; i < int(pr.nhost_reasons); i++ {
|
||||
hr := C.job_host_reason(pr, C.int(i))
|
||||
|
|
|
|||
|
|
@ -1656,3 +1656,46 @@ func TestFormatJobID_ArrayChild(t *testing.T) {
|
|||
t.Errorf("FormatJobID(array) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// The C path (cJobToModel) must NOT pre-fill main_reason_name for a
|
||||
// detail-only record: pendReasonName(0) = "unknown(0)" made the pure-layer
|
||||
// promotion unreachable on the live path (advisor P0 verification finding —
|
||||
// jobs whose res_reasons named the real cause while main_reason_name read
|
||||
// unknown(0)). This pins the shape cJobToModel now hands to rawJobToModel:
|
||||
// main_reason 0 with an EMPTY name (res detail rows as the C record carried
|
||||
// them) must promote no_resource, and a non-zero main reason keeps its name.
|
||||
func TestPendReasonShape_CPathDetailOnlyPromotes(t *testing.T) {
|
||||
// what cJobToModel builds for main_reason 0 since the fix: name empty
|
||||
r := rawJobData{
|
||||
jobID: 11,
|
||||
state: 1,
|
||||
pendReason: &rawPendReason{
|
||||
MainReason: 0,
|
||||
MainReasonName: "", // cJobToModel leaves it for the promotion
|
||||
ResReasons: []rawResReason{{Resource: "limit=limit1", Host: "", Reason: 2}},
|
||||
},
|
||||
}
|
||||
info := rawJobToModel(r)
|
||||
var decoded struct {
|
||||
MainReason int `json:"main_reason"`
|
||||
MainReasonName string `json:"main_reason_name"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(info.PendingReason.String), &decoded); err != nil {
|
||||
t.Fatalf("PendingReason invalid JSON: %v", err)
|
||||
}
|
||||
if decoded.MainReasonName != "no_resource" {
|
||||
t.Errorf("detail-only via C shape: main_reason_name = %q, want promoted no_resource", decoded.MainReasonName)
|
||||
}
|
||||
|
||||
// non-zero main reason keeps the mapped name (promotion must not touch it)
|
||||
r2 := r
|
||||
r2.pendReason = &rawPendReason{MainReason: 6, MainReasonName: "queue_closed"}
|
||||
info2 := rawJobToModel(r2)
|
||||
var d2 struct {
|
||||
MainReasonName string `json:"main_reason_name"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(info2.PendingReason.String), &d2)
|
||||
if d2.MainReasonName != "queue_closed" {
|
||||
t.Errorf("explicit main reason overwritten: got %q, want queue_closed", d2.MainReasonName)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue