This is an automated email from the ASF dual-hosted git repository.
manirajv06 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-core.git
The following commit(s) were added to refs/heads/master by this push:
new ba2609a3 [YUNIKORN-3449] Preemption falsely aborts and starves large
asks by ignoring node available capacity in shortfall check (#1157)
ba2609a3 is described below
commit ba2609a34bd66defed6a64fa1d2c6526fb50fae7
Author: hedger9487 <[email protected]>
AuthorDate: Wed Sep 16 18:01:16 2026 +0530
[YUNIKORN-3449] Preemption falsely aborts and starves large asks by
ignoring node available capacity in shortfall check (#1157)
When evaluating candidates in TryPreemption(), downstream shortfall
verification previously compared victimsTotalResource against the ask demand
directly, ignoring the candidate node's existing available capacity
(nodeAvailableMap[nodeID]). For node-local preemptions where the node has
partial free capacity and victims cover the remaining deficit, preemption
falsely aborted with PreemptionShortfall.
Decouple node physical shortfall checks from queue preemption checks in
TryPreemption(). For node-driven preemptions where all victims reside on the
target node (!fitIn && !hasVictimsOnOtherNodes), consider the node's available
capacity alongside victims (avail + victimVal >= needVal).
Closes: #1157
Signed-off-by: mani <[email protected]>
---
pkg/scheduler/objects/preemption.go | 20 +++++++++++----
pkg/scheduler/objects/preemption_test.go | 43 ++++++++++++++++++++++++++++++++
2 files changed, 58 insertions(+), 5 deletions(-)
diff --git a/pkg/scheduler/objects/preemption.go
b/pkg/scheduler/objects/preemption.go
index 0ec90804..e44c5b93 100644
--- a/pkg/scheduler/objects/preemption.go
+++ b/pkg/scheduler/objects/preemption.go
@@ -643,11 +643,15 @@ func (p *Preemptor) TryPreemption() (*AllocationResult,
bool) {
// on different criteria. for example, victims could be picked up
either from specific node (bin packing) or
// from multiple nodes (fair) given the choices.
var finalVictims []*Allocation
+ hasVictimsOnOtherNodes := false
for _, victim := range victims {
// Victims from any node is acceptable as long as chosen node
has enough space to accommodate the ask
// Otherwise, preempting victims from 'n' different nodes
doesn't help to achieve the goal.
- if !fitIn && victim.GetNodeID() != nodeID {
- continue
+ if victim.GetNodeID() != nodeID {
+ hasVictimsOnOtherNodes = true
+ if !fitIn {
+ continue
+ }
}
// check if victim contributes to any resource dimension that
is still needed
allocRes := victim.GetAllocatedResource()
@@ -663,9 +667,15 @@ func (p *Preemptor) TryPreemption() (*AllocationResult,
bool) {
hasShortfall := victimsTotalResource.IsEmpty()
if !hasShortfall {
for k, victimVal := range victimsTotalResource.Resources {
- if needVal, ok :=
p.ask.GetAllocatedResource().Resources[k]; ok && victimVal < needVal {
- hasShortfall = true
- break
+ if needVal, ok :=
p.ask.GetAllocatedResource().Resources[k]; ok {
+ var avail resources.Quantity
+ if !fitIn && !hasVictimsOnOtherNodes {
+ avail =
p.nodeAvailableMap[nodeID].Resources[k]
+ }
+ if avail+victimVal < needVal {
+ hasShortfall = true
+ break
+ }
}
}
}
diff --git a/pkg/scheduler/objects/preemption_test.go
b/pkg/scheduler/objects/preemption_test.go
index 89af4320..82d818ca 100644
--- a/pkg/scheduler/objects/preemption_test.go
+++ b/pkg/scheduler/objects/preemption_test.go
@@ -2467,3 +2467,46 @@ func Test_PreemptReleasesReservationsOnSuccess(t
*testing.T) {
case <-time.After(100 * time.Millisecond):
}
}
+
+// TestTryPreemption_NodeAvailableDeficit proves YUNIKORN-3449:
+// When a node has partial available capacity, preemption should succeed if
victims cover the remaining deficit.
+func TestTryPreemption_NodeAvailableDeficit(t *testing.T) {
+ appQueueMapping := NewAppQueueMapping()
+ node := newNode(nodeID1, map[string]resources.Quantity{"first": 4})
+ iterator := getNodeIteratorFn(node)
+ rootQ, err := createRootQueue(map[string]string{"first": "20"})
+ assert.NilError(t, err)
+ parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true,
map[string]string{"first": "20"}, map[string]string{"first": "10"},
appQueueMapping)
+ assert.NilError(t, err)
+ childQ1, err := createManagedQueueGuaranteed(parentQ, "child1", false,
nil, nil, appQueueMapping)
+ assert.NilError(t, err)
+ childQ2, err := createManagedQueueGuaranteed(parentQ, "child2", false,
map[string]string{"first": "20"}, map[string]string{"first": "15"},
appQueueMapping)
+ assert.NilError(t, err)
+
+ app1 := newApplication(appID1, "default", "root.parent.child1")
+ app1.SetQueue(childQ1)
+ childQ1.AddApplication(app1)
+ appQueueMapping.AddAppQueueMapping(app1.ApplicationID, childQ1)
+
+ // alloc1 on node-1 uses first: 2. (Node capacity: 4, so nodeAvailable
= 2 free)
+ ask1 := newAllocationAsk("alloc1", appID1,
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2}))
+ assert.NilError(t, app1.AddAllocationAsk(ask1))
+ alloc1 := newAllocationWithKey("alloc1", appID1, nodeID1,
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 2}))
+ app1.AddAllocation(alloc1)
+ assert.Check(t, node.TryAddAllocation(alloc1), "node alloc1 failed")
+ assert.NilError(t,
childQ1.TryIncAllocatedResource(ask1.GetAllocatedResource()))
+
+ // Preemptor ask in childQ2 needs first: 4
+ app2, ask2, err := creatApp2(childQ2,
map[string]resources.Quantity{"first": 4}, "alloc2", appQueueMapping)
+ assert.NilError(t, err)
+
+ headRoom :=
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10})
+ preemptor := NewPreemptor(app2, headRoom, 30*time.Second, ask2,
iterator(), false)
+
+ result, ok := preemptor.TryPreemption()
+ assert.Assert(t, ok, "preemption should succeed: 2 available on node +
2 victim = 4 needed")
+ assert.Assert(t, result != nil, "expected non-nil allocation result")
+ assert.Equal(t, "alloc2", result.Request.GetAllocationKey())
+ assert.Equal(t, nodeID1, result.NodeID)
+ assert.Check(t, alloc1.IsPreempted(), "alloc1 should be preempted")
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]