fix(apiserver): send exdata, exec cwd and exec home with submitted jobs (bug-1936)

Cause: modelJobToSubmit() filled only cmdline, execuser, launch_type and
pre/post-exec of job_startinfo_t, so the extended data the submit handler
builds (env, *_limit incl. -W run limit, file transfers, interactive mode),
the cwd and the exec home never reached the controller or the worker.

Fix: map the start info in a pure submitStartInfo() (exdata from
ExtendData, execcwd from ExecCWD or CWD, exechome from ExecHome) that
modelJobToSubmit copies into the C struct; decode exdata back in
cJobToModel/cJobArrayToModels so the store keeps ExtendData and CWD.
Reject extend_data the worker's parse_exdata() cannot take (not a JSON
object, over EXDATA_LENGTH-1 bytes) and drop an empty transfer list
instead of sending a null.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-10 08:28:50 +00:00
parent 7a9d75d661
commit d01ced9bd0
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
5 changed files with 236 additions and 14 deletions

View file

@ -236,6 +236,55 @@ type rawStartInfo struct {
execHome string
execCWD string
execUser string
exdata string
}
// submitStartInfo maps a Go JobInfo to the job_startinfo_t fields a
// submission carries to the controller (and from there, unchanged, to the
// worker's startjob request). modelJobToSubmit (convert.go) is the CGO
// adapter that copies the result into the C struct.
//
// bug-1936: exdata (env, limits, transfers, interactive mode, built by the
// submit handler into ExtendData), the exec cwd and the exec home were
// never set, so none of them reached the worker.
func submitStartInfo(job *models.JobInfo) rawStartInfo {
execCWD := job.ExecCWD
if execCWD == "" {
execCWD = job.CWD
}
return rawStartInfo{
cmdline: job.Command,
launchType: job.LaunchType,
preExec: job.PreExecCmd,
postExec: job.PostExecCmd,
execHome: job.ExecHome,
execCWD: execCWD,
execUser: resolveExecUser(job.ExecUser, job.SubmitUser),
exdata: job.ExtendData.String,
}
}
// maxExdataLen is the longest exdata the worker can take: xcecd hands it to
// xcecd_worker in job_assignment_t.exdata[EXDATA_LENGTH] (16 KiB,
// src/worker/include/uv_ipc.h), NUL included.
const maxExdataLen = 16*1024 - 1
// ValidateExdata checks a job's extended data before it is submitted: the
// worker's parse_exdata() fails the job at launch on anything but a JSON
// object, and a longer string than maxExdataLen would be cut short on its
// way to the job process.
func ValidateExdata(exdata string) error {
if exdata == "" {
return nil
}
if len(exdata) > maxExdataLen {
return fmt.Errorf("extended job data is %d bytes, more than the limit of %d", len(exdata), maxExdataLen)
}
var obj map[string]interface{}
if err := json.Unmarshal([]byte(exdata), &obj); err != nil {
return errors.New("extended job data must be a JSON object")
}
return nil
}
// rawSubmitData mirrors the C job_submit_data_t fields used by the Go model.
@ -358,7 +407,9 @@ func rawJobToModel(r rawJobData) *models.JobInfo {
info.PostExecCmd = si.postExec
info.ExecHome = si.execHome
info.ExecCWD = si.execCWD
info.CWD = si.execCWD // the submit cwd is sent as the exec cwd (bug-1936)
info.ExecUser = si.execUser
info.ExtendData = models.Jsonb{String: si.exdata}
}
}
info.SubmitTime = r.subTime