fix(scheduler): place a task-range job with the largest count that fits (bug-1931)

The parjob plan generator split only maxTasks, so a job submitted with a
task range (minTasks < maxTasks) pended until all maxTasks tasks fit,
even when minTasks did (a tasks 2..5 job pended on a host with 2 free
slots).

The runner now handles a task range in sched_alloc_range_(): it probes
minTasks first and pends after that one search when it does not fit;
otherwise it places maxTasks when that fits, or binary-searches for the
largest count that fits and places it. Fitting is monotone in the count
for balanced plans, so the search is exact for today's plan shapes.
ctx->ntasks carries the count; parjob's job-level pass scales per-task
shared wants and global limits to it, and the generator gets the request
with its task count fixed to it. Fixed counts keep the old path.

The decision trace shows each count tried (task count N (minimum) /
(maximum), placing N tasks) and releases a fitting probe's hosts.
Test fixtures that used minTasks = 1 as filler for an N-task job are now
fixed-count.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-09 08:51:44 +00:00
parent 7cdf25e001
commit e3b87d28f3
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
19 changed files with 726 additions and 32 deletions

View file

@ -189,6 +189,7 @@ typedef enum {
SCHED_TRACE_E_ROLLBACK, /* released hosts, merged (5.8) */
SCHED_TRACE_E_CHECK_ROW, /* cluster-wide check row (4.1, 4.2) */
SCHED_TRACE_E_HOST_ROW, /* unmet row under a host (5.4, 5.5) */
SCHED_TRACE_E_TASK_COUNT, /* task-range count tried (bug-1931) */
SCHED_TRACE_E_NTYPES
} sched_trace_entry_type_t;
@ -292,7 +293,7 @@ typedef struct sched_trace_entry_s {
int rc; /* > 0 satisfied, 0 not, < 0 error */
int not_tried_after; /* members after this one, not tried */
} resreq_end;
struct { /* ALT_SKIPPED, ALT_RESULT (i == 0: none chosen) */
struct { /* ALT_SKIPPED, ALT_RESULT (i == 0: none chosen), TASK_COUNT */
int i;
int n;
} alt;
@ -626,6 +627,8 @@ extern void sched_trace_check_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj);
extern void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
bool plans_follow);
/* bug-1931: count n next; kind 1 minimum, 2 maximum, 3 placing, else 0 */
extern void sched_trace_task_count(xce_sched_trace_rec_t* rec, int n, int kind);
/* No plan generated for the current requirement, and why (4.7). Counts
* the requirement under "no plan generated" (9.3), once: a search end

View file

@ -189,6 +189,10 @@ typedef struct {
xce_primary_resreq_t* primary;
int nhosts;
int nslots;
/* bug-1931: the task count a task-range attempt (minTasks < maxTasks)
* is trying, set by the runner before the job-level prepare pass; 0
* (sched_ctx_init()/sched_ctx_set_primary()) means primary->maxTasks. */
int ntasks;
struct {
xvec_t* shared;
zarray_t hosts;