You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scheduler: fix executor allocation for non-gang policies and gang batch semantics
Three interrelated bugs prevented correct executor allocation depending
on which scheduler plugins were configured.
Bug 1 — executors never allocated without gang (DRF-only, priority-only)
PluginManager aggregated is_ready/is_fulfilled with unwrap_or(true),
meaning when no plugin had a batch opinion the result was true.
AllocateAction's pre-check (`if fulfilled || ready { skip }`) and
DispatchAction's commit condition (`if fulfilled { commit }`) therefore
skipped or no-op'd every session, leaving tasks permanently pending.
Fix: replace bool aggregation with Option<bool> three-value logic —
Some(false) = veto, Some(true) = approved, None = no opinion.
Expose bool at the public API level by tracking per-session op counts
(no_gang_alloc_ops / no_gang_bind_ops) inside PluginManager when no
gang plugin is loaded. is_ready/is_fulfilled return false before the
first op and true after, so callers use uniform `if is_ready() { break; }`
without needing to know whether gang is configured.
Bug 2 — gang only ever created 1 executor regardless of task count
The old readiness formula `total % batch_size == 0` was true at
every multiple, so after one allocation (total=1, batch_size=1) the
loop broke and no further executors were created for the remaining
pending tasks.
Fix: introduce `incomplete_tasks` in GangState (sampled once per cycle
as Pending + Running task count) and compute:
needed = (incomplete_tasks / batch_size) * batch_size
ready = needed > 0 && total == needed
This creates exactly enough executors to cover all incomplete tasks in
full batches per scheduling cycle.
Bug 3 — duplicate executor creation when Dispatch and Allocate ran in the
same cycle for a gang session
DispatchAction lacked a pre-session guard: a session whose executor
was still in Binding state (counted in Gang's `allocated`) received
additional bindings every cycle.
Fix: check is_fulfilled before entering the bind loop; skip the session
if it is already satisfied.
Additional changes:
- Remove "drf" from DEFAULT_POLICIES; DRF remains opt-in via
cluster.policies. Default stack is now priority + gang + shim.
- Allow listing "shim" in cluster.policies without error (log warning,
ignore); shim is always-on.
- Regression tests added for all affected configurations:
· drf-only and priority-only: executor is allocated
· gang batch_size=1: one executor per incomplete task per cycle
· gang batch_size=2: exactly two executors per cycle
· no-gang: at most one executor per cycle
· dispatch without gang: idle executor is bound
· plugin aggregation: None/Some(false)/Some(true) edge cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0 commit comments