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]

Reply via email to