apiserver: apply the controller's jobPidInfo start-report push

handleNotifyData had no case for NotifyData_data_jobPidInfo, so the
controller's start-report push (apiserver_job_start_fun, fired from
HOOK_DETAIL_JOB_START the moment WAL_JOB_START commits) was silently
dropped. interactive_job_url then stayed invisible until the next
periodic rusage-driven job UPDATE re-delivered the full row — up to
10s later (job_rusage_collect_interval), the dominant chunk of an
-I --tty job's submit-to-first-output latency (measured ~6s on t1,
server-side work itself is ~0.6s).

Add OnJobPidInfo to the notification interface and DefaultHandler:
it merges the worker-reported pids/pgids and the exdata {"url": ...}
onto the already-stored job record (get-mutate-put, mirroring
OnJobFinished) and publishes a job.updated event. The CGO decode
side is factored into cPidInfoToRaw/pidInfoJSONB, shared with the
existing xce_job_t conversion.

Cluster-verified on t1: URL is pollable ~0.6s after submit
(previously ~11s), full submit/run/finish lifecycle clean, all
daemons healthy.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
lei.chen 2026-10-15 10:08:23 +00:00
parent 66a1d1e680
commit 3545aa0d77
6 changed files with 168 additions and 16 deletions

View file

@ -93,6 +93,27 @@ func cJobArrayToModels(jobArray *C.xce_job_submit_t) []*models.JobInfo {
return rawJobArrayToModels(uint64(jobArray.jobid), uint64(jobArray.array_size), rd)
}
// cPidInfoToRaw decodes a C job_pidinfo_t — the worker's start report
// (pids/pgids plus exdata, whose {"url": ...} JSON is the interactive
// session URL for ttyd/XJOB jobs) — into the rawPidInfo the pure layer
// consumes. Shared by cJobToModel (the full xce_job_t conversion, where the
// capnp Job wire carries pidinfo at Job.pidinfo@9) and the standalone
// jobPidInfo notify path (NotifyData_data_jobPidInfo), which delivers the
// same struct the moment WAL_JOB_START commits.
func cPidInfoToRaw(pi *C.job_pidinfo_t) *rawPidInfo {
if pi == nil {
return nil
}
p := &rawPidInfo{exdata: goString(pi.exdata)}
for i := 0; i < int(pi.npids); i++ {
p.pids = append(p.pids, int(C.pidinfo_pid(pi, C.int(i))))
}
for i := 0; i < int(pi.npgids); i++ {
p.pgids = append(p.pgids, int(C.pidinfo_pgid(pi, C.int(i))))
}
return p
}
// cJobToModel extracts C xce_job_t fields into a rawJobData and maps it to JobInfo.
func cJobToModel(job *C.xce_job_t) *models.JobInfo {
r := rawJobData{
@ -207,17 +228,7 @@ func cJobToModel(job *C.xce_job_t) *models.JobInfo {
// capnp Job wire carries pidinfo (Job.pidinfo@9 / JobPIDInfo.exdata@3)
// but the conversion dropped it, so interactive_job_url never reached
// the API and xcecli's browser flow could never fire.
if job.pidinfo != nil {
pi := job.pidinfo
p := &rawPidInfo{exdata: goString(pi.exdata)}
for i := 0; i < int(pi.npids); i++ {
p.pids = append(p.pids, int(C.pidinfo_pid(pi, C.int(i))))
}
for i := 0; i < int(pi.npgids); i++ {
p.pgids = append(p.pgids, int(C.pidinfo_pgid(pi, C.int(i))))
}
r.pidInfo = p
}
r.pidInfo = cPidInfoToRaw(job.pidinfo)
return rawJobToModel(r)
}