New spec from /kiro-discovery (2026-09-29), added to roadmap.md: - Begin time: the scheduler won't start a job before it (gate in sched_job_is_ready() using the core clock). New pend reason BEGIN_TIME_NOT_REACHED. - End time: the worker enforces it for running jobs (SIGTERM, grace, SIGKILL) and records a persisted END_TIME_REACHED exit reason. The primary is not involved. A pending job whose end time can't be met stays PEND with END_TIME_UNMEETABLE until the user kills or modifies it. - Runtime estimate (-We): a scheduling hint only. It sets the rule now + (estimate, else run limit, else 0) > end; an estimate larger than the run limit is rejected. - Times and estimate can be changed only while the job is pending, through a new `xce jobs modify` in xcecli and a new `jobmod` in the harness. Harness jobsub gains all four options. - Fixes the silent drop of xcecli -b/-t/-W and REST run_time_estimation, which the apiserver's modelJobToSubmit never passes to the controller. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
14 KiB
Brief: job-time-window
Problem
XCE users can't constrain when a job runs:
- Begin time. "Don't start this job before 22:00", for example to wait for off-peak hours, input data, or a maintenance window to end.
- End time. "This job must be finished by 06:00", for example when results are useless after a deadline, or a reservation or maintenance starts.
xcecli jobs submit already offers -b/--begin_time, -t/--term_time and
-W/--run_limit, and the apiserver accepts them. They are silently dropped
before reaching the controller, so users believe they are protected when they
are not.
Current State
Codebase exploration, 2026-09-29.
Client and apiserver
- xcecli:
-b(cmd/jobs/submit.go:682),-t(:686) and-W(:688), parsed intojobReq.BeginTime/TermTime/RunLimit(:278, :312-330). - apiserver submit request:
BeginTime,TermTime,RunTimeEstimationandRunLimit(models/jobs.go:641-674). They are copied only into the Go job model (controllers/jobs.go:1475-1487, restart path :1287-1318).run_limitis folded into theExtendDataJSON underlimits(controllers/jobs.go:2195-2211).
- The gap:
modelJobToSubmit(backend/convert.go:272-318) sets none of begin time, term time, runtime estimate, orExtendData→start_info.exdata. So-b,-tand-Wnever reach the controller. Only thexce_testharness's-x <exdata>delivers a run limit today.
C side
job_submit_data_t(include/xce/types.h:356-370),JobSubmitData(schema/controller.capnp:579-591, last ordinal @10) andxce_sched_job_t(scheduler/include/sched/types.h:122-142) have no begin, end or limit field.
Scheduler
- The FCFS picker (
plugins/builtin_fcfs.c:837-887) takes the first job for whichsched_queue_is_ready()andsched_job_is_ready()both hold. Both are stubs returning 1 (core.c:490,:493), which is the natural eligibility hook. - Sessions run about every 1 s (
main.c:508-545), so the scheduler notices a begin time within about a second, with no extra wakeup. - Time must come from
xce_core_now(core)/core->session_start(core_clock.c), nevertime(). That keeps the simulator's discrete clock and--sim-verifydeterministic, andtest_core_isolation.cforbids wall-clock calls incore*.c.
Pend reasons
- Only resource reasons exist:
PREASON_BAD_RESREQ,PREASON_NO_RESOURCEandPREASON_MATCH_FAIL(include/preason.h:7-9). - There is no non-resource reason, and no publish path for a job the picker
skips. Publishing goes through
sched_publish_pend_reason(scheduler.c:1821).
Worker
- The run limit is read from
exdata.limits.run_limit(xcecd_exdata.c:306-309) and set asRuntimeMaxUSecon the transient unit (xcecd_cgroups.c:753-770). That is relative to unit start. - The kill path is
xcecd_worker.c:1115-1124(signal_gid_except_self, TERM→KILL escalation). - The job helper
xcecd_workerruns inside the unit and reports the job's exit (uv_ipc.c:1000). If systemd times out the unit, the helper is killed too, and the exit shows only as a plain signal exit.
Exit info
job_exitinfo_tis{exitcode}only (types.h:378-380; capnpJobExitInfo).job_failinfo_t.detailis not persisted (WAL_JOB_FAIL carries code plus stage only,persist.c:4224-4257).- There is no exit-reason field.
Persistence
- The WAL_JOB_SUBMIT payload is the whole capnp
xce_job_t, so newJobSubmitDatafields ride along automatically. - These must be updated by hand:
- the libxce job-submit interning table (
lib/jobj.c:29-55,:123-182). Its equality and hash must include the new fields, or submits that differ only in time would be merged; - the scheduler's
schedobj.c:253-279and update path:733; - the scheduler snapshot
snapshot_wire.c:134-166, which rebuildssubmitfield by field.
- the libxce job-submit interning table (
Desired Outcome
- Begin time.
- A job is not started before its begin time.
- Until then it pends with a clear pend reason: "begin time not reached (starts at T)".
- End time, running jobs (user decision, 2026-09-29: the worker handles
running jobs, and the primary is not involved).
- A running job still running at its end time is terminated by its worker:
- a warning signal (SIGTERM);
- a configurable grace period;
- then SIGKILL.
- The job ends as EXIT with a recorded exit reason, "end time reached", visible to users and persisted.
- A running job still running at its end time is terminated by its worker:
- End time, pending jobs (user decision).
- A pending job whose end time can no longer be met is not started and stays PEND with a clear pend reason ("end time cannot be met").
- It stays that way until the user kills it or changes its end time. It is never auto-expired, and the controller runs no sweep.
- Runtime estimate (user decision, 2026-09-29, folded into this spec).
- A new submit option
xcecli jobs submit --estimated_run_time/-We. The REST API already acceptsrun_time_estimation(models/jobs.go:659) but drops it, like-b/-t/-W. - It is a scheduling hint only and is never enforced. That is the
difference from the run limit
-W, which is a hard cap the worker kills at; this mirrors LSF-Weversus-W. - It is carried end to end to the scheduler (object and snapshot) and can be changed while the job is pending.
- If both are given and estimate > run limit, the submit is rejected with a clear error (user decision).
- An
application-profilemay later supply a default estimate.
- A new submit option
- "End time cannot be met" rule (settled). A pending job is not started when
now + expected_runtime > end, whereexpected_runtimeis the estimate if given, else the run limit if given, else 0 (plainnow ≥ end).nowcomes from the scheduler core clock. - Changes. Begin time, end time and the runtime estimate can be changed only while the job is pending (user decision). Once the job runs, its end time is fixed, so the worker never needs live updates.
- Wiring fix (user decision: fixed in this spec).
-b,-tand-W(exdata/run_limit) actually reach the controller, scheduler and worker. Log it in the buglog as the drop defect. - Validation at submit.
- Reject begin ≥ end.
- Reject an end time already in the past.
- Reject a time that cannot be parsed.
- Both client tools support every new option (user direction, 2026-09-29).
- xcecli (
cmd/jobs/submit.go):-b/--begin_time,-t/--term_timeand-W/--run_limitexist today but are dropped downstream; this spec makes them work end to end;- add
-We/--estimated_run_time; xceclihas no command for changing a submitted job, so add one, for examplexce jobs modify <jobid> [-b] [-t] [-We], pending jobs only.
- Harness
xce_test(src/harness/test.c):jobsub(jobsub_flags[]at :3575, usage at :602) today takes only-u,-q,-c,-R,-n,-xand-a. Add begin time, end time, run limit and estimate (for example-b/--begin-time,-t/--end-time,-W/--run-limit,-e/--estimated-run-time; short letters are settled in design).-Wmust be a first-class flag rather than hand-written-xexdata JSON, and it must compose with a user-supplied-x.- Add a matching
jobmodcommand for pending jobs. - Update the harness help (
test_repl_help-style help tests) and thexce_testrc-file or script docs.
- Both tools accept the same time formats, validated identically: shared rules for absolute times, relative offsets and time zones, settled in requirements.
- Both show clear errors for begin ≥ end, an end time in the past, and estimate > run limit.
- xcecli (
- Visibility. Both times appear in job details (
job-status-detail-displayrenders the new pend and exit reasons).
Approach
Chosen: approach B′, the worker enforces running jobs and the scheduler gates pending ones (user direction, 2026-09-29). This keeps the primary out of it. The rejected option (A) was a primary-only controller sweep with the worker as a backstop.
-
Fields. Add
beginTimeandendTime(absolute epoch seconds, 0 = unset) andrunEstimate(seconds, 0 = unset) as additive fields onJobSubmitData. The run limit stays inexdata.limits.run_limit, but the scheduler also needs it for the unmeetable check, so expose it on the scheduler job object. Carry them through:job_submit_data_t;- the libxce interning equality and hash;
- the apiserver
modelJobToSubmit, which also passesExtendData→exdata; xce_sched_job_tplusschedobj.c;- the scheduler snapshot;
- the recording and replay path.
-
Scheduler gate in
sched_job_is_ready(), or an equivalent eligibility step, usingxce_core_now(core):now < begin→ not eligible, reason BEGIN_TIME_NOT_REACHED;- "end cannot be met" → not eligible, reason END_TIME_UNMEETABLE.
Two new non-resource pend-reason codes, published for skipped jobs.
-
Worker enforcement. The job helper runs the deadline itself: at
end, it sends SIGTERM to the job's process group, waits the grace period, then sends SIGKILL, and reports the exit with reason END_TIME_REACHED. Belt and braces: the unit'sRuntimeMaxUSecis set toend + grace + margin − nowat launch, as a last-resort backstop if the helper itself hangs. Every node of a multi-host job enforces its own tasks. -
Exit reason. An additive exit-reason field on the job's exit info, persisted through WAL_JOB_FAIL/FINISH and the snapshot (field completeness).
-
Changing times on a pending job goes through the existing job modify/update path (WAL_JOB_UPDATE) and is refused for non-pending jobs.
Scope
- In:
- The begin and end fields and the runtime estimate end to end (new
-We/--estimated_run_timexcecli flag), and the-W/exdatawiring fix. - Submit validation, including estimate > run limit.
- Submit validation.
- The scheduler gate and the two new pend reasons.
- Worker end-time enforcement with grace, plus the backstop.
- The new, persisted exit reason.
- Changing the times of pending jobs, through a new
xce jobs modifyin xcecli and a newjobmodin the harness. - Client support in both xcecli (the
-Weflag, with-b/-t/-Wmade to work) and the harnessjobsub(all four options), with help text and docs. - Display in xcecli and REST.
- Simulator determinism (the gate uses the core clock).
- Tests: unit, sim, and a live kill-at-deadline test.
- The begin and end fields and the runtime estimate end to end (new
- Open for requirements:
- The grace-period default, and whether it is cluster config or per job.
- The time zone and format accepted by
-b/-t(xcecliutils.ParseTime). - Whether a job array's elements share the window.
- Clock-skew tolerance between the scheduler, controller and worker hosts (NTP assumed).
- Out:
- Controller-side enforcement or sweeps (user decision).
- Automatic expiry of unmeetable pending jobs (user decision: they stay PEND).
- Changing the end time of a running job.
- Backfill or advance reservation built on end times.
- Deadline-based preemption (
scheduler-preemptionlists it as out of scope). - Queue run and dispatch windows (
types.h:570-573,lib/window.c, unused).
Boundary Candidates
- Field plumbing: capnp, C structs, libxce interning, apiserver conversion, scheduler object and snapshot, sim recording.
- Client surfaces: xcecli submit and modify, harness
jobsubandjobmod, shared time parsing and validation. - The scheduler eligibility gate and the new pend-reason codes.
- Worker deadline enforcement (helper timer plus unit backstop).
- The exit-reason field and its persistence.
- The pending-job change path and submit validation.
Out of Boundary
- The display of reasons (
job-status-detail-display). This spec adds the codes; that spec's code-to-text table needs the new entries. - How pend reasons are grouped for jobs with alternative resource requirements
(
scheduler-alter-pend-reasons). - Deciding which jobs a user may see or control (existing RBAC).
Upstream / Downstream
- Upstream:
- The existing submit, modify and kill paths.
- The REST
run_time_estimationfield, which exists but is dropped today. - The scheduler FCFS picker and
sched_job_is_ready(). - The worker transient unit (
RuntimeMaxUSecsupport already exists). - The WAL submit and update facts.
- Downstream:
job-status-detail-display, which shows the times and the new reason texts.- Future backfill or reservation work, which can use the runtime estimate.
application-profile, where resource defaults might supply a default run limit.
Existing Spec Touchpoints
- Extends: none.
- Adjacent:
job-status-detail-display(new pend and exit reason codes need text)scheduler-alter-pend-reasons(pend-reason model)scheduler-preemptioncontroller-job-cleanup(an EXIT from the end time becomes eligible for cleanup as usual)application-profilescheduler-sim-discrete-clock(the core clock)
Constraints
- The primary does no end-time work. Enforcement lives on the worker, and gating lives in the scheduler.
- All wire and schema changes are additive Cap'n Proto. Old WAL records and snapshots decode with no window, which is today's behaviour.
- Save/restore field completeness (
persistence.md): the begin and end fields and the exit reason must round trip through the WAL submit, update and fail facts, the controller snapshot, the scheduler snapshot and the recordings. Run/xce-audit-invariants(controller dimension 7, scheduler dimension 4). - The scheduler's core time only comes from
xce_core_now, so--sim-verifystays deterministic. - The worker's non-systemd process-manager build (
#ifndef DBUS_MANAGER) must enforce the deadline too; the helper timer covers it. - TDD:
testsuite/first, then_test.go. ASAN must be clean. - HA/persistence touch: use the full 3-phase workflow.