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

@ -32,6 +32,15 @@ func goString(s *C.char) string {
return C.GoString(s)
}
// cStringOrNil returns a C copy of s, or nil for an empty string so an unset
// field stays NULL in the C struct. The caller frees the result.
func cStringOrNil(s string) *C.char {
if s == "" {
return nil
}
return C.CString(s)
}
// cJobArrayToModels decodes a compact Data.jobArray push descriptor
// (*C.xce_job_submit_t: base jobid, array_size, one shared job_submit_data_t
// spec — the same JobSubmit shape apiserver_push_jobarray() sends,
@ -69,6 +78,7 @@ func cJobArrayToModels(jobArray *C.xce_job_submit_t) []*models.JobInfo {
execHome: goString(si.exechome),
execCWD: goString(si.execcwd),
execUser: goString(si.execuser),
exdata: goString(si.exdata),
}
}
@ -110,6 +120,7 @@ func cJobToModel(job *C.xce_job_t) *models.JobInfo {
execHome: goString(si.exechome),
execCWD: goString(si.execcwd),
execUser: goString(si.execuser),
exdata: goString(si.exdata),
}
}
r.submit = rd
@ -321,20 +332,16 @@ func cClusterConfToClusterInfo(conf *C.cluster_config_t) *models.ClusterInfo {
// modelJobToSubmit converts a Go JobInfo to C xce_job_submit_t heap structs.
// The caller must free all returned C memory by calling freeJobSubmit.
func modelJobToSubmit(job *models.JobInfo) (*C.xce_job_submit_t, *C.job_submit_data_t, *C.job_startinfo_t) {
rsi := submitStartInfo(job)
si := (*C.job_startinfo_t)(C.calloc(1, C.sizeof_job_startinfo_t))
si.cmdline = C.CString(job.Command)
if execUser := resolveExecUser(job.ExecUser, job.SubmitUser); execUser != "" {
si.execuser = C.CString(execUser)
}
if job.LaunchType != "" {
si.launch_type = C.CString(job.LaunchType)
}
if job.PreExecCmd != "" {
si.job_pre_exec = C.CString(job.PreExecCmd)
}
if job.PostExecCmd != "" {
si.job_post_exec = C.CString(job.PostExecCmd)
}
si.cmdline = C.CString(rsi.cmdline)
si.execuser = cStringOrNil(rsi.execUser)
si.launch_type = cStringOrNil(rsi.launchType)
si.job_pre_exec = cStringOrNil(rsi.preExec)
si.job_post_exec = cStringOrNil(rsi.postExec)
si.exechome = cStringOrNil(rsi.execHome)
si.execcwd = cStringOrNil(rsi.execCWD)
si.exdata = cStringOrNil(rsi.exdata)
sd := (*C.job_submit_data_t)(C.calloc(1, C.sizeof_job_submit_data_t))
sd.queue = C.CString(job.QueueName)
@ -381,6 +388,9 @@ func freeJobSubmit(js *C.xce_job_submit_t, sd *C.job_submit_data_t, si *C.job_st
C.free(unsafe.Pointer(si.launch_type))
C.free(unsafe.Pointer(si.job_pre_exec))
C.free(unsafe.Pointer(si.job_post_exec))
C.free(unsafe.Pointer(si.exechome))
C.free(unsafe.Pointer(si.execcwd))
C.free(unsafe.Pointer(si.exdata))
C.free(unsafe.Pointer(si))
}
if sd != nil {