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

@ -606,6 +606,23 @@ func interactiveURLFromExdata(exdata string) string {
return obj.URL
}
// pidInfoJSONB marshals a start report's pids/pgids into the PIDInfo Jsonb
// the API exposes (processJobData parses the JSONB string before the row
// leaves the API, so clients see an object with pids/pgids). Shared by
// rawJobToModel (the xce_job_t conversion, which carries the full
// start report) and OnJobPidInfo (the standalone jobPidInfo notify, whose
// only extra payload is the interactive URL).
func pidInfoJSONB(pids, pgids []int) (models.Jsonb, bool) {
data, err := json.Marshal(struct {
Pids []int `json:"pids,omitempty"`
Pgids []int `json:"pgids,omitempty"`
}{Pids: pids, Pgids: pgids})
if err != nil {
return models.Jsonb{}, false
}
return models.Jsonb{String: string(data)}, true
}
// rawJobToModel maps a rawJobData to a models.JobInfo.
// This pure-Go function contains the field-mapping logic and is tested without CGO.
func rawJobToModel(r rawJobData) *models.JobInfo {
@ -714,11 +731,8 @@ func rawJobToModel(r rawJobData) *models.JobInfo {
// with pids/pgids. The interactive URL is promoted to its own
// top-level field — it is what xcecli's waitForInteractiveURL and
// the --browse flow consume.
if data, err := json.Marshal(struct {
Pids []int `json:"pids,omitempty"`
Pgids []int `json:"pgids,omitempty"`
}{Pids: p.pids, Pgids: p.pgids}); err == nil {
info.PIDInfo = models.Jsonb{String: string(data)}
if jb, ok := pidInfoJSONB(p.pids, p.pgids); ok {
info.PIDInfo = jb
}
info.InteractiveJobURL = interactiveURLFromExdata(p.exdata)
}