GangScheduling.PreEnqueue & HierarchyTracker Gate Pod ActivationactiveQ, GangScheduling.PreEnqueue(ctx, pod) queries HierarchyTracker.FindRootGroupReadiness(pgKey). The entire root tree must satisfy its bottom-up readiness quorum without exceeding WorkloadMaxTreeDepth (4).
minCount)A leaf PodGroup counts both unscheduled pods in the queue AND already-scheduled pods in the cache: totalPods = unscheduledPods + scheduledPods. When totalPods >= spec.schedulingPolicy.gang.minCount (or >= 1 for Basic), the leaf PG marks itself READY and increments its parent CPG's readyChildren counter.
minGroupCount)An intermediate or root CompositePodGroup is READY when readyChildren >= spec.schedulingPolicy.gang.minGroupCount (or >= 1 for Basic). Readiness propagates up to the top-most Root CPG.
WorkloadMaxTreeDepth = 4)If climbing parent links takes more than 4 hops (or hits a cycle), FindRootGroupReadiness returns an error and PreEnqueue rejects the pod with UnschedulableAndUnresolvable.
Suppose cpg-root has minGroupCount: 1 with two child subtrees (cpg-sub1 and cpg-sub2). Even if cpg-sub2 is NOT ready yet, as soon as cpg-sub1 becomes ready, cpg-root has 1/1 ready children โ so all queued pods in the hierarchy pass PreEnqueue!
PodGroupInfo vs. QueuedPodGroupInfoPriorityQueue.Pop() pops a ready hierarchy, it returns a *framework.QueuedPodGroupInfo wrapping a structural *framework.PodGroupInfo tree. Understanding what is inside each structure is essential.
PodGroupInfo (Structural Hierarchy Tree)All Groups in Tree
PodGroupInfo contains the entire known hierarchy tree rooted at the top-most CompositePodGroup, with Children deterministically sorted by name during structural mutations (AddSubtree / UpdateGenericPodGroup).
QueuedPodGroupInfo (Unscheduled Pods for This Cycle)Only Unscheduled Queued Pods
QueuedPodGroupInfo embeds *PodGroupInfo and maps each leaf EntityKey to the []*QueuedPodInfo that were waiting in the queue when popped:
CompositePodGroup does not have to schedule all of its subtrees in a single cycle. Click through the 3 stages below to see how an already-scheduled CPG wakes up when new pods turn on additional subgroups!
runRootSchedulingAlgorithmPlacementFeasible, Node CPU Contention & S โ F Subtree RollbackrunRootSchedulingAlgorithm, the scheduler traverses the CPG tree depth-first, tentatively assuming pods on nodes via assumeAndReserveWithRevert and checking PlacementFeasible after every pod and child subgroup.
S โ F RollbackTestCPGHierarchicalScheduling_Internalcpg-sub1 (pg1: 2 pods ร 2 CPU) โ Fits on node-1 & node-2pg1-pod-0 takes 2 CPU on node-1; pg1-pod-1 takes 2 CPU on node-2. cpg-sub1 meets minGroupCount: 1. Remaining cluster CPU: 2 CPU on node-1, 2 CPU on node-2.
cpg-sub2 requires minGroupCount: 2 (needs BOTH pg2 AND pg3)First, pg2 (2 pods ร 2 CPU) runs: pg2-pod-0 fits on node-1 (2/4 CPU left -> 4/4 CPU used) and pg2-pod-1 fits on node-2 (4/4 CPU used). pg2 raw status is Success!
pg3-pod-0 Fails NodeResourcesFit โ cpg-sub2 Fails โ pg2 Rolled Back!Next, pg3-pod-0 tries to schedule, but both nodes are at 4/4 CPU! NodeResourcesFit returns 0/2 nodes available: 2 Insufficient cpu. pg3 fails PlacementFeasible (pg3-pod-1 is skipped immediately). Now cpg-sub2 only has 1/2 scheduled children (`pg2`), so cpg-sub2 fails PlacementFeasible! Its revertFn rolls back pg2's reservations on node-1 and node-2, and completeCompositePodGroupAlgorithmResult overwrites pg2 from Success โ Unschedulable (S โ F)!
completeCompositePodGroupAlgorithmResultS โ F PropagationAddAttemptedPodGroupIfNeededsubmitPodGroupAlgorithmResult, every pod that was not bound (failed filter, skipped, or rolled back S โ F) plus any new pods that arrived while the CPG was popped are reconciled by PriorityQueue.AddAttemptedPodGroupIfNeeded.
pendingPodGroupPodsDuring Cycle
While cpg-root is popped from activeQ, activeQ.isLastPoppedEntity(cpg-root) is true.
โข Every failed/rolled-back pod passed to FailureHandler -> AddUnschedulablePodIfNotPresent is intercepted and placed into pendingPodGroupPods[rootKey].
โข Any brand-new pod arriving via PriorityQueue.Add() during the cycle ALSO goes into pendingPodGroupPods[rootKey]!
QueuedPodGroupInfoEnd of Cycle
AddAttemptedPodGroupIfNeeded(logger, podGroupInfo, cycle, rootStatus) drains pendingPodGroupPods[rootKey] and constructs a fresh QueuedPodGroupInfo containing:
[pg2-pod-0, pg2-pod-1, pg3-pod-0, pg3-pod-1, + any in-flight pods]
Notice that pg1-pod-0 and pg1-pod-1 are already bound in Cache, so they are NOT in the new QueuedPodGroupInfo!
Where does the rebuilt QueuedPodGroupInfo go?
โข If PreEnqueue fails on the remaining pods: parked in unschedulableEntities until more pods/events arrive.
โข If rootStatus == Success (e.g. cpg-sub1 bound!): requeueWithoutBackoff = true & preserveTimestamp = true โ goes straight into activeQ with zero backoff!
โข If rootStatus == Unschedulable: increments UnschedulableCount and goes to backoffQ.